From 47688a8af2450c12aa52935c63cf025043a7d623 Mon Sep 17 00:00:00 2001 From: xda <150917089+xda2023@users.noreply.github.com> Date: Thu, 14 Nov 2024 12:00:23 +0800 Subject: [PATCH] dock: Generalize Dock's panel to accommodate DockItem (#412) ![2024-11-13 23 01 30](https://github.com/user-attachments/assets/dbb92e6b-ac5d-47c7-9a78-4029b414cdfc) --- crates/app/src/story_workspace.rs | 58 ++++++++++++++++---- crates/ui/src/dock/dock.rs | 88 +++++++++++++++++++++---------- crates/ui/src/dock/mod.rs | 77 +++++++++++++++++---------- crates/ui/src/dock/state.rs | 38 +++++-------- crates/ui/src/dock/tab_panel.rs | 15 ++++-- 5 files changed, 182 insertions(+), 94 deletions(-) diff --git a/crates/app/src/story_workspace.rs b/crates/app/src/story_workspace.rs index 755a0836..21a1a521 100644 --- a/crates/app/src/story_workspace.rs +++ b/crates/app/src/story_workspace.rs @@ -11,7 +11,7 @@ use story::{ use ui::{ button::{Button, ButtonStyled as _}, color_picker::{ColorPicker, ColorPickerEvent}, - dock::{DockArea, DockAreaState, DockEvent, DockItem, PanelView}, + dock::{DockArea, DockAreaState, DockEvent, DockItem}, h_flex, popup_menu::PopupMenuExt, theme::{ActiveTheme, Theme}, @@ -208,16 +208,56 @@ impl StoryWorkspace { fn reset_default_layout(dock_area: WeakView, cx: &mut WindowContext) { let dock_item = Self::init_default_layout(&dock_area, cx); - let left_panels: Vec> = - vec![Arc::new(StoryContainer::panel::(cx))]; - let bottom_panels: Vec> = vec![ - Arc::new(StoryContainer::panel::(cx)), - Arc::new(StoryContainer::panel::(cx)), - ]; + let left_panels = DockItem::split_with_sizes( + Axis::Vertical, + vec![ + DockItem::tabs( + vec![Arc::new(StoryContainer::panel::(cx))], + None, + &dock_area, + cx, + ), + DockItem::tabs( + vec![ + Arc::new(StoryContainer::panel::(cx)), + Arc::new(StoryContainer::panel::(cx)), + ], + None, + &dock_area, + cx, + ), + ], + vec![None, Some(px(360.))], + &dock_area, + cx, + ); - let right_panels: Vec> = - vec![Arc::new(StoryContainer::panel::(cx))]; + let bottom_panels = DockItem::tabs( + vec![ + Arc::new(StoryContainer::panel::(cx)), + Arc::new(StoryContainer::panel::(cx)), + ], + None, + &dock_area, + cx, + ); + + let right_panels = DockItem::split_with_sizes( + Axis::Vertical, + vec![DockItem::tabs( + vec![ + Arc::new(StoryContainer::panel::(cx)), + Arc::new(StoryContainer::panel::(cx)), + ], + None, + &dock_area, + cx, + )], + vec![None], + &dock_area, + cx, + ); _ = dock_area.update(cx, |view, cx| { view.set_version(MAIN_DOCK_AREA.version, cx); diff --git a/crates/ui/src/dock/dock.rs b/crates/ui/src/dock/dock.rs index 2232ee78..5eb34cc0 100644 --- a/crates/ui/src/dock/dock.rs +++ b/crates/ui/src/dock/dock.rs @@ -1,10 +1,8 @@ //! Dock is a fixed container that places at left, bottom, right of the Windows. -use std::sync::Arc; - use gpui::{ - div, prelude::FluentBuilder as _, px, Axis, Element, InteractiveElement as _, IntoElement, - MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Render, + div, prelude::FluentBuilder as _, px, Axis, Element, EntityId, InteractiveElement as _, + IntoElement, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Render, StatefulInteractiveElement, Style, Styled as _, View, ViewContext, VisualContext as _, WeakView, WindowContext, }; @@ -16,7 +14,7 @@ use crate::{ AxisExt as _, StyledExt, }; -use super::{DockArea, PanelView, TabPanel}; +use super::{DockArea, DockItem, TabPanel}; #[derive(Clone, Render)] struct ResizePanel; @@ -58,7 +56,7 @@ impl DockPlacement { pub struct Dock { pub(super) placement: DockPlacement, dock_area: WeakView, - pub(crate) panel: View, + pub(crate) panel: DockItem, /// The size is means the width or height of the Dock, if the placement is left or right, the size is width, otherwise the size is height. pub(super) size: Pixels, pub(super) open: bool, @@ -77,7 +75,13 @@ impl Dock { tab }); - Self::subscribe_panel_events(dock_area.clone(), panel.clone(), cx); + let panel = DockItem::Tabs { + items: Vec::new(), + active_ix: 0, + view: panel.clone(), + }; + + Self::subscribe_panel_events(dock_area.clone(), &panel, cx); Self { placement, @@ -105,16 +109,21 @@ impl Dock { dock_area: WeakView, placement: DockPlacement, size: Pixels, - panel: View, + panel: DockItem, open: bool, cx: &mut WindowContext, ) -> Self { - Self::subscribe_panel_events(dock_area.clone(), panel.clone(), cx); + Self::subscribe_panel_events(dock_area.clone(), &panel, cx); if !open { - panel.update(cx, |panel, cx| { - panel.set_collapsed(true, cx); - }); + match panel.clone() { + DockItem::Tabs { view, .. } => { + view.update(cx, |panel, cx| { + panel.set_collapsed(true, cx); + }); + } + _ => {} + } } Self { @@ -129,24 +138,38 @@ impl Dock { fn subscribe_panel_events( dock_area: WeakView, - panel: View, + panel: &DockItem, cx: &mut WindowContext, ) { - // Subscribe the panel to the dock area. - cx.defer({ - move |cx| { - _ = dock_area.update(cx, |this, cx| { - this.subscribe_panel(&panel, cx); + match panel { + DockItem::Tabs { view, .. } => { + cx.defer({ + let view = view.clone(); + move |cx| { + _ = dock_area.update(cx, |this, cx| { + this.subscribe_panel(&view, cx); + }); + } }); } - }); + DockItem::Split { items, view, .. } => { + for item in items { + Self::subscribe_panel_events(dock_area.clone(), item, cx); + } + cx.defer({ + let view = view.clone(); + move |cx| { + _ = dock_area.update(cx, |this, cx| { + this.subscribe_panel(&view, cx); + }); + } + }); + } + } } - pub fn set_panels(&mut self, panels: Vec>, cx: &mut ViewContext) { - self.panel.update(cx, |tab_panel, _| { - tab_panel.panels = panels; - tab_panel.active_ix = 0; - }); + pub fn set_panel(&mut self, panel: DockItem, cx: &mut ViewContext) { + self.panel = panel; cx.notify(); } @@ -174,10 +197,9 @@ impl Dock { /// Set the open state of the Dock. pub fn set_open(&mut self, open: bool, cx: &mut ViewContext) { self.open = open; - cx.defer(move |this, cx| { - this.panel.update(cx, |panel, cx| { - panel.set_collapsed(!open, cx); - }); + let item = self.panel.clone(); + cx.defer(move |_, cx| { + item.set_collapsed(!open, cx); }); cx.notify(); } @@ -257,6 +279,11 @@ impl Dock { fn done_resizing(&mut self, _: &mut ViewContext) { self.is_resizing = false; } + + /// This method allows the Dock to determine if its panel contains the entity_id of the TabPanel + pub(crate) fn panel_contains_entity_id(&self, entity_id: EntityId) -> bool { + self.panel.contains_entity_id(entity_id) + } } impl Render for Dock { @@ -276,7 +303,10 @@ impl Render for Dock { .when(!self.open && self.placement.is_bottom(), |this| { this.h(px(30.)) }) - .child(self.panel.clone()) + .map(|this| match &self.panel { + DockItem::Split { view, .. } => this.child(view.clone()), + DockItem::Tabs { view, .. } => this.child(view.clone()), + }) .child(self.render_resize_handle(cx)) .child(DockElement { view: cx.view().clone(), diff --git a/crates/ui/src/dock/mod.rs b/crates/ui/src/dock/mod.rs index b13279e9..557e9f5d 100644 --- a/crates/ui/src/dock/mod.rs +++ b/crates/ui/src/dock/mod.rs @@ -5,12 +5,13 @@ mod stack_panel; mod state; mod tab_panel; -use anyhow::{bail, Result}; +use anyhow::Result; pub use dock::*; use gpui::{ actions, canvas, div, prelude::FluentBuilder, AnyElement, AnyView, AppContext, Axis, Bounds, - EventEmitter, InteractiveElement as _, IntoElement, ParentElement as _, Pixels, Render, - SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView, WindowContext, + Entity, EntityId, EventEmitter, InteractiveElement as _, IntoElement, ParentElement as _, + Pixels, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView, + WindowContext, }; pub use panel::*; pub use stack_panel::*; @@ -193,6 +194,32 @@ impl DockItem { Self::Tabs { items, .. } => items.iter().find(|item| *item == &panel).cloned(), } } + + pub fn set_collapsed(&self, collapsed: bool, cx: &mut WindowContext) { + match self { + DockItem::Tabs { view, .. } => { + view.update(cx, |tab_panel, cx| { + tab_panel.set_collapsed(collapsed, cx); + }); + } + DockItem::Split { items, .. } => { + // For each child item, set collapsed state + for item in items { + item.set_collapsed(collapsed, cx); + } + } + } + } + + /// Recursively checks if the DockItem or any of its children contain the entity_id of the TabPanel + pub fn contains_entity_id(&self, entity_id: EntityId) -> bool { + match self { + DockItem::Tabs { view, .. } => view.entity_id() == entity_id, + DockItem::Split { items, .. } => { + items.iter().any(|item| item.contains_entity_id(entity_id)) + } + } + } } impl DockArea { @@ -245,53 +272,56 @@ impl DockArea { pub fn set_left_dock( &mut self, - panels: Vec>, + panel: DockItem, size: Option, cx: &mut ViewContext, ) { + self.subscribe_item(&panel, cx); let weak_self = cx.view().downgrade(); self.left_dock = Some(cx.new_view(|cx| { let mut dock = Dock::left(weak_self.clone(), cx); if let Some(size) = size { dock.set_size(size, cx); } - dock.set_panels(panels, cx); + dock.set_panel(panel, cx); dock - })) + })); } pub fn set_bottom_dock( &mut self, - panels: Vec>, + panel: DockItem, size: Option, cx: &mut ViewContext, ) { + self.subscribe_item(&panel, cx); let weak_self = cx.view().downgrade(); self.bottom_dock = Some(cx.new_view(|cx| { let mut dock = Dock::bottom(weak_self.clone(), cx); if let Some(size) = size { dock.set_size(size, cx); } - dock.set_panels(panels, cx); + dock.set_panel(panel, cx); dock - })) + })); } pub fn set_right_dock( &mut self, - panels: Vec>, + panel: DockItem, size: Option, cx: &mut ViewContext, ) { + self.subscribe_item(&panel, cx); let weak_self = cx.view().downgrade(); self.right_dock = Some(cx.new_view(|cx| { let mut dock = Dock::right(weak_self.clone(), cx); if let Some(size) = size { dock.set_size(size, cx); } - dock.set_panels(panels, cx); + dock.set_panel(panel, cx); dock - })) + })); } pub fn is_dock_open(&self, placement: DockPlacement, cx: &AppContext) -> bool { @@ -334,25 +364,16 @@ impl DockArea { self.version = state.version; let weak_self = cx.view().downgrade(); - if let Some(left_dock) = state.left_dock { - match left_dock.to_dock(weak_self.clone(), cx) { - Ok(dock) => self.left_dock = Some(dock), - Err(err) => bail!("failed to load left dock: {}", err), - } + if let Some(left_dock_state) = state.left_dock { + self.left_dock = Some(left_dock_state.to_dock(weak_self.clone(), cx)); } - if let Some(right_dock) = state.right_dock { - match right_dock.to_dock(weak_self.clone(), cx) { - Ok(dock) => self.right_dock = Some(dock), - Err(err) => bail!("failed to load right dock: {}", err), - } + if let Some(right_dock_state) = state.right_dock { + self.right_dock = Some(right_dock_state.to_dock(weak_self.clone(), cx)); } - if let Some(bottom_dock) = state.bottom_dock { - match bottom_dock.to_dock(weak_self.clone(), cx) { - Ok(dock) => self.bottom_dock = Some(dock), - Err(err) => bail!("failed to load bottom dock: {}", err), - } + if let Some(bottom_dock_state) = state.bottom_dock { + self.bottom_dock = Some(bottom_dock_state.to_dock(weak_self.clone(), cx)); } self.items = state.center.to_item(weak_self, cx); @@ -405,7 +426,7 @@ impl DockArea { })); } DockItem::Tabs { .. } => { - // We subscribe the tab panel event is in StackPanel insert_panel + // We subscribe to the tab panel event in StackPanel's insert_panel } } } diff --git a/crates/ui/src/dock/state.rs b/crates/ui/src/dock/state.rs index 264a5318..f69c8add 100644 --- a/crates/ui/src/dock/state.rs +++ b/crates/ui/src/dock/state.rs @@ -1,11 +1,9 @@ -use anyhow::{bail, Result}; use gpui::{AppContext, Axis, Pixels, View, VisualContext as _, WeakView, WindowContext}; use itertools::Itertools as _; use serde::{Deserialize, Serialize}; use super::{ invalid_panel::InvalidPanel, Dock, DockArea, DockItem, DockPlacement, Panel, PanelRegistry, - PanelView, TabPanel, }; /// Used to serialize and deserialize the DockArea @@ -39,33 +37,23 @@ impl DockState { placement: dock.placement, size: dock.size, open: dock.open, - panel: dock.panel.dump(cx), + panel: dock.panel.view().dump(cx), } } /// Convert the DockState to Dock - pub fn to_dock( - &self, - dock_area: WeakView, - cx: &mut WindowContext, - ) -> Result> { - let view = self.panel.to_item(dock_area.clone(), cx).view(); - if let Ok(tab_panel) = view.view().downcast::() { - let dock = cx.new_view(|cx| { - Dock::from_state( - dock_area.clone(), - self.placement, - self.size, - tab_panel, - self.open, - cx, - ) - }); - - Ok(dock) - } else { - bail!("Invalid panel, failed to downcast to TabPanel") - } + pub fn to_dock(&self, dock_area: WeakView, cx: &mut WindowContext) -> View { + let item = self.panel.to_item(dock_area.clone(), cx); + cx.new_view(|cx| { + Dock::from_state( + dock_area.clone(), + self.placement, + self.size, + item, + self.open, + cx, + ) + }) } } diff --git a/crates/ui/src/dock/tab_panel.rs b/crates/ui/src/dock/tab_panel.rs index 1c5082c1..b6f14533 100644 --- a/crates/ui/src/dock/tab_panel.rs +++ b/crates/ui/src/dock/tab_panel.rs @@ -351,19 +351,28 @@ impl TabPanel { let mut self_is_bottom_dock = false; if let Some(left_view) = &dock_area.read(cx).left_dock { has_left_dock = true; - if left_view.read(cx).panel.entity_id() == cx.view().entity_id() { + if left_view + .read(cx) + .panel_contains_entity_id(cx.view().entity_id()) + { self_is_left_dock = true; } } if let Some(right_view) = &dock_area.read(cx).right_dock { has_right_dock = true; - if right_view.read(cx).panel.entity_id() == cx.view().entity_id() { + if right_view + .read(cx) + .panel_contains_entity_id(cx.view().entity_id()) + { self_is_right_dock = true; } } if let Some(bottom_view) = &dock_area.read(cx).bottom_dock { has_bottom_dock = true; - if bottom_view.read(cx).panel.entity_id() == cx.view().entity_id() { + if bottom_view + .read(cx) + .panel_contains_entity_id(cx.view().entity_id()) + { self_is_bottom_dock = true; } }