From 407db7a7d5170f5557aa8b66aaa6068e0c3d70e8 Mon Sep 17 00:00:00 2001 From: reya Date: Wed, 18 Dec 2024 07:54:40 +0700 Subject: [PATCH] feat: add tiles from gpui-components --- assets/icons/resize-corner.svg | 9 + crates/app/src/views/dock/chat/mod.rs | 6 +- crates/app/src/views/dock/left_dock.rs | 6 +- crates/app/src/views/dock/welcome.rs | 6 +- crates/ui/src/dock/dock.rs | 17 +- crates/ui/src/dock/invalid_panel.rs | 11 +- crates/ui/src/dock/mod.rs | 137 +++-- crates/ui/src/dock/panel.rs | 31 +- crates/ui/src/dock/stack_panel.rs | 23 +- crates/ui/src/dock/state.rs | 77 ++- crates/ui/src/dock/tab_panel.rs | 21 +- crates/ui/src/dock/tiles.rs | 708 +++++++++++++++++++++++++ crates/ui/src/icon.rs | 148 +++--- 13 files changed, 1018 insertions(+), 182 deletions(-) create mode 100644 assets/icons/resize-corner.svg create mode 100644 crates/ui/src/dock/tiles.rs diff --git a/assets/icons/resize-corner.svg b/assets/icons/resize-corner.svg new file mode 100644 index 0000000..31065be --- /dev/null +++ b/assets/icons/resize-corner.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/crates/app/src/views/dock/chat/mod.rs b/crates/app/src/views/dock/chat/mod.rs index e7addd8..e336c75 100644 --- a/crates/app/src/views/dock/chat/mod.rs +++ b/crates/app/src/views/dock/chat/mod.rs @@ -1,6 +1,6 @@ use coop_ui::{ button::Button, - dock::{DockItemState, Panel, PanelEvent, TitleStyle}, + dock::{Panel, PanelEvent, PanelState, TitleStyle}, popup_menu::PopupMenu, }; use form::Form; @@ -75,8 +75,8 @@ impl Panel for ChatPanel { vec![] } - fn dump(&self, _cx: &AppContext) -> DockItemState { - DockItemState::new(self) + fn dump(&self, _cx: &AppContext) -> PanelState { + PanelState::new(self) } } diff --git a/crates/app/src/views/dock/left_dock.rs b/crates/app/src/views/dock/left_dock.rs index f722a6d..0a08d9c 100644 --- a/crates/app/src/views/dock/left_dock.rs +++ b/crates/app/src/views/dock/left_dock.rs @@ -1,6 +1,6 @@ use coop_ui::{ button::Button, - dock::{DockItemState, Panel, PanelEvent, TitleStyle}, + dock::{Panel, PanelEvent, PanelState, TitleStyle}, popup_menu::PopupMenu, scroll::ScrollbarAxis, StyledExt, @@ -68,8 +68,8 @@ impl Panel for LeftDock { vec![] } - fn dump(&self, _cx: &AppContext) -> DockItemState { - DockItemState::new(self) + fn dump(&self, _cx: &AppContext) -> PanelState { + PanelState::new(self) } } diff --git a/crates/app/src/views/dock/welcome.rs b/crates/app/src/views/dock/welcome.rs index fce2478..d7a0077 100644 --- a/crates/app/src/views/dock/welcome.rs +++ b/crates/app/src/views/dock/welcome.rs @@ -1,6 +1,6 @@ use coop_ui::{ button::Button, - dock::{DockItemState, Panel, PanelEvent, TitleStyle}, + dock::{Panel, PanelEvent, PanelState, TitleStyle}, popup_menu::PopupMenu, theme::{ActiveTheme, Colorize}, StyledExt, @@ -58,8 +58,8 @@ impl Panel for WelcomePanel { vec![] } - fn dump(&self, _cx: &AppContext) -> DockItemState { - DockItemState::new(self) + fn dump(&self, _cx: &AppContext) -> PanelState { + PanelState::new(self) } } diff --git a/crates/ui/src/dock/dock.rs b/crates/ui/src/dock/dock.rs index 98f7637..7325aaf 100644 --- a/crates/ui/src/dock/dock.rs +++ b/crates/ui/src/dock/dock.rs @@ -1,7 +1,3 @@ -//! 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, Entity, InteractiveElement as _, IntoElement, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Render, @@ -9,6 +5,7 @@ use gpui::{ WeakView, WindowContext, }; use serde::{Deserialize, Serialize}; +use std::sync::Arc; use crate::{ resizable::{HANDLE_PADDING, HANDLE_SIZE, PANEL_MIN_SIZE}, @@ -193,6 +190,16 @@ impl Dock { } }); } + DockItem::Tiles { view, .. } => { + cx.defer({ + let view = view.clone(); + move |cx| { + _ = dock_area.update(cx, |this, cx| { + this.subscribe_panel(&view, cx); + }); + } + }); + } DockItem::Panel { .. } => { // Not supported } @@ -376,6 +383,8 @@ impl Render for Dock { DockItem::Split { view, .. } => this.child(view.clone()), DockItem::Tabs { view, .. } => this.child(view.clone()), DockItem::Panel { view, .. } => this.child(view.clone().view()), + // Not support to render Tiles and Tile into Dock + DockItem::Tiles { .. } => this, }) .child(self.render_resize_handle(cx)) .child(DockElement { diff --git a/crates/ui/src/dock/invalid_panel.rs b/crates/ui/src/dock/invalid_panel.rs index fbdb1d6..7e514e3 100644 --- a/crates/ui/src/dock/invalid_panel.rs +++ b/crates/ui/src/dock/invalid_panel.rs @@ -3,18 +3,17 @@ use gpui::{ Styled as _, WindowContext, }; -use crate::theme::ActiveTheme as _; - -use super::{DockItemState, Panel, PanelEvent}; +use super::{Panel, PanelEvent, PanelState}; +use crate::theme::ActiveTheme; pub(crate) struct InvalidPanel { name: SharedString, focus_handle: FocusHandle, - old_state: DockItemState, + old_state: PanelState, } impl InvalidPanel { - pub(crate) fn new(name: &str, state: DockItemState, cx: &mut WindowContext) -> Self { + pub(crate) fn new(name: &str, state: PanelState, cx: &mut WindowContext) -> Self { Self { focus_handle: cx.focus_handle(), name: SharedString::from(name.to_owned()), @@ -27,7 +26,7 @@ impl Panel for InvalidPanel { "InvalidPanel" } - fn dump(&self, _cx: &AppContext) -> super::DockItemState { + fn dump(&self, _cx: &AppContext) -> super::PanelState { self.old_state.clone() } } diff --git a/crates/ui/src/dock/mod.rs b/crates/ui/src/dock/mod.rs index 29659dd..1d1a0b2 100644 --- a/crates/ui/src/dock/mod.rs +++ b/crates/ui/src/dock/mod.rs @@ -5,9 +5,9 @@ mod panel; mod stack_panel; mod state; mod tab_panel; +mod tiles; use anyhow::Result; - use gpui::{ actions, canvas, div, prelude::FluentBuilder, AnyElement, AnyView, AppContext, Axis, Bounds, Edges, Entity as _, EntityId, EventEmitter, InteractiveElement as _, IntoElement, @@ -21,6 +21,7 @@ pub use panel::*; pub use stack_panel::*; pub use state::*; pub use tab_panel::*; +pub use tiles::*; use crate::theme::ActiveTheme; @@ -74,7 +75,7 @@ pub struct DockArea { pub enum DockItem { /// Split layout Split { - axis: gpui::Axis, + axis: Axis, items: Vec, sizes: Vec>, view: View, @@ -87,6 +88,11 @@ pub enum DockItem { }, /// Panel layout Panel { view: Arc }, + /// Tiles layout + Tiles { + items: Vec, + view: View, + }, } impl DockItem { @@ -153,6 +159,51 @@ impl DockItem { Self::Panel { view: panel } } + /// Create DockItem with tiles layout + /// + /// This items and metas should have the same length. + pub fn tiles( + items: Vec, + metas: Vec + Copy>, + dock_area: &WeakView, + cx: &mut WindowContext, + ) -> Self { + assert!(items.len() == metas.len()); + + let tile_panel = cx.new_view(|cx| { + let mut tiles = Tiles::new(cx); + for (ix, item) in items.clone().into_iter().enumerate() { + match item { + DockItem::Tabs { view, .. } => { + let meta: TileMeta = metas[ix].into(); + let tile_item = + TileItem::new(Arc::new(view), meta.bounds).z_index(meta.z_index); + tiles.add_item(tile_item, dock_area, cx); + } + _ => { + // Ignore non-tabs items + } + } + } + tiles + }); + + cx.defer({ + let tile_panel = tile_panel.clone(); + let dock_area = dock_area.clone(); + move |cx| { + _ = dock_area.update(cx, |this, cx| { + this.subscribe_panel(&tile_panel, cx); + }); + } + }); + + Self::Tiles { + items: tile_panel.read(cx).panels.clone(), + view: tile_panel, + } + } + /// Create DockItem with tabs layout, items are displayed as tabs. /// /// The `active_ix` is the index of the active tab, if `None` the first tab is active. @@ -201,10 +252,11 @@ impl DockItem { } /// Returns the views of the dock item. - fn view(&self) -> Arc { + pub fn view(&self) -> Arc { match self { Self::Split { view, .. } => Arc::new(view.clone()), Self::Tabs { view, .. } => Arc::new(view.clone()), + Self::Tiles { view, .. } => Arc::new(view.clone()), Self::Panel { view, .. } => view.clone(), } } @@ -217,6 +269,14 @@ impl DockItem { } Self::Tabs { items, .. } => items.iter().find(|item| *item == &panel).cloned(), Self::Panel { view } => Some(view.clone()), + Self::Tiles { items, .. } => items.iter().find_map(|item| { + #[allow(clippy::op_ref)] + if &item.panel == &panel { + Some(item.panel.clone()) + } else { + None + } + }), } } @@ -252,6 +312,7 @@ impl DockItem { stack_panel.add_panel(new_item.view(), None, dock_area.clone(), cx); }); } + Self::Tiles { .. } => {} Self::Panel { .. } => {} } } @@ -269,6 +330,7 @@ impl DockItem { item.set_collapsed(collapsed, cx); } } + DockItem::Tiles { .. } => {} DockItem::Panel { .. } => {} } } @@ -278,6 +340,7 @@ impl DockItem { match self { DockItem::Tabs { view, .. } => Some(view.clone()), DockItem::Split { view, .. } => view.read(cx).left_top_tab_panel(true, cx), + DockItem::Tiles { .. } => None, DockItem::Panel { .. } => None, } } @@ -287,6 +350,7 @@ impl DockItem { match self { DockItem::Tabs { view, .. } => Some(view.clone()), DockItem::Split { view, .. } => view.read(cx).right_top_tab_panel(true, cx), + DockItem::Tiles { .. } => None, DockItem::Panel { .. } => None, } } @@ -601,7 +665,7 @@ impl DockArea { Ok(()) } - /// Dump the dock panels layout to DockItemState. + /// Dump the dock panels layout to PanelState. /// /// See also [DockArea::load]. pub fn dump(&self, cx: &AppContext) -> DockAreaState { @@ -658,6 +722,9 @@ impl DockArea { DockItem::Tabs { .. } => { // We subscribe to the tab panel event in StackPanel's insert_panel } + DockItem::Tiles { .. } => { + // We subscribe to the tab panel event in Tiles's [`add_item`](Tiles::add_item) + } DockItem::Panel { .. } => { // Not supported } @@ -727,6 +794,7 @@ impl DockArea { match &self.items { DockItem::Split { view, .. } => view.clone().into_any_element(), DockItem::Tabs { view, .. } => view.clone().into_any_element(), + DockItem::Tiles { view, .. } => view.clone().into_any_element(), DockItem::Panel { view, .. } => view.clone().view().into_any_element(), } } @@ -774,41 +842,50 @@ impl Render for DockArea { if let Some(zoom_view) = self.zoom_view.clone() { this.child(zoom_view) } else { - this.child( - div() - .flex() - .flex_row() - .h_full() - // Left dock - .when_some(self.left_dock.clone(), |this, dock| { - this.child(div().flex().flex_none().child(dock)) - .bg(cx.theme().sidebar) - .text_color(cx.theme().sidebar_foreground) - }) - // Center - .child( + match &self.items { + DockItem::Tiles { view, .. } => { + // render tiles + this.child(view.clone()) + } + _ => { + // render dock + this.child( div() .flex() - .flex_1() - .flex_col() - .overflow_hidden() - // Top center + .flex_row() + .h_full() + // Left dock + .when_some(self.left_dock.clone(), |this, dock| { + this.bg(cx.theme().sidebar) + .text_color(cx.theme().sidebar_foreground) + .child(div().flex().flex_none().child(dock)) + }) + // Center .child( div() + .flex() .flex_1() + .flex_col() .overflow_hidden() - .child(self.render_items(cx)), + // Top center + .child( + div() + .flex_1() + .overflow_hidden() + .child(self.render_items(cx)), + ) + // Bottom Dock + .when_some(self.bottom_dock.clone(), |this, dock| { + this.child(dock) + }), ) - // Bottom Dock - .when_some(self.bottom_dock.clone(), |this, dock| { - this.child(dock) + // Right Dock + .when_some(self.right_dock.clone(), |this, dock| { + this.child(div().flex().flex_none().child(dock)) }), ) - // Right Dock - .when_some(self.right_dock.clone(), |this, dock| { - this.child(div().flex().flex_none().child(dock)) - }), - ) + } + } } }) } diff --git a/crates/ui/src/dock/panel.rs b/crates/ui/src/dock/panel.rs index 50dd626..f51d54a 100644 --- a/crates/ui/src/dock/panel.rs +++ b/crates/ui/src/dock/panel.rs @@ -4,7 +4,7 @@ use gpui::{ }; use std::{collections::HashMap, sync::Arc}; -use super::{DockArea, DockItemInfo, DockItemState}; +use super::{DockArea, PanelInfo, PanelState}; use crate::{button::Button, popup_menu::PopupMenu}; pub enum PanelEvent { @@ -36,7 +36,7 @@ pub trait Panel: EventEmitter + FocusableView { /// The title of the panel fn title(&self, _cx: &WindowContext) -> AnyElement { - SharedString::from("Unnamed").into_any_element() + SharedString::from("Untitled").into_any_element() } /// The theme of the panel title, default is `None`. @@ -65,13 +65,13 @@ pub trait Panel: EventEmitter + FocusableView { } /// Dump the panel, used to serialize the panel. - fn dump(&self, _cx: &AppContext) -> DockItemState { - DockItemState::new(self) + fn dump(&self, _cx: &AppContext) -> PanelState { + PanelState::new(self) } } pub trait PanelView: 'static + Send + Sync { - fn panel_name(&self, _cx: &WindowContext) -> &'static str; + fn panel_name(&self, _cx: &AppContext) -> &'static str; fn title(&self, _cx: &WindowContext) -> AnyElement; fn title_style(&self, _cx: &WindowContext) -> Option; fn closeable(&self, cx: &WindowContext) -> bool; @@ -80,11 +80,11 @@ pub trait PanelView: 'static + Send + Sync { fn toolbar_buttons(&self, cx: &WindowContext) -> Vec