dock: Simplify DockItem API. (#1732)

## Break Change

- The `DockItem::tabs` has removed `active_ix` argument, use
`.active_index()` method instead.

```diff
- DockItem::tabs(vec![panel], Some(1), &weak_self, window, cx)
+ DockItem::tabs(vec![panel], &weak_self, window, cx).active_index(1)
```
This commit is contained in:
Jason Lee 2025-12-03 17:53:37 +08:00 committed by GitHub
parent e2f022cb59
commit b70a0633ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 109 additions and 43 deletions

View file

@ -258,8 +258,7 @@ impl StoryWorkspace {
fn reset_default_layout(dock_area: WeakEntity<DockArea>, window: &mut Window, cx: &mut App) { fn reset_default_layout(dock_area: WeakEntity<DockArea>, window: &mut Window, cx: &mut App) {
let dock_item = Self::init_default_layout(&dock_area, window, cx); let dock_item = Self::init_default_layout(&dock_area, window, cx);
let left_panels = DockItem::split_with_sizes( let left_panels = DockItem::v_split(
Axis::Vertical,
vec![ vec![
DockItem::tab( DockItem::tab(
StoryContainer::panel::<ListStory>(window, cx), StoryContainer::panel::<ListStory>(window, cx),
@ -272,38 +271,33 @@ impl StoryWorkspace {
Arc::new(StoryContainer::panel::<ScrollbarStory>(window, cx)), Arc::new(StoryContainer::panel::<ScrollbarStory>(window, cx)),
Arc::new(StoryContainer::panel::<AccordionStory>(window, cx)), Arc::new(StoryContainer::panel::<AccordionStory>(window, cx)),
], ],
None,
&dock_area, &dock_area,
window, window,
cx, cx,
), )
.size(px(360.)),
], ],
vec![None, Some(px(360.))],
&dock_area, &dock_area,
window, window,
cx, cx,
); );
let bottom_panels = DockItem::split_with_sizes( let bottom_panels = DockItem::v_split(
Axis::Vertical,
vec![DockItem::tabs( vec![DockItem::tabs(
vec![ vec![
Arc::new(StoryContainer::panel::<TooltipStory>(window, cx)), Arc::new(StoryContainer::panel::<TooltipStory>(window, cx)),
Arc::new(StoryContainer::panel::<IconStory>(window, cx)), Arc::new(StoryContainer::panel::<IconStory>(window, cx)),
], ],
None,
&dock_area, &dock_area,
window, window,
cx, cx,
)], )],
vec![None],
&dock_area, &dock_area,
window, window,
cx, cx,
); );
let right_panels = DockItem::split_with_sizes( let right_panels = DockItem::v_split(
Axis::Vertical,
vec![ vec![
DockItem::tab( DockItem::tab(
StoryContainer::panel::<ImageStory>(window, cx), StoryContainer::panel::<ImageStory>(window, cx),
@ -318,7 +312,6 @@ impl StoryWorkspace {
cx, cx,
), ),
], ],
vec![None],
&dock_area, &dock_area,
window, window,
cx, cx,
@ -340,8 +333,7 @@ impl StoryWorkspace {
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
) -> DockItem { ) -> DockItem {
DockItem::split_with_sizes( DockItem::v_split(
Axis::Vertical,
vec![DockItem::tabs( vec![DockItem::tabs(
vec![ vec![
Arc::new(StoryContainer::panel::<ButtonStory>(window, cx)), Arc::new(StoryContainer::panel::<ButtonStory>(window, cx)),
@ -364,12 +356,10 @@ impl StoryWorkspace {
Arc::new(StoryContainer::panel::<FormStory>(window, cx)), Arc::new(StoryContainer::panel::<FormStory>(window, cx)),
Arc::new(StoryContainer::panel::<NotificationStory>(window, cx)), Arc::new(StoryContainer::panel::<NotificationStory>(window, cx)),
], ],
None,
&dock_area, &dock_area,
window, window,
cx, cx,
)], )],
vec![None],
&dock_area, &dock_area,
window, window,
cx, cx,

View file

@ -3,15 +3,15 @@
use std::{ops::Deref, sync::Arc}; use std::{ops::Deref, sync::Arc};
use gpui::{ use gpui::{
div, prelude::FluentBuilder as _, px, App, AppContext, Axis, Context, Element, Empty, Entity, App, AppContext, Axis, Context, Element, Empty, Entity, IntoElement, MouseMoveEvent,
IntoElement, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Render, Style, MouseUpEvent, ParentElement as _, Pixels, Point, Render, Style, StyleRefinement, Styled as _,
StyleRefinement, Styled as _, WeakEntity, Window, WeakEntity, Window, div, prelude::FluentBuilder as _, px,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
resizable::{resize_handle, PANEL_MIN_SIZE},
StyledExt, StyledExt,
resizable::{PANEL_MIN_SIZE, resize_handle},
}; };
use super::{DockArea, DockItem, PanelView, TabPanel}; use super::{DockArea, DockItem, PanelView, TabPanel};
@ -91,6 +91,7 @@ impl Dock {
}); });
let panel = DockItem::Tabs { let panel = DockItem::Tabs {
size: None,
items: Vec::new(), items: Vec::new(),
active_ix: 0, active_ix: 0,
view: panel.clone(), view: panel.clone(),

View file

@ -78,20 +78,31 @@ pub enum DockItem {
/// Split layout /// Split layout
Split { Split {
axis: Axis, axis: Axis,
/// Self size, only used for build split panels
size: Option<Pixels>,
items: Vec<DockItem>, items: Vec<DockItem>,
/// Items sizes
sizes: Vec<Option<Pixels>>, sizes: Vec<Option<Pixels>>,
view: Entity<StackPanel>, view: Entity<StackPanel>,
}, },
/// Tab layout /// Tab layout
Tabs { Tabs {
/// Self size, only used for build split panels
size: Option<Pixels>,
items: Vec<Arc<dyn PanelView>>, items: Vec<Arc<dyn PanelView>>,
active_ix: usize, active_ix: usize,
view: Entity<TabPanel>, view: Entity<TabPanel>,
}, },
/// Panel layout /// Panel layout
Panel { view: Arc<dyn PanelView> }, Panel {
/// Self size, only used for build split panels
size: Option<Pixels>,
view: Arc<dyn PanelView>,
},
/// Tiles layout /// Tiles layout
Tiles { Tiles {
/// Self size, only used for build split panels
size: Option<Pixels>,
items: Vec<TileItem>, items: Vec<TileItem>,
view: Entity<Tiles>, view: Entity<Tiles>,
}, },
@ -122,7 +133,45 @@ impl std::fmt::Debug for DockItem {
} }
impl DockItem { impl DockItem {
/// Create DockItem with split layout, each item of panel have equal size. /// Get the size of the DockItem.
fn get_size(&self) -> Option<Pixels> {
match self {
Self::Split { size, .. } => *size,
Self::Tabs { size, .. } => *size,
Self::Panel { size, .. } => *size,
Self::Tiles { size, .. } => *size,
}
}
/// Set size for the DockItem.
pub fn size(mut self, new_size: impl Into<Pixels>) -> Self {
let new_size: Option<Pixels> = Some(new_size.into());
match self {
Self::Split { ref mut size, .. } => *size = new_size,
Self::Tabs { ref mut size, .. } => *size = new_size,
Self::Tiles { ref mut size, .. } => *size = new_size,
Self::Panel { ref mut size, .. } => *size = new_size,
}
self
}
/// Set active index for the DockItem, only valid for [`DockItem::Tabs`].
pub fn active_index(mut self, new_active_ix: usize) -> Self {
debug_assert!(
matches!(self, Self::Tabs { .. }),
"active_ix can only be set for DockItem::Tabs"
);
if let Self::Tabs {
ref mut active_ix, ..
} = self
{
*active_ix = new_active_ix;
}
self
}
/// Create DockItem::Split with given split layout.
pub fn split( pub fn split(
axis: Axis, axis: Axis,
items: Vec<DockItem>, items: Vec<DockItem>,
@ -130,10 +179,30 @@ impl DockItem {
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
) -> Self { ) -> Self {
let sizes = vec![None; items.len()]; let sizes = items.iter().map(|item| item.get_size()).collect();
Self::split_with_sizes(axis, items, sizes, dock_area, window, cx) Self::split_with_sizes(axis, items, sizes, dock_area, window, cx)
} }
/// Create DockItem with vertical split layout.
pub fn v_split(
items: Vec<DockItem>,
dock_area: &WeakEntity<DockArea>,
window: &mut Window,
cx: &mut App,
) -> Self {
Self::split(Axis::Vertical, items, dock_area, window, cx)
}
/// Create DockItem with horizontal split layout.
pub fn h_split(
items: Vec<DockItem>,
dock_area: &WeakEntity<DockArea>,
window: &mut Window,
cx: &mut App,
) -> Self {
Self::split(Axis::Horizontal, items, dock_area, window, cx)
}
/// Create DockItem with split layout, each item of panel have specified size. /// Create DockItem with split layout, each item of panel have specified size.
/// ///
/// Please note that the `items` and `sizes` must have the same length. /// Please note that the `items` and `sizes` must have the same length.
@ -176,6 +245,7 @@ impl DockItem {
Self::Split { Self::Split {
axis, axis,
size: None,
items, items,
sizes, sizes,
view: stack_panel, view: stack_panel,
@ -184,7 +254,10 @@ impl DockItem {
/// Create DockItem with panel layout /// Create DockItem with panel layout
pub fn panel(panel: Arc<dyn PanelView>) -> Self { pub fn panel(panel: Arc<dyn PanelView>) -> Self {
Self::Panel { view: panel } Self::Panel {
size: None,
view: panel,
}
} }
/// Create DockItem with tiles layout /// Create DockItem with tiles layout
@ -209,7 +282,7 @@ impl DockItem {
TileItem::new(Arc::new(view), meta.bounds).z_index(meta.z_index); TileItem::new(Arc::new(view), meta.bounds).z_index(meta.z_index);
tiles.add_item(tile_item, dock_area, window, cx); tiles.add_item(tile_item, dock_area, window, cx);
} }
DockItem::Panel { view } => { DockItem::Panel { view, .. } => {
let meta: TileMeta = metas[ix].into(); let meta: TileMeta = metas[ix].into();
let tile_item = let tile_item =
TileItem::new(view.clone(), meta.bounds).z_index(meta.z_index); TileItem::new(view.clone(), meta.bounds).z_index(meta.z_index);
@ -235,6 +308,7 @@ impl DockItem {
}); });
Self::Tiles { Self::Tiles {
size: None,
items: tile_panel.read(cx).panels.clone(), items: tile_panel.read(cx).panels.clone(),
view: tile_panel, view: tile_panel,
} }
@ -245,7 +319,6 @@ impl DockItem {
/// The `active_ix` is the index of the active tab, if `None` the first tab is active. /// The `active_ix` is the index of the active tab, if `None` the first tab is active.
pub fn tabs( pub fn tabs(
items: Vec<Arc<dyn PanelView>>, items: Vec<Arc<dyn PanelView>>,
active_ix: Option<usize>,
dock_area: &WeakEntity<DockArea>, dock_area: &WeakEntity<DockArea>,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
@ -254,7 +327,7 @@ impl DockItem {
for item in items.into_iter() { for item in items.into_iter() {
new_items.push(item) new_items.push(item)
} }
Self::new_tabs(new_items, active_ix, dock_area, window, cx) Self::new_tabs(new_items, None, dock_area, window, cx)
} }
pub fn tab<P: Panel>( pub fn tab<P: Panel>(
@ -284,6 +357,7 @@ impl DockItem {
}); });
Self::Tabs { Self::Tabs {
size: None,
items, items,
active_ix, active_ix,
view: tab_panel, view: tab_panel,
@ -307,7 +381,7 @@ impl DockItem {
items.iter().find_map(|item| item.find_panel(panel.clone())) items.iter().find_map(|item| item.find_panel(panel.clone()))
} }
Self::Tabs { items, .. } => items.iter().find(|item| *item == &panel).cloned(), Self::Tabs { items, .. } => items.iter().find(|item| *item == &panel).cloned(),
Self::Panel { view } => Some(view.clone()), Self::Panel { view, .. } => Some(view.clone()),
Self::Tiles { items, .. } => items.iter().find_map(|item| { Self::Tiles { items, .. } => items.iter().find_map(|item| {
if &item.panel == &panel { if &item.panel == &panel {
Some(item.panel.clone()) Some(item.panel.clone())
@ -346,13 +420,13 @@ impl DockItem {
} }
// Unable to find tabs, create new tabs // Unable to find tabs, create new tabs
let new_item = Self::tabs(vec![panel.clone()], None, dock_area, window, cx); let new_item = Self::tabs(vec![panel.clone()], dock_area, window, cx);
items.push(new_item.clone()); items.push(new_item.clone());
view.update(cx, |stack_panel, cx| { view.update(cx, |stack_panel, cx| {
stack_panel.add_panel(new_item.view(), None, dock_area.clone(), window, cx); stack_panel.add_panel(new_item.view(), None, dock_area.clone(), window, cx);
}); });
} }
Self::Tiles { view, items } => { Self::Tiles { view, items, .. } => {
let tile_item = TileItem::new( let tile_item = TileItem::new(
Arc::new(cx.new(|cx| { Arc::new(cx.new(|cx| {
let mut tab_panel = TabPanel::new(None, dock_area.clone(), window, cx); let mut tab_panel = TabPanel::new(None, dock_area.clone(), window, cx);
@ -411,7 +485,7 @@ impl DockItem {
} }
} }
DockItem::Tiles { .. } => {} DockItem::Tiles { .. } => {}
DockItem::Panel { view } => view.set_active(!collapsed, window, cx), DockItem::Panel { view, .. } => view.set_active(!collapsed, window, cx),
} }
} }
@ -447,6 +521,7 @@ impl DockArea {
let dock_item = DockItem::Split { let dock_item = DockItem::Split {
axis: Axis::Horizontal, axis: Axis::Horizontal,
size: None,
items: vec![], items: vec![],
sizes: vec![], sizes: vec![],
view: stack_panel.clone(), view: stack_panel.clone(),
@ -726,7 +801,7 @@ impl DockArea {
dock.update(cx, |dock, cx| dock.add_panel(panel, window, cx)) dock.update(cx, |dock, cx| dock.add_panel(panel, window, cx))
} else { } else {
self.set_left_dock( self.set_left_dock(
DockItem::tabs(vec![panel], None, &weak_self, window, cx), DockItem::tabs(vec![panel], &weak_self, window, cx),
None, None,
true, true,
window, window,
@ -739,7 +814,7 @@ impl DockArea {
dock.update(cx, |dock, cx| dock.add_panel(panel, window, cx)) dock.update(cx, |dock, cx| dock.add_panel(panel, window, cx))
} else { } else {
self.set_bottom_dock( self.set_bottom_dock(
DockItem::tabs(vec![panel], None, &weak_self, window, cx), DockItem::tabs(vec![panel], &weak_self, window, cx),
None, None,
true, true,
window, window,
@ -752,7 +827,7 @@ impl DockArea {
dock.update(cx, |dock, cx| dock.add_panel(panel, window, cx)) dock.update(cx, |dock, cx| dock.add_panel(panel, window, cx))
} else { } else {
self.set_right_dock( self.set_right_dock(
DockItem::tabs(vec![panel], None, &weak_self, window, cx), DockItem::tabs(vec![panel], &weak_self, window, cx),
None, None,
true, true,
window, window,

View file

@ -1,4 +1,4 @@
use gpui::{point, px, size, App, AppContext, Axis, Bounds, Entity, Pixels, WeakEntity, Window}; use gpui::{App, AppContext, Axis, Bounds, Entity, Pixels, WeakEntity, Window, point, px, size};
use itertools::Itertools as _; use itertools::Itertools as _;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -219,7 +219,7 @@ impl PanelState {
}) })
.collect_vec(); .collect_vec();
DockItem::tabs(items, Some(active_index), &dock_area, window, cx) DockItem::tabs(items, &dock_area, window, cx).active_index(active_index)
} }
PanelInfo::Panel(_) => { PanelInfo::Panel(_) => {
let view = PanelRegistry::build_panel( let view = PanelRegistry::build_panel(
@ -230,7 +230,7 @@ impl PanelState {
window, window,
cx, cx,
); );
DockItem::tabs(vec![view.into()], None, &dock_area, window, cx) DockItem::tabs(vec![view.into()], &dock_area, window, cx)
} }
PanelInfo::Tiles { metas } => DockItem::tiles(items, metas, &dock_area, window, cx), PanelInfo::Tiles { metas } => DockItem::tiles(items, metas, &dock_area, window, cx),
} }

View file

@ -57,7 +57,7 @@ impl AppMenuBar {
} else { } else {
selected_ix.saturating_sub(1) selected_ix.saturating_sub(1)
}; };
self.set_selected_ix(Some(new_ix), window, cx); self.set_selected_index(Some(new_ix), window, cx);
} }
fn on_move_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) { fn on_move_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
@ -70,14 +70,14 @@ impl AppMenuBar {
} else { } else {
selected_ix + 1 selected_ix + 1
}; };
self.set_selected_ix(Some(new_ix), window, cx); self.set_selected_index(Some(new_ix), window, cx);
} }
fn on_cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) { fn on_cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
self.set_selected_ix(None, window, cx); self.set_selected_index(None, window, cx);
} }
fn set_selected_ix(&mut self, ix: Option<usize>, _: &mut Window, cx: &mut Context<Self>) { fn set_selected_index(&mut self, ix: Option<usize>, _: &mut Window, cx: &mut Context<Self>) {
self.selected_ix = ix; self.selected_ix = ix;
cx.notify(); cx.notify();
} }
@ -189,7 +189,7 @@ impl AppMenu {
_ = self.menu_bar.update(cx, |state, cx| { _ = self.menu_bar.update(cx, |state, cx| {
let new_ix = if is_selected { None } else { Some(self.ix) }; let new_ix = if is_selected { None } else { Some(self.ix) };
state.set_selected_ix(new_ix, window, cx); state.set_selected_index(new_ix, window, cx);
}); });
} }
@ -204,7 +204,7 @@ impl AppMenu {
} }
_ = self.menu_bar.update(cx, |state, cx| { _ = self.menu_bar.update(cx, |state, cx| {
state.set_selected_ix(Some(self.ix), window, cx); state.set_selected_index(Some(self.ix), window, cx);
}); });
} }
} }