From 7edbdd01f4190f168aa45a68e09ea76534f6e480 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Thu, 14 Nov 2024 12:05:58 +0800 Subject: [PATCH] dock: Remove the inside toggle dock button in Dock to reduce complex and add dock::ToggleButtons. (#415) Draft https://github.com/longbridgeapp/gpui-component/pull/414 to keep old code. image --- crates/app/src/story_workspace.rs | 8 +- crates/ui/src/dock/dock.rs | 15 +- crates/ui/src/dock/mod.rs | 30 ++-- crates/ui/src/dock/stack_panel.rs | 63 +------- crates/ui/src/dock/tab_panel.rs | 210 +-------------------------- crates/ui/src/dock/toggle_buttons.rs | 112 ++++++++++++++ 6 files changed, 150 insertions(+), 288 deletions(-) create mode 100644 crates/ui/src/dock/toggle_buttons.rs diff --git a/crates/app/src/story_workspace.rs b/crates/app/src/story_workspace.rs index 21a1a521..7f5e3da2 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}, + dock::{DockArea, DockAreaState, DockEvent, DockItem, ToggleButtons}, h_flex, popup_menu::PopupMenuExt, theme::{ActiveTheme, Theme}, @@ -386,6 +386,12 @@ impl Render for StoryWorkspace { .justify_end() .px_2() .gap_2() + .child( + ToggleButtons::new(self.dock_area.downgrade()) + .small() + .outline() + .mr_4(), + ) .child(self.theme_color_picker.clone()) .child( Button::new("theme-mode") diff --git a/crates/ui/src/dock/dock.rs b/crates/ui/src/dock/dock.rs index 5eb34cc0..b3cd2a42 100644 --- a/crates/ui/src/dock/dock.rs +++ b/crates/ui/src/dock/dock.rs @@ -1,8 +1,8 @@ //! Dock is a fixed container that places at left, bottom, right of the Windows. use gpui::{ - div, prelude::FluentBuilder as _, px, Axis, Element, EntityId, InteractiveElement as _, - IntoElement, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Render, + div, prelude::FluentBuilder as _, px, Axis, Element, InteractiveElement as _, IntoElement, + MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Render, StatefulInteractiveElement, Style, Styled as _, View, ViewContext, VisualContext as _, WeakView, WindowContext, }; @@ -279,16 +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 { fn render(&mut self, cx: &mut ViewContext) -> impl gpui::IntoElement { - if !self.open && !self.placement.is_bottom() { + if !self.open { return div(); } @@ -299,10 +294,6 @@ impl Render for Dock { DockPlacement::Left | DockPlacement::Right => this.h_flex().h_full().w(self.size), DockPlacement::Bottom => this.w_full().h(self.size), }) - // Bottom Dock should keep the title bar, then user can click the Toggle button - .when(!self.open && self.placement.is_bottom(), |this| { - this.h(px(30.)) - }) .map(|this| match &self.panel { DockItem::Split { view, .. } => this.child(view.clone()), DockItem::Tabs { view, .. } => this.child(view.clone()), diff --git a/crates/ui/src/dock/mod.rs b/crates/ui/src/dock/mod.rs index 557e9f5d..14b59de5 100644 --- a/crates/ui/src/dock/mod.rs +++ b/crates/ui/src/dock/mod.rs @@ -4,20 +4,22 @@ mod panel; mod stack_panel; mod state; mod tab_panel; +mod toggle_buttons; use anyhow::Result; pub use dock::*; use gpui::{ actions, canvas, div, prelude::FluentBuilder, AnyElement, AnyView, AppContext, Axis, Bounds, - Entity, EntityId, EventEmitter, InteractiveElement as _, IntoElement, ParentElement as _, - Pixels, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView, - WindowContext, + EventEmitter, InteractiveElement as _, IntoElement, ParentElement as _, Pixels, Render, + SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView, WindowContext, }; +use std::sync::Arc; + pub use panel::*; pub use stack_panel::*; pub use state::*; -use std::sync::Arc; pub use tab_panel::*; +pub use toggle_buttons::*; pub fn init(cx: &mut AppContext) { cx.set_global(PanelRegistry::new()); @@ -210,16 +212,6 @@ impl DockItem { } } } - - /// 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 { @@ -324,6 +316,16 @@ impl DockArea { })); } + /// Determine if the dock area has a dock at the given placement. + pub fn has_dock(&self, placement: DockPlacement) -> bool { + match placement { + DockPlacement::Left => self.left_dock.is_some(), + DockPlacement::Bottom => self.bottom_dock.is_some(), + DockPlacement::Right => self.right_dock.is_some(), + } + } + + /// Determine if the dock at the given placement is open. pub fn is_dock_open(&self, placement: DockPlacement, cx: &AppContext) -> bool { match placement { DockPlacement::Left => self diff --git a/crates/ui/src/dock/stack_panel.rs b/crates/ui/src/dock/stack_panel.rs index 4ff76fe0..c8889141 100644 --- a/crates/ui/src/dock/stack_panel.rs +++ b/crates/ui/src/dock/stack_panel.rs @@ -8,12 +8,12 @@ use crate::{ ResizablePanelGroup, }, theme::ActiveTheme, - AxisExt, Placement, + Placement, }; use super::{DockArea, DockItemState, Panel, PanelEvent, PanelView, TabPanel}; use gpui::{ - prelude::FluentBuilder as _, AppContext, Axis, DismissEvent, Entity, EventEmitter, FocusHandle, + prelude::FluentBuilder as _, AppContext, Axis, DismissEvent, EventEmitter, FocusHandle, FocusableView, IntoElement, ParentElement, Pixels, Render, Styled, Subscription, View, ViewContext, VisualContext, WeakView, }; @@ -180,7 +180,9 @@ impl StackPanel { move |cx| { // If the panel is a TabPanel, set its parent to this. if let Ok(tab_panel) = panel.view().downcast::() { - tab_panel.update(cx, |tab_panel, _| tab_panel.set_parent(view.downgrade())); + tab_panel.update(cx, |tab_panel, cx| { + tab_panel.set_parent(view.downgrade(), cx) + }); } else if let Ok(stack_panel) = panel.view().downcast::() { stack_panel.update(cx, |stack_panel, _| { stack_panel.parent = Some(view.downgrade()) @@ -283,61 +285,6 @@ impl StackPanel { .update(cx, |view, cx| view.set_axis(axis, cx)); cx.notify(); } - - /// Check if the given panel is at the first top left in the stack. - pub(super) fn is_top_left_panel( - &self, - panel: View, - check_parent: bool, - cx: &AppContext, - ) -> bool { - let first_panel = self.panels.first(); - - if check_parent { - if let Some(parent) = self.parent.as_ref().and_then(|parent| parent.upgrade()) { - return parent.read(cx).is_top_left_panel(panel, true, cx); - } - } - - if let Some(view) = first_panel { - if let Ok(view) = view.view().downcast::() { - return view.entity_id() == panel.entity_id(); - } else if let Ok(view) = view.view().downcast::() { - return view.read(cx).is_top_left_panel(panel, false, cx); - } - } - false - } - - /// Check if the given panel is at the first top right in the stack. - pub(super) fn is_top_right_panel( - &self, - panel: View, - check_parent: bool, - cx: &AppContext, - ) -> bool { - let first_panel = if self.axis.is_vertical() { - self.panels.first() - } else { - self.panels.last() - }; - - if check_parent { - if let Some(parent) = self.parent.as_ref().and_then(|parent| parent.upgrade()) { - return parent.read(cx).is_top_right_panel(panel, true, cx); - } - } - - if let Some(view) = first_panel { - if let Ok(view) = view.view().downcast::() { - return view.entity_id() == panel.entity_id(); - } else if let Ok(view) = view.view().downcast::() { - return view.read(cx).is_top_right_panel(panel, false, cx); - } - } - - false - } } impl FocusableView for StackPanel { diff --git a/crates/ui/src/dock/tab_panel.rs b/crates/ui/src/dock/tab_panel.rs index b6f14533..745e7865 100644 --- a/crates/ui/src/dock/tab_panel.rs +++ b/crates/ui/src/dock/tab_panel.rs @@ -2,10 +2,9 @@ use std::sync::Arc; use gpui::{ div, prelude::FluentBuilder, px, rems, AnchorCorner, AppContext, DefiniteLength, DismissEvent, - DragMoveEvent, Empty, Entity, EventEmitter, FocusHandle, FocusableView, - InteractiveElement as _, IntoElement, ParentElement, Pixels, Render, ScrollHandle, - SharedString, StatefulInteractiveElement, Styled, View, ViewContext, VisualContext as _, - WeakView, WindowContext, + DragMoveEvent, Empty, EventEmitter, FocusHandle, FocusableView, InteractiveElement as _, + IntoElement, ParentElement, Pixels, Render, ScrollHandle, StatefulInteractiveElement, Styled, + View, ViewContext, VisualContext as _, WeakView, WindowContext, }; use rust_i18n::t; @@ -20,8 +19,7 @@ use crate::{ }; use super::{ - ClosePanel, DockArea, DockItemState, DockPlacement, Panel, PanelEvent, PanelView, StackPanel, - ToggleZoom, + ClosePanel, DockArea, DockItemState, Panel, PanelEvent, PanelView, StackPanel, ToggleZoom, }; #[derive(Clone)] @@ -154,7 +152,7 @@ impl TabPanel { } } - pub(super) fn set_parent(&mut self, view: WeakView) { + pub(super) fn set_parent(&mut self, view: WeakView, _: &mut ViewContext) { self.stack_panel = Some(view); } @@ -332,167 +330,9 @@ impl TabPanel { ) } - fn render_dock_toggle_button( - &self, - placement: DockPlacement, - cx: &mut ViewContext, - ) -> Option { - let dock_area = self.dock_area.upgrade().expect("BUG: DockArea is missing"); - - if self.is_zoomed { - return None; - } - - let mut has_left_dock = false; - let mut has_right_dock = false; - let mut has_bottom_dock = false; - let mut self_is_left_dock = false; - let mut self_is_right_dock = false; - 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_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_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_contains_entity_id(cx.view().entity_id()) - { - self_is_bottom_dock = true; - } - } - - // Check the dock origin vs self.bounds.origin, if they are in the same line, then render the ToggleButton - match placement { - DockPlacement::Left => { - if !has_left_dock { - return None; - } - - if self_is_left_dock || self_is_right_dock || self_is_bottom_dock { - return None; - } - if let Some(parent) = self - .stack_panel - .as_ref() - .and_then(|parent| parent.upgrade()) - { - if !parent - .read(cx) - .is_top_left_panel(cx.view().clone(), true, cx) - { - return None; - } - } - } - DockPlacement::Right => { - if !has_right_dock { - return None; - } - - if self_is_left_dock || self_is_right_dock || self_is_bottom_dock { - return None; - } - - if let Some(parent) = self - .stack_panel - .as_ref() - .and_then(|parent| parent.upgrade()) - { - if !parent - .read(cx) - .is_top_right_panel(cx.view().clone(), true, cx) - { - return None; - } - } - } - DockPlacement::Bottom => { - if !has_bottom_dock { - return None; - } - if !self_is_bottom_dock { - return None; - } - } - } - - let is_left_dock_open = dock_area - .read(cx) - .is_dock_open(super::DockPlacement::Left, cx); - let is_right_dock_open = dock_area - .read(cx) - .is_dock_open(super::DockPlacement::Right, cx); - let is_bottom_dock_open = dock_area - .read(cx) - .is_dock_open(super::DockPlacement::Bottom, cx); - - let (icon, is_open) = match placement { - DockPlacement::Left => { - if is_left_dock_open { - (IconName::PanelLeft, true) - } else { - (IconName::PanelLeftOpen, false) - } - } - DockPlacement::Right => { - if is_right_dock_open { - (IconName::PanelRight, true) - } else { - (IconName::PanelRightOpen, false) - } - } - DockPlacement::Bottom => { - if is_bottom_dock_open { - (IconName::PanelBottom, true) - } else { - (IconName::PanelBottomOpen, false) - } - } - }; - - Some( - Button::new(SharedString::from(format!("toggle-dock:{:?}", placement))) - .icon(icon) - .xsmall() - .ghost() - .tooltip(match is_open { - true => t!("Dock.Collapse"), - false => t!("Dock.Expand"), - }) - .on_click(cx.listener({ - let dock_area = dock_area.clone(); - move |_, _, cx| { - dock_area.update(cx, |dock_area, cx| { - dock_area.toggle_dock(placement, cx); - }); - } - })), - ) - } - fn render_title_bar(&self, cx: &mut ViewContext) -> impl IntoElement { let view = cx.view().clone(); - let left_dock_button = self.render_dock_toggle_button(DockPlacement::Left, cx); - let bottom_dock_button = self.render_dock_toggle_button(DockPlacement::Bottom, cx); - let right_dock_button = self.render_dock_toggle_button(DockPlacement::Right, cx); - if self.panels.len() == 1 { let panel = self.panels.get(0).unwrap(); let title_style = panel.title_style(cx); @@ -504,24 +344,9 @@ impl TabPanel { .h(px(30.)) .py_2() .px_3() - .when(left_dock_button.is_some(), |this| this.pl_2()) - .when(right_dock_button.is_some(), |this| this.pr_2()) .when_some(title_style, |this, theme| { this.bg(theme.background).text_color(theme.foreground) }) - .when( - left_dock_button.is_some() || bottom_dock_button.is_some(), - |this| { - this.child( - h_flex() - .flex_shrink_0() - .mr_1() - .gap_1() - .children(left_dock_button) - .children(bottom_dock_button), - ) - }, - ) .child( div() .id("tab") @@ -549,8 +374,7 @@ impl TabPanel { .flex_shrink_0() .ml_1() .gap_1() - .child(self.render_toolbar(cx)) - .children(right_dock_button), + .child(self.render_toolbar(cx)), ) .into_any_element(); } @@ -559,25 +383,6 @@ impl TabPanel { TabBar::new("tab-bar") .track_scroll(self.tab_bar_scroll_handle.clone()) - .when( - left_dock_button.is_some() || bottom_dock_button.is_some(), - |this| { - this.prefix( - h_flex() - .items_center() - .top_0() - .right_0() - .border_r_1() - .border_b_1() - .h_full() - .border_color(cx.theme().border) - .bg(cx.theme().tab_bar) - .px_2() - .children(left_dock_button) - .children(bottom_dock_button), - ) - }, - ) .children(self.panels.iter().enumerate().map(|(ix, panel)| { let mut active = ix == self.active_ix; @@ -645,8 +450,7 @@ impl TabPanel { .bg(cx.theme().tab_bar) .px_2() .gap_1() - .child(self.render_toolbar(cx)) - .when_some(right_dock_button, |this, btn| this.child(btn)), + .child(self.render_toolbar(cx)), ) .into_any_element() } diff --git a/crates/ui/src/dock/toggle_buttons.rs b/crates/ui/src/dock/toggle_buttons.rs new file mode 100644 index 00000000..b429ace5 --- /dev/null +++ b/crates/ui/src/dock/toggle_buttons.rs @@ -0,0 +1,112 @@ +use gpui::{ + div, prelude::FluentBuilder as _, Div, InteractiveElement as _, IntoElement, + ParentElement as _, RenderOnce, Stateful, Styled, WeakView, WindowContext, +}; + +use crate::{ + button::{Button, ButtonStyle, ButtonStyled}, + button_group::ButtonGroup, + IconName, Selectable as _, Sizable, Size, +}; + +use super::{DockArea, DockPlacement}; + +#[derive(IntoElement)] +pub struct ToggleButtons { + base: Stateful
, + dock_area: WeakView, + size: Size, + style: ButtonStyle, +} + +impl ToggleButtons { + /// Create a new instance of the toggle buttons. + pub fn new(dock_area: WeakView) -> Self { + Self { + dock_area, + base: div().id("dock-toggle-buttons"), + style: ButtonStyle::Outline, + size: Size::Medium, + } + } +} + +impl Sizable for ToggleButtons { + fn with_size(mut self, size: impl Into) -> Self { + self.size = size.into(); + self + } +} +impl ButtonStyled for ToggleButtons { + fn with_style(mut self, style: ButtonStyle) -> Self { + self.style = style; + self + } +} +impl Styled for ToggleButtons { + fn style(&mut self) -> &mut gpui::StyleRefinement { + self.base.style() + } +} + +impl RenderOnce for ToggleButtons { + fn render(self, cx: &mut WindowContext) -> impl IntoElement { + let Some(dock_area) = self.dock_area.upgrade() else { + return self.base; + }; + + let left_dock: Option = dock_area + .read(cx) + .has_dock(DockPlacement::Left) + .then(|| dock_area.read(cx).is_dock_open(DockPlacement::Left, cx)); + let right_dock: Option = dock_area + .read(cx) + .has_dock(DockPlacement::Right) + .then(|| dock_area.read(cx).is_dock_open(DockPlacement::Right, cx)); + let bottom_dock: Option = dock_area + .read(cx) + .has_dock(DockPlacement::Bottom) + .then(|| dock_area.read(cx).is_dock_open(DockPlacement::Bottom, cx)); + + self.base.child( + ButtonGroup::new("toggle-docks") + .with_style(self.style) + .with_size(self.size) + .when_some(left_dock, |this, open| { + this.child( + Button::new("toggle-left-dock") + .icon(IconName::PanelLeft) + .selected(open), + ) + }) + .when_some(bottom_dock, |this, open| { + this.child( + Button::new("toggle-bottom-dock") + .icon(IconName::PanelBottom) + .selected(open), + ) + }) + .when_some(right_dock, |this, open| { + this.child( + Button::new("toggle-right-dock") + .icon(IconName::PanelRight) + .selected(open), + ) + }) + .on_click(move |indexes, cx| { + if let Some(ix) = indexes.first() { + let placement = match ix { + 0 => DockPlacement::Left, + 1 => DockPlacement::Bottom, + 2 => DockPlacement::Right, + _ => DockPlacement::Left, + }; + + dock_area.update(cx, |this, cx| { + this.toggle_dock(placement, cx); + }) + } + }), + ) + } +}