From 020228ca909864d8317307734be2ca87508ff0d4 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Thu, 19 Dec 2024 18:05:04 +0800 Subject: [PATCH] . --- crates/story/examples/tiles.rs | 12 ++- crates/story/src/lib.rs | 88 ++++++++++++++++---- crates/story/src/main.rs | 135 +++++++++++++++++-------------- crates/ui/src/dock/mod.rs | 4 +- crates/ui/src/dock/tab_panel.rs | 15 ++-- crates/ui/src/resizable/panel.rs | 69 +++++++++------- 6 files changed, 207 insertions(+), 116 deletions(-) diff --git a/crates/story/examples/tiles.rs b/crates/story/examples/tiles.rs index fde3102f..b0c82e0d 100644 --- a/crates/story/examples/tiles.rs +++ b/crates/story/examples/tiles.rs @@ -145,8 +145,16 @@ impl StoryTiles { fn init_default_layout(dock_area: &WeakView, cx: &mut WindowContext) -> DockItem { DockItem::tiles( vec![ - DockItem::tab(StoryContainer::panel::(cx), dock_area, cx), - DockItem::tab(StoryContainer::panel::(cx), dock_area, cx), + DockItem::tab( + StoryContainer::panel::(dock_area, cx), + dock_area, + cx, + ), + DockItem::tab( + StoryContainer::panel::(dock_area, cx), + dock_area, + cx, + ), ], vec![ Bounds::new(point(px(10.), px(10.)), size(px(610.), px(190.))), diff --git a/crates/story/src/lib.rs b/crates/story/src/lib.rs index a03e2943..b400cb49 100644 --- a/crates/story/src/lib.rs +++ b/crates/story/src/lib.rs @@ -19,6 +19,8 @@ mod text_story; mod tooltip_story; mod webview_story; +use std::sync::Arc; + pub use assets::Assets; pub use accordion_story::AccordionStory; @@ -43,15 +45,18 @@ pub use tooltip_story::TooltipStory; pub use webview_story::WebViewStory; use gpui::{ - actions, div, prelude::FluentBuilder as _, px, AnyElement, AnyView, AppContext, Div, - EventEmitter, FocusableView, Hsla, InteractiveElement, IntoElement, ParentElement, Render, - SharedString, Styled as _, View, ViewContext, VisualContext, WindowContext, + actions, div, prelude::FluentBuilder as _, px, AnyElement, AnyView, AppContext, Context as _, + Div, EventEmitter, FocusableView, Global, Hsla, InteractiveElement, IntoElement, Model, + ParentElement, Render, SharedString, Styled as _, Subscription, View, ViewContext, + VisualContext, WeakView, WindowContext, }; use ui::{ button::Button, divider::Divider, - dock::{register_panel, Panel, PanelEvent, PanelInfo, PanelState, TitleStyle}, + dock::{ + register_panel, DockArea, Panel, PanelEvent, PanelInfo, PanelState, PanelView, TitleStyle, + }, h_flex, label::Label, notification::Notification, @@ -62,12 +67,35 @@ use ui::{ const PANEL_NAME: &str = "StoryContainer"; +pub struct AppState { + pub invisible_panels: Model>, +} +impl AppState { + fn init(cx: &mut AppContext) { + let state = Self { + invisible_panels: cx.new_model(|_| Vec::new()), + }; + cx.set_global::(state); + } + + pub fn global(cx: &AppContext) -> &Self { + cx.global::() + } + + pub fn global_mut(cx: &mut AppContext) -> &mut Self { + cx.global_mut::() + } +} + +impl Global for AppState {} + pub fn init(cx: &mut AppContext) { input_story::init(cx); dropdown_story::init(cx); popup_story::init(cx); + AppState::init(cx); - register_panel(cx, PANEL_NAME, |_, _, info, cx| { + register_panel(cx, PANEL_NAME, |dock_area, _, info, cx| { let story_state = match info { PanelInfo::Panel(value) => StoryState::from_value(value.clone()), _ => { @@ -77,14 +105,14 @@ pub fn init(cx: &mut AppContext) { let view = cx.new_view(|cx| { let (title, description, closable, zoomable, story) = story_state.to_story(cx); - let mut container = StoryContainer::new(cx).story(story, story_state.story_klass); + let mut container = StoryContainer::new(title.into(), &dock_area, cx) + .story(story, story_state.story_klass); cx.on_focus_in(&container.focus_handle, |this: &mut StoryContainer, _| { println!("StoryContainer focus in: {}", this.name); }) .detach(); - container.name = title.into(); container.description = description.into(); container.closable = closable; container.zoomable = zoomable; @@ -124,6 +152,8 @@ pub struct StoryContainer { story_klass: Option, closable: bool, zoomable: bool, + visible: bool, + _subscriptions: Vec, } #[derive(Debug)] @@ -155,12 +185,37 @@ pub trait Story: FocusableView { impl EventEmitter for StoryContainer {} impl StoryContainer { - pub fn new(cx: &mut WindowContext) -> Self { + pub fn new( + name: SharedString, + dock_area: &WeakView, + cx: &mut ViewContext, + ) -> Self { let focus_handle = cx.focus_handle(); + // Observe invisible panels to toggle panel visibility in DockArea. + let invisible_panels = AppState::global(cx).invisible_panels.clone(); + + let _subscriptions = vec![cx.observe(&invisible_panels, { + let dock_area = dock_area.clone(); + let panel_name = name.clone(); + move |story, model, cx| { + let was_visible = story.visible; + let panel: Arc = Arc::new(cx.view().clone()); + + _ = dock_area.update(cx, |this, cx| { + let visible = !model.read(cx).contains(&panel_name); + if visible != was_visible { + story.visible = visible; + println!("Update panel: {}, visible: {}", &panel_name, visible); + this.set_panel_visible(&panel, visible, cx); + } + }); + } + })]; + Self { focus_handle, - name: "".into(), + name, title_bg: None, description: "".into(), width: None, @@ -169,10 +224,12 @@ impl StoryContainer { story_klass: None, closable: true, zoomable: true, + visible: true, + _subscriptions, } } - pub fn panel(cx: &mut WindowContext) -> View { + pub fn panel(dock_area: &WeakView, cx: &mut WindowContext) -> View { let name = S::title(); let description = S::description(); let story = S::new_view(cx); @@ -180,11 +237,10 @@ impl StoryContainer { let focus_handle = story.focus_handle(cx); let view = cx.new_view(|cx| { - let mut story = Self::new(cx).story(story.into(), story_klass); + let mut story = Self::new(name.into(), dock_area, cx).story(story.into(), story_klass); story.focus_handle = focus_handle; story.closable = S::closable(); story.zoomable = S::zoomable(); - story.name = name.into(); story.description = description.into(); story.title_bg = S::title_bg(); story @@ -304,12 +360,12 @@ impl Panel for StoryContainer { self.zoomable } - fn set_zoomed(&self, zoomed: bool, _cx: &ViewContext) { - println!("panel: {} zoomed: {}", self.name, zoomed); + fn set_zoomed(&self, _: bool, _cx: &ViewContext) { + // println!("panel: {} zoomed: {}", self.name, zoomed); } - fn set_active(&self, active: bool, _cx: &ViewContext) { - println!("panel: {} active: {}", self.name, active); + fn set_active(&self, _: bool, _cx: &ViewContext) { + // println!("panel: {} active: {}", self.name, active); } fn popup_menu(&self, menu: PopupMenu, _cx: &WindowContext) -> PopupMenu { diff --git a/crates/story/src/main.rs b/crates/story/src/main.rs index 12ee1de0..82f72ce3 100644 --- a/crates/story/src/main.rs +++ b/crates/story/src/main.rs @@ -1,12 +1,13 @@ use anyhow::{Context as _, Result}; -use gpui::{Context, *}; +use gpui::*; use prelude::FluentBuilder as _; use serde::Deserialize; use std::{sync::Arc, time::Duration}; use story::{ - AccordionStory, Assets, ButtonStory, CalendarStory, DropdownStory, IconStory, ImageStory, - InputStory, ListStory, ModalStory, PopupStory, ProgressStory, ResizableStory, ScrollableStory, - SidebarStory, StoryContainer, SwitchStory, TableStory, TextStory, TooltipStory, + AccordionStory, AppState, Assets, ButtonStory, CalendarStory, DropdownStory, IconStory, + ImageStory, InputStory, ListStory, ModalStory, PopupStory, ProgressStory, ResizableStory, + ScrollableStory, SidebarStory, StoryContainer, SwitchStory, TableStory, TextStory, + TooltipStory, }; use ui::{ button::{Button, ButtonVariants as _}, @@ -69,7 +70,6 @@ pub struct StoryWorkspace { font_size_selector: View, theme_color_picker: View, last_layout_state: Option, - invisable_panels: Model>, _save_layout_task: Option>, } @@ -140,15 +140,12 @@ impl StoryWorkspace { ) .detach(); - let invisable_panels = cx.new_model(|_| vec![]); - Self { theme_color: None, dock_area, locale_selector, font_size_selector, theme_color_picker, - invisable_panels, last_layout_state: None, _save_layout_task: None, } @@ -245,11 +242,15 @@ impl StoryWorkspace { let left_panels = DockItem::split_with_sizes( Axis::Vertical, vec![ - DockItem::tab(StoryContainer::panel::(cx), &dock_area, cx), + DockItem::tab( + StoryContainer::panel::(&dock_area, cx), + &dock_area, + cx, + ), DockItem::tabs( vec![ - Arc::new(StoryContainer::panel::(cx)), - Arc::new(StoryContainer::panel::(cx)), + Arc::new(StoryContainer::panel::(&dock_area, cx)), + Arc::new(StoryContainer::panel::(&dock_area, cx)), ], None, &dock_area, @@ -265,8 +266,8 @@ impl StoryWorkspace { Axis::Vertical, vec![DockItem::tabs( vec![ - Arc::new(StoryContainer::panel::(cx)), - Arc::new(StoryContainer::panel::(cx)), + Arc::new(StoryContainer::panel::(&dock_area, cx)), + Arc::new(StoryContainer::panel::(&dock_area, cx)), ], None, &dock_area, @@ -280,8 +281,16 @@ impl StoryWorkspace { let right_panels = DockItem::split_with_sizes( Axis::Vertical, vec![ - DockItem::tab(StoryContainer::panel::(cx), &dock_area, cx), - DockItem::tab(StoryContainer::panel::(cx), &dock_area, cx), + DockItem::tab( + StoryContainer::panel::(&dock_area, cx), + &dock_area, + cx, + ), + DockItem::tab( + StoryContainer::panel::(&dock_area, cx), + &dock_area, + cx, + ), ], vec![None], &dock_area, @@ -304,25 +313,25 @@ impl StoryWorkspace { Axis::Vertical, vec![DockItem::tabs( vec![ - Arc::new(StoryContainer::panel::(cx)), - Arc::new(StoryContainer::panel::(cx)), - Arc::new(StoryContainer::panel::(cx)), - Arc::new(StoryContainer::panel::(cx)), - Arc::new(StoryContainer::panel::(cx)), - Arc::new(StoryContainer::panel::(cx)), - Arc::new(StoryContainer::panel::(cx)), - Arc::new(StoryContainer::panel::(cx)), - Arc::new(StoryContainer::panel::(cx)), - Arc::new(StoryContainer::panel::(cx)), - Arc::new(StoryContainer::panel::(cx)), - Arc::new(StoryContainer::panel::(cx)), - Arc::new(StoryContainer::panel::(cx)), - Arc::new(StoryContainer::panel::(cx)), - Arc::new(StoryContainer::panel::(cx)), - Arc::new(StoryContainer::panel::(cx)), - Arc::new(StoryContainer::panel::(cx)), - Arc::new(StoryContainer::panel::(cx)), - // Arc::new(StoryContainer::panel::(cx)), + Arc::new(StoryContainer::panel::(&dock_area, cx)), + Arc::new(StoryContainer::panel::(&dock_area, cx)), + Arc::new(StoryContainer::panel::(&dock_area, cx)), + Arc::new(StoryContainer::panel::(&dock_area, cx)), + Arc::new(StoryContainer::panel::(&dock_area, cx)), + Arc::new(StoryContainer::panel::(&dock_area, cx)), + Arc::new(StoryContainer::panel::(&dock_area, cx)), + Arc::new(StoryContainer::panel::(&dock_area, cx)), + Arc::new(StoryContainer::panel::(&dock_area, cx)), + Arc::new(StoryContainer::panel::(&dock_area, cx)), + Arc::new(StoryContainer::panel::(&dock_area, cx)), + Arc::new(StoryContainer::panel::(&dock_area, cx)), + Arc::new(StoryContainer::panel::(&dock_area, cx)), + Arc::new(StoryContainer::panel::(&dock_area, cx)), + Arc::new(StoryContainer::panel::(&dock_area, cx)), + Arc::new(StoryContainer::panel::(&dock_area, cx)), + Arc::new(StoryContainer::panel::(&dock_area, cx)), + Arc::new(StoryContainer::panel::(&dock_area, cx)), + // Arc::new(StoryContainer::panel::(&dock_area, cx)), ], None, &dock_area, @@ -381,27 +390,27 @@ impl StoryWorkspace { } fn on_action_add_panel(&mut self, action: &AddPanel, cx: &mut ViewContext) { + let dock_area = self.dock_area.downgrade(); // Random pick up a panel to add let panel = match rand::random::() % 18 { - 0 => Arc::new(StoryContainer::panel::(cx)), - 1 => Arc::new(StoryContainer::panel::(cx)), - 2 => Arc::new(StoryContainer::panel::(cx)), - 3 => Arc::new(StoryContainer::panel::(cx)), - 4 => Arc::new(StoryContainer::panel::(cx)), - 5 => Arc::new(StoryContainer::panel::(cx)), - 6 => Arc::new(StoryContainer::panel::(cx)), - 7 => Arc::new(StoryContainer::panel::(cx)), - 8 => Arc::new(StoryContainer::panel::(cx)), - 9 => Arc::new(StoryContainer::panel::(cx)), - 10 => Arc::new(StoryContainer::panel::(cx)), - 11 => Arc::new(StoryContainer::panel::(cx)), - 12 => Arc::new(StoryContainer::panel::(cx)), - 13 => Arc::new(StoryContainer::panel::(cx)), - 14 => Arc::new(StoryContainer::panel::(cx)), - 15 => Arc::new(StoryContainer::panel::(cx)), - 16 => Arc::new(StoryContainer::panel::(cx)), - // 17 => Arc::new(StoryContainer::panel::(cx)), - _ => Arc::new(StoryContainer::panel::(cx)), + 0 => Arc::new(StoryContainer::panel::(&dock_area, cx)), + 1 => Arc::new(StoryContainer::panel::(&dock_area, cx)), + 2 => Arc::new(StoryContainer::panel::(&dock_area, cx)), + 3 => Arc::new(StoryContainer::panel::(&dock_area, cx)), + 4 => Arc::new(StoryContainer::panel::(&dock_area, cx)), + 5 => Arc::new(StoryContainer::panel::(&dock_area, cx)), + 6 => Arc::new(StoryContainer::panel::(&dock_area, cx)), + 7 => Arc::new(StoryContainer::panel::(&dock_area, cx)), + 8 => Arc::new(StoryContainer::panel::(&dock_area, cx)), + 9 => Arc::new(StoryContainer::panel::(&dock_area, cx)), + 10 => Arc::new(StoryContainer::panel::(&dock_area, cx)), + 11 => Arc::new(StoryContainer::panel::(&dock_area, cx)), + 12 => Arc::new(StoryContainer::panel::(&dock_area, cx)), + 13 => Arc::new(StoryContainer::panel::(&dock_area, cx)), + 14 => Arc::new(StoryContainer::panel::(&dock_area, cx)), + 15 => Arc::new(StoryContainer::panel::(&dock_area, cx)), + 16 => Arc::new(StoryContainer::panel::(&dock_area, cx)), + _ => Arc::new(StoryContainer::panel::(&dock_area, cx)), }; self.dock_area.update(cx, |dock_area, cx| { @@ -415,12 +424,14 @@ impl StoryWorkspace { cx: &mut ViewContext, ) { let panel_name = action.0.clone(); - self.invisable_panels.update(cx, |invisable_panels, _| { - if invisable_panels.contains(&panel_name) { - invisable_panels.retain(|id| id != &panel_name); + let invisble_panels = AppState::global(cx).invisible_panels.clone(); + invisble_panels.update(cx, |names, cx| { + if names.contains(&panel_name) { + names.retain(|id| id != &panel_name); } else { - invisable_panels.push(panel_name); + names.push(panel_name); } + cx.notify(); }); cx.notify(); } @@ -446,7 +457,7 @@ impl Render for StoryWorkspace { let modal_layer = Root::render_modal_layer(cx); let notification_layer = Root::render_notification_layer(cx); let notifications_count = cx.notifications().len(); - let invisable_panels = self.invisable_panels.read(cx).clone(); + let invisble_panels = AppState::global(cx).invisible_panels.clone(); div() .id("story-workspace") @@ -473,7 +484,7 @@ impl Render for StoryWorkspace { .icon(IconName::LayoutDashboard) .small() .ghost() - .popup_menu(move |menu, _| { + .popup_menu(move |menu, cx| { menu.menu( "Add Panel to Center", Box::new(AddPanel(DockPlacement::Center)), @@ -494,7 +505,8 @@ impl Render for StoryWorkspace { .separator() .menu_with_check( "Button", - invisable_panels + !invisble_panels + .read(cx) .contains(&SharedString::from("Button")), Box::new(TogglePanelVisible(SharedString::from( "Button", @@ -502,7 +514,8 @@ impl Render for StoryWorkspace { ) .menu_with_check( "Accordion", - invisable_panels + !invisble_panels + .read(cx) .contains(&SharedString::from("Accordion")), Box::new(TogglePanelVisible(SharedString::from( "Accordion", diff --git a/crates/ui/src/dock/mod.rs b/crates/ui/src/dock/mod.rs index 259fc0d1..62de09a4 100644 --- a/crates/ui/src/dock/mod.rs +++ b/crates/ui/src/dock/mod.rs @@ -363,8 +363,8 @@ impl DockItem { ) { match self { DockItem::Tabs { view, .. } => { - view.update(cx, |tab_panel, cx| { - tab_panel.set_panel_visible(panel, visible, cx); + view.update(cx, |this, cx| { + this.set_panel_visible(panel, visible, cx); }); } DockItem::Split { items, .. } => { diff --git a/crates/ui/src/dock/tab_panel.rs b/crates/ui/src/dock/tab_panel.rs index e28861fc..09a4c941 100644 --- a/crates/ui/src/dock/tab_panel.rs +++ b/crates/ui/src/dock/tab_panel.rs @@ -244,20 +244,19 @@ impl TabPanel { return; }; - if self.panels.len() == 1 { - _ = stack_panel.update(cx, |view, cx| { - let panel: Arc = Arc::new(cx.view().clone()); - view.set_panel_visible(&panel, visible, cx); - }); - return; - } - if visible { self.invisable_panels.remove(&panel.view().entity_id()); } else { self.invisable_panels .insert(panel.view().entity_id(), visible); } + + let visible_in_stack = self.visible_panels().count() > 0; + let panel: Arc = Arc::new(cx.view().clone()); + _ = stack_panel.update(cx, |view, cx| { + view.set_panel_visible(&panel, visible_in_stack, cx); + }); + return; } fn add_panel_with_active( diff --git a/crates/ui/src/resizable/panel.rs b/crates/ui/src/resizable/panel.rs index 2168cb64..1e671e0c 100644 --- a/crates/ui/src/resizable/panel.rs +++ b/crates/ui/src/resizable/panel.rs @@ -153,17 +153,8 @@ impl ResizablePanelGroup { return; }; - panel.update(cx, |this, _| { - this.visible = visible; - if visible { - this.size = this.visible_size; - if let Some(size) = this.visible_size { - self.sizes[ix] = size; - } - } else { - this.visible_size = this.size; - self.sizes[ix] = px(0.); - } + panel.update(cx, |this, cx| { + this.set_visible(visible, cx); }); cx.notify() } @@ -311,8 +302,7 @@ pub struct ResizablePanel { bounds: Bounds, resize_handle: Option, visible: bool, - /// The size of the panel when it is visible, used to keep old size when toggle visibility. - visible_size: Option, + was_visible_size: Option, } impl ResizablePanel { @@ -322,7 +312,7 @@ impl ResizablePanel { initial_size: None, size: None, visible: true, - visible_size: None, + was_visible_size: None, size_ratio: None, axis: Axis::Horizontal, content_builder: None, @@ -351,24 +341,49 @@ impl ResizablePanel { self } + /// Set this panel to be visible or not. + /// If invisible, the size of the panel will be set to 0. + fn set_visible(&mut self, visible: bool, cx: &mut ViewContext) { + self.visible = visible; + if visible { + if let Some(size) = self.was_visible_size { + self.was_visible_size = None; + self.size = Some(size); + self.update_group_panel_size(size, cx); + } else { + self.size = None; + } + } else { + self.was_visible_size = self.size; + self.size = Some(px(0.)); + self.update_group_panel_size(px(0.), cx); + } + cx.notify(); + } + + fn update_group_panel_size(&mut self, new_size: Pixels, cx: &mut ViewContext) { + let panel_view = cx.view().clone(); + if let Some(group) = self.group.clone() { + cx.window_context().defer(move |cx| { + _ = group.update(cx, |view, _| { + if let Some(ix) = view + .panels + .iter() + .position(|v| v.entity_id() == panel_view.entity_id()) + { + view.sizes[ix] = new_size; + } + }); + }); + } + } + /// Save the real panel size, and update group sizes fn update_size(&mut self, bounds: Bounds, cx: &mut ViewContext) { let new_size = bounds.size.along(self.axis); self.bounds = bounds; self.size = Some(new_size); - - let panel_view = cx.view().clone(); - if let Some(group) = self.group.as_ref() { - _ = group.update(cx, |view, _| { - if let Some(ix) = view - .panels - .iter() - .position(|v| v.entity_id() == panel_view.entity_id()) - { - view.sizes[ix] = new_size; - } - }); - } + self.update_group_panel_size(new_size, cx); cx.notify(); } }