diff --git a/crates/coop/src/sidebar/mod.rs b/crates/coop/src/sidebar/mod.rs index 63496d9..cbb8f8f 100644 --- a/crates/coop/src/sidebar/mod.rs +++ b/crates/coop/src/sidebar/mod.rs @@ -18,15 +18,12 @@ use smallvec::{smallvec, SmallVec}; use state::{NostrRegistry, FIND_DELAY}; use theme::{ActiveTheme, TITLEBAR_HEIGHT}; use ui::button::{Button, ButtonVariants}; -use ui::divider::Divider; use ui::dock_area::panel::{Panel, PanelEvent}; use ui::indicator::Indicator; use ui::input::{InputEvent, InputState, TextInput}; use ui::notification::Notification; use ui::scroll::Scrollbar; -use ui::{ - h_flex, v_flex, Disableable, Icon, IconName, Selectable, Sizable, StyledExt, WindowExtension, -}; +use ui::{h_flex, v_flex, Icon, IconName, Selectable, Sizable, StyledExt, WindowExtension}; mod entry; @@ -122,12 +119,10 @@ impl Sidebar { } } InputEvent::Focus => { - this.set_input_focus(window, cx); + this.set_input_focus(true, window, cx); this.get_contact_list(window, cx); } - InputEvent::Blur => { - this.set_input_focus(window, cx); - } + _ => {} }; }), ); @@ -246,6 +241,7 @@ impl Sidebar { })); } + /// Set the results of the search fn set_results(&mut self, results: Vec, cx: &mut Context) { self.find_results.update(cx, |this, cx| { *this = Some(results); @@ -253,6 +249,7 @@ impl Sidebar { }); } + /// Set the finding status fn set_finding(&mut self, status: bool, _window: &mut Window, cx: &mut Context) { // Disable the input to prevent duplicate requests self.find_input.update(cx, |this, cx| { @@ -264,13 +261,14 @@ impl Sidebar { cx.notify(); } - fn set_input_focus(&mut self, window: &mut Window, cx: &mut Context) { - self.find_focused = !self.find_focused; + /// Set the focus status of the input element. + fn set_input_focus(&mut self, status: bool, window: &mut Window, cx: &mut Context) { + self.find_focused = status; cx.notify(); - // Reset the find panel - if !self.find_focused { - self.reset(window, cx); + // Focus to the input element + if !status { + window.focus_prev(cx); } } @@ -356,7 +354,8 @@ impl Sidebar { } /// Set the active filter for the sidebar. - fn set_filter(&mut self, kind: RoomKind, cx: &mut Context) { + fn set_filter(&mut self, kind: RoomKind, window: &mut Window, cx: &mut Context) { + self.set_input_focus(false, window, cx); self.filter.update(cx, |this, cx| { *this = kind; cx.notify(); @@ -495,12 +494,13 @@ impl Render for Sidebar { v_flex() .image_cache(self.image_cache.clone()) .size_full() - .relative() + .gap_2() .child( h_flex() .h(TITLEBAR_HEIGHT) .border_b_1() - .border_color(cx.theme().border) + .border_color(cx.theme().border_variant) + .bg(cx.theme().elevated_surface_background) .child( TextInput::new(&self.find_input) .appearance(false) @@ -520,22 +520,17 @@ impl Render for Sidebar { ) .child( h_flex() - .h(TITLEBAR_HEIGHT) + .px_2() + .gap_2() .justify_center() - .border_b_1() - .border_color(cx.theme().border) .when(show_find_panel, |this| { this.child( Button::new("search-results") .icon(IconName::Search) - .label("Search") .tooltip("All search results") .small() - .underline() - .ghost() + .ghost_alt() .font_semibold() - .rounded_none() - .h_full() .flex_1() .selected(true), ) @@ -552,21 +547,16 @@ impl Render for Sidebar { .when(!show_find_panel, |this| this.label("Inbox")) .tooltip("All ongoing conversations") .small() - .underline() - .ghost() + .ghost_alt() .font_semibold() - .rounded_none() - .h_full() .flex_1() - .disabled(show_find_panel) .selected( !show_find_panel && self.current_filter(&RoomKind::Ongoing, cx), ) - .on_click(cx.listener(|this, _ev, _window, cx| { - this.set_filter(RoomKind::Ongoing, cx); + .on_click(cx.listener(|this, _ev, window, cx| { + this.set_filter(RoomKind::Ongoing, window, cx); })), ) - .child(Divider::vertical()) .child( Button::new("requests") .map(|this| { @@ -579,31 +569,26 @@ impl Render for Sidebar { .when(!show_find_panel, |this| this.label("Requests")) .tooltip("Incoming new conversations") .small() - .ghost() - .underline() + .ghost_alt() .font_semibold() - .rounded_none() - .h_full() .flex_1() - .disabled(show_find_panel) .selected( !show_find_panel && !self.current_filter(&RoomKind::Ongoing, cx), ) .when(self.new_requests, |this| { this.child(div().size_1().rounded_full().bg(cx.theme().cursor)) }) - .on_click(cx.listener(|this, _ev, _window, cx| { - this.set_filter(RoomKind::default(), cx); + .on_click(cx.listener(|this, _ev, window, cx| { + this.set_filter(RoomKind::default(), window, cx); })), ), ) .when(!show_find_panel && !loading && total_rooms == 0, |this| { this.child( - div().mt_2().px_2().child( + div().px_2().child( v_flex() .p_3() .h_24() - .w_full() .border_2() .border_dashed() .border_color(cx.theme().border_variant) @@ -629,9 +614,8 @@ impl Render for Sidebar { v_flex() .h_full() .px_1p5() - .mt_2() - .flex_1() .gap_1() + .flex_1() .overflow_y_hidden() .when(show_find_panel, |this| { this.gap_3() @@ -747,7 +731,7 @@ impl Render for Sidebar { .bg(cx.theme().background.opacity(0.85)) .border_color(cx.theme().border_disabled) .border_1() - .when(cx.theme().shadow, |this| this.shadow_sm()) + .when(cx.theme().shadow, |this| this.shadow_xs()) .rounded_full() .text_xs() .font_semibold() diff --git a/crates/coop/src/workspace.rs b/crates/coop/src/workspace.rs index 9749ba9..2eac3ab 100644 --- a/crates/coop/src/workspace.rs +++ b/crates/coop/src/workspace.rs @@ -9,7 +9,7 @@ use gpui::{ use person::PersonRegistry; use smallvec::{smallvec, SmallVec}; use state::{NostrRegistry, RelayState}; -use theme::{ActiveTheme, SIDEBAR_WIDTH, TITLEBAR_HEIGHT}; +use theme::{ActiveTheme, Theme, SIDEBAR_WIDTH, TITLEBAR_HEIGHT}; use title_bar::TitleBar; use ui::avatar::Avatar; use ui::button::{Button, ButtonVariants}; @@ -41,10 +41,17 @@ impl Workspace { fn new(window: &mut Window, cx: &mut Context) -> Self { let chat = ChatRegistry::global(cx); let titlebar = cx.new(|_| TitleBar::new()); - let dock = cx.new(|cx| DockArea::new(window, cx).panel_style(PanelStyle::TabBar)); + let dock = cx.new(|cx| DockArea::new(window, cx).style(PanelStyle::TabBar)); let mut subscriptions = smallvec![]; + subscriptions.push( + // Observe system appearance and update theme + cx.observe_window_appearance(window, |_this, window, cx| { + Theme::sync_system_appearance(Some(window), cx); + }), + ); + subscriptions.push( // Observe all events emitted by the chat registry cx.subscribe_in(&chat, window, move |this, chat, ev, window, cx| { @@ -236,11 +243,11 @@ impl Workspace { h_flex() .h_6() .w_full() - .px_1() + .px_2() .text_xs() .text_color(cx.theme().warning_foreground) .bg(cx.theme().warning_background) - .rounded_sm() + .rounded_full() .child(SharedString::from( "User hasn't configured a messaging relay list", )), diff --git a/crates/theme/src/colors.rs b/crates/theme/src/colors.rs index bdb9426..c11d5a5 100644 --- a/crates/theme/src/colors.rs +++ b/crates/theme/src/colors.rs @@ -78,8 +78,10 @@ pub struct ThemeColors { // Tab colors pub tab_inactive_background: Hsla, - pub tab_hover_background: Hsla, + pub tab_inactive_foreground: Hsla, pub tab_active_background: Hsla, + pub tab_active_foreground: Hsla, + pub tab_hover_foreground: Hsla, // Scrollbar colors pub scrollbar_thumb_background: Hsla, @@ -106,10 +108,10 @@ impl ThemeColors { background: neutral().light().step_1(), surface_background: neutral().light().step_2(), elevated_surface_background: neutral().light().step_3(), - panel_background: gpui::white(), + panel_background: neutral().light().step_1(), overlay: neutral().light_alpha().step_3(), - title_bar: gpui::transparent_black(), - title_bar_inactive: neutral().light().step_1(), + title_bar: neutral().light().step_2(), + title_bar_inactive: neutral().light().step_3(), window_border: hsl(240.0, 5.9, 78.0), border: neutral().light().step_6(), @@ -164,9 +166,11 @@ impl ThemeColors { ghost_element_selected: neutral().light().step_5(), ghost_element_disabled: neutral().light_alpha().step_2(), - tab_inactive_background: neutral().light().step_3(), - tab_hover_background: neutral().light().step_4(), - tab_active_background: neutral().light().step_5(), + tab_inactive_background: neutral().light().step_2(), + tab_inactive_foreground: neutral().light().step_11(), + tab_active_background: neutral().light().step_1(), + tab_active_foreground: neutral().light().step_12(), + tab_hover_foreground: brand().light().step_9(), scrollbar_thumb_background: neutral().light_alpha().step_3(), scrollbar_thumb_hover_background: neutral().light_alpha().step_4(), @@ -246,9 +250,11 @@ impl ThemeColors { ghost_element_selected: neutral().dark().step_5(), ghost_element_disabled: neutral().dark_alpha().step_2(), - tab_inactive_background: neutral().dark().step_3(), - tab_hover_background: neutral().dark().step_4(), - tab_active_background: neutral().dark().step_5(), + tab_inactive_background: neutral().dark().step_2(), + tab_inactive_foreground: neutral().dark().step_11(), + tab_active_background: neutral().dark().step_3(), + tab_active_foreground: neutral().dark().step_12(), + tab_hover_foreground: brand().dark().step_9(), scrollbar_thumb_background: neutral().dark_alpha().step_3(), scrollbar_thumb_hover_background: neutral().dark_alpha().step_4(), diff --git a/crates/title_bar/src/lib.rs b/crates/title_bar/src/lib.rs index 24bb446..4bbaa0e 100644 --- a/crates/title_bar/src/lib.rs +++ b/crates/title_bar/src/lib.rs @@ -106,7 +106,7 @@ impl Render for TitleBar { }) .bg(color) .border_b_1() - .border_color(cx.theme().border) + .border_color(cx.theme().border_variant) .content_stretch() .child( h_flex() diff --git a/crates/ui/src/dock_area/dock.rs b/crates/ui/src/dock_area/dock.rs index 3c427a6..374317e 100644 --- a/crates/ui/src/dock_area/dock.rs +++ b/crates/ui/src/dock_area/dock.rs @@ -1,32 +1,27 @@ +use std::ops::Deref; use std::sync::Arc; use gpui::prelude::FluentBuilder as _; use gpui::{ - div, px, App, AppContext, Axis, Context, Element, Entity, InteractiveElement as _, IntoElement, - MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Render, - StatefulInteractiveElement, Style, Styled as _, WeakEntity, Window, + div, px, App, AppContext, Axis, Context, Element, Entity, IntoElement, MouseMoveEvent, + MouseUpEvent, ParentElement as _, Pixels, Point, Render, Style, Styled as _, WeakEntity, + Window, }; -use serde::{Deserialize, Serialize}; -use theme::ActiveTheme; use super::{DockArea, DockItem}; use crate::dock_area::panel::PanelView; use crate::dock_area::tab_panel::TabPanel; -use crate::resizable::{HANDLE_PADDING, HANDLE_SIZE, PANEL_MIN_SIZE}; -use crate::{AxisExt as _, StyledExt}; +use crate::resizable::{resize_handle, PANEL_MIN_SIZE}; +use crate::StyledExt; #[derive(Clone, Render)] struct ResizePanel; -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DockPlacement { - #[serde(rename = "center")] Center, - #[serde(rename = "left")] Left, - #[serde(rename = "bottom")] Bottom, - #[serde(rename = "right")] Right, } @@ -58,16 +53,21 @@ impl DockPlacement { pub struct Dock { pub(super) placement: DockPlacement, dock_area: WeakEntity, + + /// Dock layout 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, + + /// Whether the Dock is open pub(super) open: bool, + /// Whether the Dock is collapsible, default: true pub(super) collapsible: bool, - // Runtime state /// Whether the Dock is resizing - is_resizing: bool, + resizing: bool, } impl Dock { @@ -98,7 +98,7 @@ impl Dock { open: true, collapsible: true, size: px(200.0), - is_resizing: false, + resizing: false, } } @@ -231,54 +231,16 @@ impl Dock { cx: &mut Context, ) -> impl IntoElement { let axis = self.placement.axis(); - let neg_offset = -HANDLE_PADDING; let view = cx.entity().clone(); - div() - .id("resize-handle") - .occlude() - .absolute() - .flex_shrink_0() - .when(self.placement.is_left(), |this| { - // FIXME: Improve this to let the scroll bar have px(HANDLE_PADDING) - this.cursor_col_resize() - .top_0() - .right(px(1.)) - .h_full() - .w(HANDLE_SIZE) - .pt_12() - .pb_4() - }) - .when(self.placement.is_right(), |this| { - this.cursor_col_resize() - .top_0() - .left(px(-0.5)) - .h_full() - .w(HANDLE_SIZE) - .pt_12() - .pb_4() - }) - .when(self.placement.is_bottom(), |this| { - this.cursor_row_resize() - .top(neg_offset) - .left_0() - .w_full() - .h(HANDLE_SIZE) - .py(HANDLE_PADDING) - }) - .child( - div() - .rounded_full() - .hover(|this| this.bg(cx.theme().border_variant)) - .when(axis.is_horizontal(), |this| this.h_full().w(HANDLE_SIZE)) - .when(axis.is_vertical(), |this| this.w_full().h(HANDLE_SIZE)), - ) + resize_handle("resize-handle", axis) + .placement(self.placement) .on_drag(ResizePanel {}, move |info, _, _, cx| { cx.stop_propagation(); - view.update(cx, |view, _| { - view.is_resizing = true; + view.update(cx, |view, _cx| { + view.resizing = true; }); - cx.new(|_| info.clone()) + cx.new(|_| info.deref().clone()) }) } @@ -288,7 +250,7 @@ impl Dock { _window: &mut Window, cx: &mut Context, ) { - if !self.is_resizing { + if !self.resizing { return; } @@ -349,7 +311,7 @@ impl Dock { } fn done_resizing(&mut self, _window: &mut Window, _cx: &mut Context) { - self.is_resizing = false; + self.resizing = false; } } @@ -440,7 +402,7 @@ impl Element for DockElement { ) { window.on_mouse_event({ let view = self.view.clone(); - let is_resizing = view.read(cx).is_resizing; + let is_resizing = view.read(cx).resizing; move |e: &MouseMoveEvent, phase, window, cx| { if !is_resizing { return; diff --git a/crates/ui/src/dock_area/mod.rs b/crates/ui/src/dock_area/mod.rs index a2fb704..670882d 100644 --- a/crates/ui/src/dock_area/mod.rs +++ b/crates/ui/src/dock_area/mod.rs @@ -2,30 +2,23 @@ use std::sync::Arc; use gpui::prelude::FluentBuilder; use gpui::{ - actions, canvas, div, px, AnyElement, AnyView, App, AppContext, Axis, Bounds, Context, Edges, - Entity, EntityId, EventEmitter, Focusable, InteractiveElement as _, IntoElement, - ParentElement as _, Pixels, Render, SharedString, Styled, Subscription, WeakEntity, Window, + actions, div, px, AnyElement, AnyView, App, AppContext, Axis, Bounds, Context, Edges, Entity, + EntityId, EventEmitter, Focusable, InteractiveElement as _, IntoElement, ParentElement as _, + Pixels, Render, SharedString, Styled, Subscription, WeakEntity, Window, }; use crate::dock_area::dock::{Dock, DockPlacement}; use crate::dock_area::panel::{Panel, PanelEvent, PanelStyle, PanelView}; use crate::dock_area::stack_panel::StackPanel; use crate::dock_area::tab_panel::TabPanel; +use crate::ElementExt; pub mod dock; pub mod panel; pub mod stack_panel; pub mod tab_panel; -actions!( - dock, - [ - /// Zoom the current panel - ToggleZoom, - /// Close the current panel - ClosePanel - ] -); +actions!(dock, [ToggleZoom, ClosePanel]); pub enum DockEvent { /// The layout of the dock has changed, subscribers this to save the layout. @@ -38,20 +31,31 @@ pub enum DockEvent { /// The main area of the dock. pub struct DockArea { pub(crate) bounds: Bounds, + /// The center view of the dockarea. pub items: DockItem, - /// The entity_id of the [`TabPanel`](TabPanel) where each toggle button should be displayed, - toggle_button_panels: Edges>, + /// The left dock of the dock_area. left_dock: Option>, + /// The bottom dock of the dock_area. bottom_dock: Option>, + /// The right dock of the dock_area. right_dock: Option>, + + /// The entity_id of the [`TabPanel`](TabPanel) where each toggle button should be displayed, + toggle_button_panels: Edges>, + + /// Whether to show the toggle button. + toggle_button_visible: bool, + /// The top zoom view of the dock_area, if any. zoom_view: Option, + /// Lock panels layout, but allow to resize. is_locked: bool, + /// The panel style, default is [`PanelStyle::Default`](PanelStyle::Default). pub(crate) panel_style: PanelStyle, subscriptions: Vec, @@ -330,6 +334,7 @@ impl DockArea { items: dock_item, zoom_view: None, toggle_button_panels: Edges::default(), + toggle_button_visible: true, left_dock: None, right_dock: None, bottom_dock: None, @@ -344,7 +349,7 @@ impl DockArea { } /// Set the panel style of the dock area. - pub fn panel_style(mut self, style: PanelStyle) -> Self { + pub fn style(mut self, style: PanelStyle) -> Self { self.panel_style = style; self } @@ -649,31 +654,35 @@ impl DockArea { cx.subscribe_in( view, window, - move |_, panel, event, window, cx| match event { + move |_this, panel, event, window, cx| match event { PanelEvent::ZoomIn => { let panel = panel.clone(); cx.spawn_in(window, async move |view, window| { - _ = view.update_in(window, |view, window, cx| { + view.update_in(window, |view, window, cx| { view.set_zoomed_in(panel, window, cx); cx.notify(); - }); + }) + .ok(); }) .detach(); } - PanelEvent::ZoomOut => cx - .spawn_in(window, async move |view, window| { + PanelEvent::ZoomOut => { + cx.spawn_in(window, async move |view, window| { _ = view.update_in(window, |view, window, cx| { view.set_zoomed_out(window, cx); }); }) - .detach(), + .detach(); + } PanelEvent::LayoutChanged => { cx.spawn_in(window, async move |view, window| { - _ = view.update_in(window, |view, window, cx| { + view.update_in(window, |view, window, cx| { view.update_toggle_button_tab_panels(window, cx) - }); + }) + .ok(); }) .detach(); + // Emit layout changed event for dock cx.emit(DockEvent::LayoutChanged); } }, @@ -746,14 +755,7 @@ impl Render for DockArea { .relative() .size_full() .overflow_hidden() - .child( - canvas( - move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds), - |_, _, _, _| {}, - ) - .absolute() - .size_full(), - ) + .on_prepaint(move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds)) .map(|this| { if let Some(zoom_view) = self.zoom_view.clone() { this.child(zoom_view) diff --git a/crates/ui/src/dock_area/panel.rs b/crates/ui/src/dock_area/panel.rs index 1efe1de..a77d7a6 100644 --- a/crates/ui/src/dock_area/panel.rs +++ b/crates/ui/src/dock_area/panel.rs @@ -6,6 +6,7 @@ use gpui::{ use crate::button::Button; use crate::menu::PopupMenu; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PanelEvent { ZoomIn, ZoomOut, diff --git a/crates/ui/src/dock_area/stack_panel.rs b/crates/ui/src/dock_area/stack_panel.rs index 92fe47a..79ba4ef 100644 --- a/crates/ui/src/dock_area/stack_panel.rs +++ b/crates/ui/src/dock_area/stack_panel.rs @@ -7,13 +7,14 @@ use gpui::{ Window, }; use smallvec::SmallVec; +use theme::{ActiveTheme, CLIENT_SIDE_DECORATION_ROUNDING}; use super::{DockArea, PanelEvent}; use crate::dock_area::panel::{Panel, PanelView}; use crate::dock_area::tab_panel::TabPanel; use crate::resizable::{ - h_resizable, resizable_panel, v_resizable, ResizablePanel, ResizablePanelEvent, - ResizablePanelGroup, + resizable_panel, ResizablePanelEvent, ResizablePanelGroup, ResizablePanelState, ResizableState, + PANEL_MIN_SIZE, }; use crate::{h_flex, AxisExt as _, Placement}; @@ -22,9 +23,8 @@ pub struct StackPanel { pub(super) axis: Axis, focus_handle: FocusHandle, pub(crate) panels: SmallVec<[Arc; 2]>, - panel_group: Entity, - #[allow(dead_code)] - subscriptions: Vec, + state: Entity, + _subscriptions: Vec, } impl Panel for StackPanel { @@ -39,28 +39,23 @@ impl Panel for StackPanel { impl StackPanel { pub fn new(axis: Axis, window: &mut Window, cx: &mut Context) -> Self { - let panel_group = cx.new(|cx| { - if axis == Axis::Horizontal { - h_resizable(window, cx) - } else { - v_resizable(window, cx) - } - }); + let state = cx.new(|_| ResizableState::default()); // Bubble up the resize event. - let subscriptions = vec![cx.subscribe_in( - &panel_group, - window, - |_, _, _: &ResizablePanelEvent, _, cx| cx.emit(PanelEvent::LayoutChanged), - )]; + let subscriptions = + vec![ + cx.subscribe_in(&state, window, |_, _, _: &ResizablePanelEvent, _, cx| { + cx.emit(PanelEvent::LayoutChanged) + }), + ]; Self { axis, parent: None, focus_handle: cx.focus_handle(), panels: SmallVec::new(), - panel_group, - subscriptions, + state, + _subscriptions: subscriptions, } } @@ -70,7 +65,7 @@ impl StackPanel { } /// Return true if self or parent only have last panel. - pub(super) fn is_last_panel(&self, cx: &App) -> bool { + pub fn is_last_panel(&self, cx: &App) -> bool { if self.panels.len() > 1 { return false; } @@ -84,12 +79,12 @@ impl StackPanel { true } - pub(super) fn panels_len(&self) -> usize { + pub fn panels_len(&self) -> usize { self.panels.len() } /// Return the index of the panel. - pub(crate) fn index_of_panel(&self, panel: Arc) -> Option { + pub fn index_of_panel(&self, panel: Arc) -> Option { self.panels.iter().position(|p| p == &panel) } @@ -172,13 +167,6 @@ impl StackPanel { self.insert_panel(panel, ix + 1, size, dock_area, window, cx); } - fn new_resizable_panel(panel: Arc, size: Option) -> ResizablePanel { - resizable_panel() - .content_view(panel.view()) - .content_visible(move |cx| panel.visible(cx)) - .when_some(size, |this, size| this.size(size)) - } - fn insert_panel( &mut self, panel: Arc, @@ -225,14 +213,21 @@ impl StackPanel { ix }; + // Get avg size of all panels to insert new panel, if size is None. + let size = match size { + Some(size) => size, + None => { + let state = self.state.read(cx); + (state.container_size() / (state.sizes().len() + 1) as f32).max(PANEL_MIN_SIZE) + } + }; + + // Insert panel self.panels.insert(ix, panel.clone()); - self.panel_group.update(cx, |view, cx| { - view.insert_child( - Self::new_resizable_panel(panel.clone(), size), - ix, - window, - cx, - ) + + // Update resizable state + self.state.update(cx, |state, cx| { + state.insert_panel(Some(size), Some(ix), cx); }); cx.emit(PanelEvent::LayoutChanged); @@ -240,47 +235,47 @@ impl StackPanel { } /// Remove panel from the stack. + /// + /// If `ix` is not found, do nothing. pub fn remove_panel( &mut self, panel: Arc, window: &mut Window, cx: &mut Context, ) { - if let Some(ix) = self.index_of_panel(panel.clone()) { - self.panels.remove(ix); - self.panel_group.update(cx, |view, cx| { - view.remove_child(ix, window, cx); - }); + let Some(ix) = self.index_of_panel(panel.clone()) else { + return; + }; - cx.emit(PanelEvent::LayoutChanged); - self.remove_self_if_empty(window, cx); - } + self.panels.remove(ix); + self.state.update(cx, |state, cx| { + state.remove_panel(ix, cx); + }); + + cx.emit(PanelEvent::LayoutChanged); + + self.remove_self_if_empty(window, cx); } /// Replace the old panel with the new panel at same index. - pub(super) fn replace_panel( + pub fn replace_panel( &mut self, old_panel: Arc, new_panel: Entity, - window: &mut Window, + _window: &mut Window, cx: &mut Context, ) { if let Some(ix) = self.index_of_panel(old_panel.clone()) { self.panels[ix] = Arc::new(new_panel.clone()); - self.panel_group.update(cx, |view, cx| { - view.replace_child( - Self::new_resizable_panel(Arc::new(new_panel.clone()), None), - ix, - window, - cx, - ); + self.state.update(cx, |state, cx| { + state.replace_panel(ix, ResizablePanelState::default(), cx); }); cx.emit(PanelEvent::LayoutChanged); } } /// If children is empty, remove self from parent view. - pub(crate) fn remove_self_if_empty(&mut self, window: &mut Window, cx: &mut Context) { + pub fn remove_self_if_empty(&mut self, window: &mut Window, cx: &mut Context) { if self.is_root() { return; } @@ -301,11 +296,7 @@ impl StackPanel { } /// Find the first top left in the stack. - pub(super) fn left_top_tab_panel( - &self, - check_parent: bool, - cx: &App, - ) -> Option> { + pub fn left_top_tab_panel(&self, check_parent: bool, cx: &App) -> Option> { if check_parent { if let Some(parent) = self.parent.as_ref().and_then(|parent| parent.upgrade()) { if let Some(panel) = parent.read(cx).left_top_tab_panel(true, cx) { @@ -329,11 +320,7 @@ impl StackPanel { } /// Find the first top right in the stack. - pub(super) fn right_top_tab_panel( - &self, - check_parent: bool, - cx: &App, - ) -> Option> { + pub fn right_top_tab_panel(&self, check_parent: bool, cx: &App) -> Option> { if check_parent { if let Some(parent) = self.parent.as_ref().and_then(|parent| parent.upgrade()) { if let Some(panel) = parent.read(cx).right_top_tab_panel(true, cx) { @@ -362,17 +349,17 @@ impl StackPanel { } /// Remove all panels from the stack. - pub(super) fn remove_all_panels(&mut self, window: &mut Window, cx: &mut Context) { + pub fn remove_all_panels(&mut self, _: &mut Window, cx: &mut Context) { self.panels.clear(); - self.panel_group - .update(cx, |view, cx| view.remove_all_children(window, cx)); + self.state.update(cx, |state, cx| { + state.clear(); + cx.notify(); + }); } /// Change the axis of the stack panel. - pub(super) fn set_axis(&mut self, axis: Axis, window: &mut Window, cx: &mut Context) { + pub fn set_axis(&mut self, axis: Axis, _: &mut Window, cx: &mut Context) { self.axis = axis; - self.panel_group - .update(cx, |view, cx| view.set_axis(axis, window, cx)); cx.notify(); } } @@ -388,10 +375,23 @@ impl EventEmitter for StackPanel {} impl EventEmitter for StackPanel {} impl Render for StackPanel { - fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { h_flex() .size_full() .overflow_hidden() - .child(self.panel_group.clone()) + .bg(cx.theme().panel_background) + .when(cx.theme().platform.is_linux(), |this| { + this.rounded_br(CLIENT_SIDE_DECORATION_ROUNDING) + }) + .child( + ResizablePanelGroup::new("stack-panel-group") + .with_state(&self.state) + .axis(self.axis) + .children(self.panels.clone().into_iter().map(|panel| { + resizable_panel() + .child(panel.view()) + .visible(panel.visible(cx)) + })), + ) } } diff --git a/crates/ui/src/dock_area/tab_panel.rs b/crates/ui/src/dock_area/tab_panel.rs index ee67cb7..0a2b69f 100644 --- a/crates/ui/src/dock_area/tab_panel.rs +++ b/crates/ui/src/dock_area/tab_panel.rs @@ -7,13 +7,13 @@ use gpui::{ MouseButton, ParentElement, Pixels, Render, ScrollHandle, SharedString, StatefulInteractiveElement, Styled, WeakEntity, Window, }; -use theme::ActiveTheme; +use theme::{ActiveTheme, CLIENT_SIDE_DECORATION_ROUNDING, TITLEBAR_HEIGHT}; -use super::panel::PanelView; -use super::stack_panel::StackPanel; -use super::{ClosePanel, DockArea, PanelEvent, PanelStyle, ToggleZoom}; use crate::button::{Button, ButtonVariants as _}; -use crate::dock_area::panel::Panel; +use crate::dock_area::dock::DockPlacement; +use crate::dock_area::panel::{Panel, PanelView}; +use crate::dock_area::stack_panel::StackPanel; +use crate::dock_area::{ClosePanel, DockArea, PanelEvent, PanelStyle, ToggleZoom}; use crate::menu::{DropdownMenu, PopupMenu}; use crate::tab::tab_bar::TabBar; use crate::tab::Tab; @@ -65,16 +65,29 @@ impl Render for DragPanel { pub struct TabPanel { focus_handle: FocusHandle, dock_area: WeakEntity, - /// The stock_panel can be None, if is None, that means the panels can't be split or move - stack_panel: Option>, + + /// List of panels in the tab panel pub(crate) panels: Vec>, + + /// Current active panel index pub(crate) active_ix: usize, + /// If this is true, the Panel closeable will follow the active panel's closeable, /// otherwise this TabPanel will not able to close pub(crate) closable: bool, + + /// The stock_panel can be None, if is None, that means the panels can't be split or move + stack_panel: Option>, + + /// Scroll handle for the tab bar tab_bar_scroll_handle: ScrollHandle, - is_zoomed: bool, - is_collapsed: bool, + + /// Whether the tab panel is zoomeds + zoomed: bool, + + /// Whether the tab panel is collapsed + collapsed: bool, + /// When drag move, will get the placement of the panel to be split will_split_placement: Option, } @@ -142,8 +155,8 @@ impl TabPanel { active_ix: 0, tab_bar_scroll_handle: ScrollHandle::new(), will_split_placement: None, - is_zoomed: false, - is_collapsed: false, + zoomed: false, + collapsed: false, closable: true, } } @@ -339,7 +352,7 @@ impl TabPanel { _window: &mut Window, cx: &mut Context, ) { - self.is_collapsed = collapsed; + self.collapsed = collapsed; cx.notify(); } @@ -352,7 +365,7 @@ impl TabPanel { return true; } - if self.is_zoomed { + if self.zoomed { return true; } @@ -408,7 +421,7 @@ impl TabPanel { window: &mut Window, cx: &mut Context, ) -> impl IntoElement { - let is_zoomed = self.is_zoomed && state.zoomable; + let is_zoomed = self.zoomed && state.zoomable; let view = cx.entity().clone(); let build_popup_menu = move |this, cx: &App| view.read(cx).popup_menu(this, cx); let toolbar = self.toolbar_buttons(window, cx); @@ -420,7 +433,7 @@ impl TabPanel { .occlude() .rounded_full() .children(toolbar.into_iter().map(|btn| btn.small().ghost().rounded())) - .when(self.is_zoomed, |this| { + .when(self.zoomed, |this| { this.child( Button::new("zoom") .icon(IconName::Zoom) @@ -433,8 +446,7 @@ impl TabPanel { ) }) .when(has_toolbar, |this| { - this.bg(cx.theme().surface_background) - .child(div().flex_shrink_0().h_4().w_px().bg(cx.theme().border)) + this.child(div().flex_shrink_0().h_4().w_px().bg(cx.theme().border)) }) .child( Button::new("menu") @@ -461,21 +473,113 @@ impl TabPanel { ) } + fn render_dock_toggle_button( + &self, + placement: DockPlacement, + _window: &mut Window, + cx: &mut Context, + ) -> Option