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.
This commit is contained in:
Nico Gründel 2025-11-13 11:02:05 +01:00 committed by GitHub
parent a176408d81
commit df57b0b847
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 20 additions and 1 deletions

View file

@ -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<PanelEvent> + 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<TabPanel>, 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<TabPanel>, 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<Vec<Button>>;
fn view(&self) -> AnyView;
@ -210,6 +218,14 @@ impl<T: Panel> PanelView for Entity<T> {
})
}
fn on_added_to(&self, tab_panel: WeakEntity<TabPanel>, 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)
}

View file

@ -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<Self>,
) {
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() {