From df57b0b847b826ef72b0447326463f5c0f2cc85d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Gr=C3=BCndel?= Date: Thu, 13 Nov 2025 11:02:05 +0100 Subject: [PATCH] dock: Add a way for Panels to track the tab bar they belong to (#1580) Currently, there is no good way for a Panel in a dock to e.g. add a tab to its own tab bar. This is especially annoying as the state in DockArea::items isn't synced to the underlying views, meaning the `DockArea::add_panel` function will do nothing after first splitting the panel, and then deleting the only element in the first part of the split. https://github.com/user-attachments/assets/2c61f2f0-9b1c-4154-8a53-b917335b94f0 This pull request adds a function that is called when a PanelView is added to a tabbar via `add_panel` or `insert_panel_at` or when it is removed via `detach_panel`. This allows for entities that implement Panel to keep track of and directly access the tab bar they belong to, and for example add new panels to it through buttons in the dropdown menu or the top right of the tab bar. --- crates/ui/src/dock/panel.rs | 18 +++++++++++++++++- crates/ui/src/dock/tab_panel.rs | 3 +++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/crates/ui/src/dock/panel.rs b/crates/ui/src/dock/panel.rs index 30a0c6b5..3d213c01 100644 --- a/crates/ui/src/dock/panel.rs +++ b/crates/ui/src/dock/panel.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, sync::Arc}; -use crate::{button::Button, menu::PopupMenu}; +use crate::{button::Button, dock::TabPanel, menu::PopupMenu}; use gpui::{ AnyElement, AnyView, App, AppContext as _, Entity, EntityId, EventEmitter, FocusHandle, Focusable, Global, Hsla, IntoElement, Render, SharedString, WeakEntity, Window, @@ -118,6 +118,12 @@ pub trait Panel: EventEmitter + Render + Focusable { /// Only current Panel will touch this method. fn set_zoomed(&mut self, zoomed: bool, window: &mut Window, cx: &mut App) {} + /// When this Panel is added to a TabPanel, this will be called. + fn on_added_to(&mut self, tab_panel: WeakEntity, window: &mut Window, cx: &mut App) {} + + /// When this Panel is removed from a TabPanel, this will be called. + fn on_removed(&mut self, window: &mut Window, cx: &mut App) {} + /// The addition dropdown menu of the panel, default is `None`. fn dropdown_menu(&self, this: PopupMenu, window: &Window, cx: &App) -> PopupMenu { this @@ -153,6 +159,8 @@ pub trait PanelView: 'static + Send + Sync { fn visible(&self, cx: &App) -> bool; fn set_active(&self, active: bool, window: &mut Window, cx: &mut App); fn set_zoomed(&self, zoomed: bool, window: &mut Window, cx: &mut App); + fn on_added_to(&self, tab_panel: WeakEntity, window: &mut Window, cx: &mut App); + fn on_removed(&self, window: &mut Window, cx: &mut App); fn dropdown_menu(&self, menu: PopupMenu, window: &Window, cx: &App) -> PopupMenu; fn toolbar_buttons(&self, window: &mut Window, cx: &mut App) -> Option>; fn view(&self) -> AnyView; @@ -210,6 +218,14 @@ impl PanelView for Entity { }) } + fn on_added_to(&self, tab_panel: WeakEntity, window: &mut Window, cx: &mut App) { + self.update(cx, |this, cx| this.on_added_to(tab_panel, window, cx)); + } + + fn on_removed(&self, window: &mut Window, cx: &mut App) { + self.update(cx, |this, cx| this.on_removed(window, cx)); + } + fn dropdown_menu(&self, menu: PopupMenu, window: &Window, cx: &App) -> PopupMenu { self.read(cx).dropdown_menu(menu, window, cx) } diff --git a/crates/ui/src/dock/tab_panel.rs b/crates/ui/src/dock/tab_panel.rs index 31e33d6b..1a292858 100644 --- a/crates/ui/src/dock/tab_panel.rs +++ b/crates/ui/src/dock/tab_panel.rs @@ -256,6 +256,7 @@ impl TabPanel { return; } + panel.on_added_to(cx.entity().downgrade(), window, cx); self.panels.push(panel); // set the active panel to the new panel if active { @@ -304,6 +305,7 @@ impl TabPanel { return; } + panel.on_added_to(cx.entity().downgrade(), window, cx); self.panels.insert(ix, panel); self.set_active_ix(ix, window, cx); cx.emit(PanelEvent::LayoutChanged); @@ -329,6 +331,7 @@ impl TabPanel { window: &mut Window, cx: &mut Context, ) { + panel.on_removed(window, cx); let panel_view = panel.view(); self.panels.retain(|p| p.view() != panel_view); if self.active_ix >= self.panels.len() {