diff --git a/Cargo.lock b/Cargo.lock index aef8f7d..ac9d52c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3059,7 +3059,7 @@ dependencies = [ [[package]] name = "gpui-native-core" version = "0.1.0" -source = "git+https://git.reya.info/reya/gpui-native-kit?rev=9678a7676f4c70f91f04b6ab0ad68148659c2558#9678a7676f4c70f91f04b6ab0ad68148659c2558" +source = "git+https://git.reya.info/reya/gpui-native-kit?rev=6667c812597bd2adf415d29a4e50355bb3bf06ee#6667c812597bd2adf415d29a4e50355bb3bf06ee" dependencies = [ "cpal", "flume 0.12.0", @@ -3074,7 +3074,7 @@ dependencies = [ [[package]] name = "gpui-native-data" version = "0.1.0" -source = "git+https://git.reya.info/reya/gpui-native-kit?rev=9678a7676f4c70f91f04b6ab0ad68148659c2558#9678a7676f4c70f91f04b6ab0ad68148659c2558" +source = "git+https://git.reya.info/reya/gpui-native-kit?rev=6667c812597bd2adf415d29a4e50355bb3bf06ee#6667c812597bd2adf415d29a4e50355bb3bf06ee" dependencies = [ "gpui", "gpui-native-core", @@ -3086,7 +3086,7 @@ dependencies = [ [[package]] name = "gpui-native-editor" version = "0.1.0" -source = "git+https://git.reya.info/reya/gpui-native-kit?rev=9678a7676f4c70f91f04b6ab0ad68148659c2558#9678a7676f4c70f91f04b6ab0ad68148659c2558" +source = "git+https://git.reya.info/reya/gpui-native-kit?rev=6667c812597bd2adf415d29a4e50355bb3bf06ee#6667c812597bd2adf415d29a4e50355bb3bf06ee" dependencies = [ "gpui", "gpui-native-core", @@ -3105,7 +3105,7 @@ dependencies = [ [[package]] name = "gpui-native-icons" version = "0.1.0" -source = "git+https://git.reya.info/reya/gpui-native-kit?rev=9678a7676f4c70f91f04b6ab0ad68148659c2558#9678a7676f4c70f91f04b6ab0ad68148659c2558" +source = "git+https://git.reya.info/reya/gpui-native-kit?rev=6667c812597bd2adf415d29a4e50355bb3bf06ee#6667c812597bd2adf415d29a4e50355bb3bf06ee" dependencies = [ "anyhow", "gpui", @@ -3114,7 +3114,7 @@ dependencies = [ [[package]] name = "gpui-native-kit" version = "0.1.0" -source = "git+https://git.reya.info/reya/gpui-native-kit?rev=9678a7676f4c70f91f04b6ab0ad68148659c2558#9678a7676f4c70f91f04b6ab0ad68148659c2558" +source = "git+https://git.reya.info/reya/gpui-native-kit?rev=6667c812597bd2adf415d29a4e50355bb3bf06ee#6667c812597bd2adf415d29a4e50355bb3bf06ee" dependencies = [ "gpui", "gpui-native-core", @@ -3128,7 +3128,7 @@ dependencies = [ [[package]] name = "gpui-native-layout" version = "0.1.0" -source = "git+https://git.reya.info/reya/gpui-native-kit?rev=9678a7676f4c70f91f04b6ab0ad68148659c2558#9678a7676f4c70f91f04b6ab0ad68148659c2558" +source = "git+https://git.reya.info/reya/gpui-native-kit?rev=6667c812597bd2adf415d29a4e50355bb3bf06ee#6667c812597bd2adf415d29a4e50355bb3bf06ee" dependencies = [ "gpui", "gpui-native-core", @@ -3141,7 +3141,7 @@ dependencies = [ [[package]] name = "gpui-native-theme" version = "0.1.0" -source = "git+https://git.reya.info/reya/gpui-native-kit?rev=9678a7676f4c70f91f04b6ab0ad68148659c2558#9678a7676f4c70f91f04b6ab0ad68148659c2558" +source = "git+https://git.reya.info/reya/gpui-native-kit?rev=6667c812597bd2adf415d29a4e50355bb3bf06ee#6667c812597bd2adf415d29a4e50355bb3bf06ee" dependencies = [ "anyhow", "async-channel", @@ -3162,7 +3162,7 @@ dependencies = [ [[package]] name = "gpui-native-utils" version = "0.1.0" -source = "git+https://git.reya.info/reya/gpui-native-kit?rev=9678a7676f4c70f91f04b6ab0ad68148659c2558#9678a7676f4c70f91f04b6ab0ad68148659c2558" +source = "git+https://git.reya.info/reya/gpui-native-kit?rev=6667c812597bd2adf415d29a4e50355bb3bf06ee#6667c812597bd2adf415d29a4e50355bb3bf06ee" dependencies = [ "gpui", "gpui-native-theme", diff --git a/Cargo.toml b/Cargo.toml index 6c638ba..93cb783 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,6 @@ resolver = "2" members = ["crates/*", "desktop"] default-members = ["desktop"] -exclude = ["crates/dock", "crates/signed_ui", "crates/workspace"] [workspace.package] version = "0.2.0" @@ -14,7 +13,7 @@ gpui = { git = "https://github.com/zed-industries/zed", rev = "f8cfc7ed9e57503de gpui_platform = { git = "https://github.com/zed-industries/zed", rev = "f8cfc7ed9e57503def9479138894f3139f5400cc", features = ["font-kit"] } reqwest_client = { git = "https://github.com/zed-industries/zed", rev = "f8cfc7ed9e57503def9479138894f3139f5400cc" } -gpui-native-kit = { git = "https://git.reya.info/reya/gpui-native-kit", rev = "9678a7676f4c70f91f04b6ab0ad68148659c2558", features = ["layout", "editor"] } +gpui-native-kit = { git = "https://git.reya.info/reya/gpui-native-kit", rev = "6667c812597bd2adf415d29a4e50355bb3bf06ee", features = ["layout", "editor"] } settings = { path = "crates/settings" } signed_app = { path = "crates/signed_app" } diff --git a/crates/dock/Cargo.toml b/crates/dock/Cargo.toml deleted file mode 100644 index b1afc86..0000000 --- a/crates/dock/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "dock" -description = "The Signed dock skin over gpui-component's upstream dock (gpui_base::dock engine + renderer traits)." -version.workspace = true -edition.workspace = true -publish.workspace = true - -[dependencies] -gpui.workspace = true -gpui-component.workspace = true -gpui-base.workspace = true -settings = { path = "../settings" } -signed_ui = { path = "../signed_ui" } - -[dev-dependencies] -gpui = { workspace = true, features = ["test-support"] } diff --git a/crates/dock/src/dock_area.rs b/crates/dock/src/dock_area.rs deleted file mode 100644 index b3c3176..0000000 --- a/crates/dock/src/dock_area.rs +++ /dev/null @@ -1,276 +0,0 @@ -use std::cell::Cell; -use std::ops::Deref as _; -use std::rc::Rc; -use std::sync::Arc; - -use gpui::prelude::FluentBuilder as _; -use gpui::{ - AnyElement, App, AppContext as _, Axis, Context, Div, Element, Empty, InteractiveElement as _, - IntoElement, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Render, Stateful, Style, - Styled as _, WeakEntity, Window, div, px, -}; -use gpui_base::dock::{ - DockArea, DockAreaRenderer, DockContext, DockEvent, DockPlacement, NodeId, PanelState, - PanelView, TabGroupRenderer, -}; -use gpui_base::{HandleEdge, resize_handle}; -use gpui_component::ActiveTheme as _; - -use crate::invalid_panel::InvalidPanel; -use crate::panel_handle; -use crate::tab_panel::SignedTabGroupSkin; - -/// State the skin shares with its per-container renderers. -pub(crate) struct SkinShared { - area: WeakEntity, - toggle_button_visible: Cell, - /// The dock whose resize handle is being dragged, if any. Only one can be. - resizing_dock: Cell>, -} - -impl SkinShared { - pub(crate) fn area(&self) -> &WeakEntity { - &self.area - } - - pub(crate) fn is_toggle_button_visible(&self) -> bool { - self.toggle_button_visible.get() - } - - pub(crate) fn resizing_dock(&self) -> &Cell> { - &self.resizing_dock - } -} - -/// The Signed appearance for a [`DockArea`]. -/// Install it in the constructor, the only place the area's weak handle is available. -/// -/// ```ignore -/// let dock = cx.new(|cx| { -/// let skin = SignedDockSkin::new(cx); -/// DockArea::new("dock", Some(1), window, cx).with_renderer(skin) -/// }); -/// ``` -pub struct SignedDockSkin { - shared: Rc, -} - -impl SignedDockSkin { - pub fn new(cx: &mut Context) -> Rc { - Rc::new(Self { - shared: Rc::new(SkinShared { - area: cx.weak_entity(), - toggle_button_visible: Cell::new(true), - resizing_dock: Cell::new(None), - }), - }) - } - - pub(crate) fn shared(&self) -> &Rc { - &self.shared - } -} - -/// Payload a dock's resize handle drags. -/// -/// It draws nothing, the handle element is the visible affordance. -#[derive(Clone)] -struct ResizePanel; - -impl Render for ResizePanel { - fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { - Empty - } -} - -impl DockAreaRenderer for SignedDockSkin { - fn frame(&self, _: &mut Window, _: &mut App) -> Stateful
{ - div() - .id("dock-area") - .relative() - .size_full() - .overflow_hidden() - .flex() - .flex_row() - } - - fn center_frame(&self, _: &mut Window, _: &mut App) -> Stateful
{ - div() - .id("dock-area-center") - .flex() - .flex_1() - .flex_col() - .overflow_hidden() - } - - fn split_frame(&self, node: NodeId, _: Axis, _: &mut Window, cx: &mut App) -> Stateful
{ - // `size_full` and `flex_1` stop the frame collapsing in an unsizing parent. - div() - .id(("dock-split-frame", node.as_u64())) - .size_full() - .flex_1() - .min_h(px(0.)) - .overflow_hidden() - .bg(cx.theme().tokens.tab_bar) - } - - fn render_dock( - &self, - dock: &DockContext, - content: AnyElement, - window: &mut Window, - cx: &mut App, - ) -> AnyElement { - div() - .flex() - .size_full() - .relative() - .child(content) - .child(self.render_resize_handle(dock, window, cx)) - .child(DockResizeTracker { - dock: dock.clone(), - shared: self.shared().clone(), - }) - .into_any_element() - } - - /// Placeholder for a panel this build cannot construct. - /// It dumps the state it was handed, so the layout survives a load and save. - fn build_placeholder( - &self, - state: &PanelState, - _: &mut Window, - cx: &mut App, - ) -> Option> { - let state = state.clone(); - Some(panel_handle(cx.new(|cx| { - InvalidPanel::new(state.panel_name.clone(), state, cx) - }))) - } - - fn tab_group_renderer(&self) -> Rc { - Rc::new(SignedTabGroupSkin::new(self.shared().clone())) - } -} - -impl SignedDockSkin { - fn render_resize_handle( - &self, - dock: &DockContext, - _: &mut Window, - _: &mut App, - ) -> impl IntoElement { - let placement = dock.placement(); - let shared = self.shared().clone(); - - resize_handle("resize-handle", placement.axis()) - .when(placement.is_left(), |this| { - this.inside(HandleEdge::Trailing) - }) - .when(placement.is_right() || placement.is_bottom(), |this| { - this.inside(HandleEdge::Leading) - }) - .on_drag(ResizePanel, move |info, _, _, cx| { - cx.stop_propagation(); - shared.resizing_dock().set(Some(placement)); - cx.new(|_| info.deref().clone()) - }) - } -} - -/// Turns the window's mouse stream into dock resizing. -/// It draws nothing, the `paint` hook is the only window listener registration point. -struct DockResizeTracker { - dock: DockContext, - shared: Rc, -} - -impl IntoElement for DockResizeTracker { - type Element = Self; - - fn into_element(self) -> Self::Element { - self - } -} - -impl Element for DockResizeTracker { - type PrepaintState = (); - type RequestLayoutState = (); - - fn id(&self) -> Option { - None - } - - fn source_location(&self) -> Option<&'static std::panic::Location<'static>> { - None - } - - fn request_layout( - &mut self, - _: Option<&gpui::GlobalElementId>, - _: Option<&gpui::InspectorElementId>, - window: &mut Window, - cx: &mut App, - ) -> (gpui::LayoutId, Self::RequestLayoutState) { - (window.request_layout(Style::default(), None, cx), ()) - } - - fn prepaint( - &mut self, - _: Option<&gpui::GlobalElementId>, - _: Option<&gpui::InspectorElementId>, - _: gpui::Bounds, - _: &mut Self::RequestLayoutState, - _: &mut Window, - _: &mut App, - ) -> Self::PrepaintState { - } - - fn paint( - &mut self, - _: Option<&gpui::GlobalElementId>, - _: Option<&gpui::InspectorElementId>, - _: gpui::Bounds, - _: &mut Self::RequestLayoutState, - _: &mut Self::PrepaintState, - window: &mut Window, - _: &mut App, - ) { - let placement = self.dock.placement(); - - window.on_mouse_event({ - let dock = self.dock.clone(); - let shared = self.shared.clone(); - move |event: &MouseMoveEvent, phase, window, cx| { - if !phase.bubble() || shared.resizing_dock().get() != Some(placement) { - return; - } - // Dragging a closed dock's handle reopens it. - // Read the live state, the snapshot in `dock` would toggle it shut again. - let open = shared - .area() - .upgrade() - .is_some_and(|area| area.read(cx).is_dock_open(placement)); - if !open { - dock.toggle(window, cx); - } - dock.resize_to(event.position, window, cx); - } - }); - - window.on_mouse_event({ - let shared = self.shared.clone(); - move |_: &MouseUpEvent, phase, _, cx| { - if !phase.bubble() || shared.resizing_dock().get() != Some(placement) { - return; - } - shared.resizing_dock().set(None); - // The size lives on the dock, not the layout tree. - // Nothing else tells a subscriber to persist it. - _ = shared - .area() - .update(cx, |_, cx| cx.emit(DockEvent::LayoutChanged)); - } - }); - } -} diff --git a/crates/dock/src/invalid_panel.rs b/crates/dock/src/invalid_panel.rs deleted file mode 100644 index e927315..0000000 --- a/crates/dock/src/invalid_panel.rs +++ /dev/null @@ -1,67 +0,0 @@ -use gpui::{ - App, Context, EventEmitter, FocusHandle, Focusable, IntoElement, ParentElement as _, Render, - SharedString, Styled as _, Window, div, -}; -use gpui_base::dock::{PanelEvent, PanelState}; -use gpui_component::ActiveTheme as _; - -use crate::Panel; - -/// Stands in for a panel this build cannot construct. -/// It returns the state it was handed, so the layout survives a load and save. -pub(crate) struct InvalidPanel { - name: SharedString, - focus_handle: FocusHandle, - old_state: PanelState, -} - -impl InvalidPanel { - pub(crate) fn new( - name: impl Into, - state: PanelState, - cx: &mut Context, - ) -> Self { - Self { - focus_handle: cx.focus_handle(), - name: name.into(), - old_state: state, - } - } -} - -impl gpui_base::dock::Panel for InvalidPanel { - fn panel_name(&self) -> &'static str { - "InvalidPanel" - } - - fn dump(&self, _: &App) -> PanelState { - self.old_state.clone() - } -} - -impl Panel for InvalidPanel {} - -impl EventEmitter for InvalidPanel {} - -impl Focusable for InvalidPanel { - fn focus_handle(&self, _: &App) -> FocusHandle { - self.focus_handle.clone() - } -} - -impl Render for InvalidPanel { - fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { - div() - .size_full() - .my_6() - .flex() - .flex_col() - .items_center() - .justify_center() - .text_color(cx.theme().muted_foreground) - .child(format!( - "The `{}` panel type is not registered in PanelRegistry.", - self.name.clone() - )) - } -} diff --git a/crates/dock/src/lib.rs b/crates/dock/src/lib.rs deleted file mode 100644 index 340da8b..0000000 --- a/crates/dock/src/lib.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::sync::Arc; - -use gpui::{Context, Pixels, Window, px}; -use gpui_base::dock::PanelView; - -mod dock_area; -mod invalid_panel; -mod tab_panel; -mod window_controls; - -pub use dock_area::SignedDockSkin; -pub use gpui_component::dock::{ - BasePanel, DockArea, DockEvent, DockLayout, DockPlacement, Panel, PanelEvent, panel_handle, -}; - -/// Add an already-wrapped panel handle to the center of `area`. -/// -/// Every panel entry point opens its panel there. -pub fn add_center_panel( - area: &mut DockArea, - panel: Arc, - window: &mut Window, - cx: &mut Context, -) { - area.add_panel_view(panel, DockPlacement::Center, None, window, cx); -} - -/// The fixed height of the tab bar, which doubles as the window title bar. -pub const TAB_BAR_HEIGHT: Pixels = px(44.); - -/// i18n shim resolving `Dock.*` keys to English, so the crate has no i18n dependency. -pub(crate) fn t(key: &'static str) -> &'static str { - match key { - "Dock.Close" => "Close", - "Dock.Zoom In" => "Zoom In", - "Dock.Zoom Out" => "Zoom Out", - "Dock.Collapse" => "Collapse", - "Dock.Expand" => "Expand", - _ => key, - } -} diff --git a/crates/dock/src/tab_panel.rs b/crates/dock/src/tab_panel.rs deleted file mode 100644 index fa04842..0000000 --- a/crates/dock/src/tab_panel.rs +++ /dev/null @@ -1,963 +0,0 @@ -use std::cell::Cell; -use std::rc::Rc; -use std::sync::Arc; -use std::time::Duration; - -use gpui::prelude::FluentBuilder as _; -use gpui::{ - Animation, AnimationExt as _, AnyElement, AnyView, App, AppContext as _, Bounds, Context, Div, - Empty, InteractiveElement as _, IntoElement, ParentElement as _, Pixels, Point, Render, - ScrollHandle, SharedString, Stateful, StatefulInteractiveElement as _, StyleRefinement, - Styled as _, Window, div, px, size, -}; -use gpui_base::dock::{ - AnyDrag, DockPlacement, DragPanel, DropIndicator, NodeId, PaneNode, PaneRef, - PanelView as BasePanelView, TabGroupContext, TabGroupRenderer, -}; -use gpui_base::{ElementExt, InteractiveElementExt, Tab, Tabs}; -use gpui_component::animation::{Lerp as _, ease_out_cubic}; -use gpui_component::button::{Button, ButtonVariants as _}; -use gpui_component::dock::{ClosePanel, PanelControl, PanelHandle, ToggleZoom}; -use gpui_component::{ActiveTheme as _, Disableable as _, IconName, Sizable as _, h_flex, v_flex}; -use settings::{SettingsStore, TabBarSettings}; -use signed_ui::title_bar_drag_handlers; - -use crate::dock_area::SkinShared; -use crate::{TAB_BAR_HEIGHT, t, window_controls}; - -const DRAG_PREVIEW_SIZE: gpui::Size = size(px(96.), px(30.)); -const COLLAPSED_TAB_BAR_SELECTOR: &str = "signed-dock-collapsed-tab-bar"; -const ACTIVE_TAB_SELECTOR: &str = "signed-dock-active-tab"; -const ACTIVE_TAB_CLOSE_SELECTOR: &str = "signed-dock-active-tab-close"; -const TAB_HOVER_GROUP: &str = "signed-dock-tab"; -const NAVIGATION_BUTTONS_TAB_THRESHOLD: usize = 10; - -/// Whether a group's prev/next buttons should be drawn. -fn navigation_buttons_visible(hide_navigation_buttons: bool, panels_len: usize) -> bool { - !hide_navigation_buttons && panels_len > NAVIGATION_BUTTONS_TAB_THRESHOLD -} - -/// A panel's title, or its registered name when the panel has no handle. -pub(crate) fn panel_title( - panel: &Arc, - window: &mut Window, - cx: &mut App, -) -> AnyElement { - match PanelHandle::of(panel) { - Some(handle) => handle.title(window, cx), - None => SharedString::from(panel.panel_name(cx)).into_any_element(), - } -} - -/// The preview that follows the cursor while a panel is dragged. -struct DragPanelPreview { - panel: Arc, -} - -impl Render for DragPanelPreview { - fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - h_flex() - .id("drag-panel") - .cursor_grab() - .p_1() - .whitespace_nowrap() - .border_1() - .border_color(cx.theme().border) - .rounded(cx.theme().radius_lg) - .text_color(cx.theme().tab_foreground) - .bg(cx.theme().tokens.tab_active) - .opacity(0.75) - .child(panel_title(&self.panel, window, cx)) - } -} - -/// The zoom affordance `panel` offers, if any. -/// -/// The panel must offer a control and be zoomable, base refuses a zoom otherwise. -fn panel_zoom_control(panel: &Arc, cx: &App) -> Option { - panel - .zoomable(cx) - .then(|| PanelHandle::of(panel).and_then(|handle| handle.zoom_control(cx))) - .flatten() -} - -/// The left-most, top-most tab group in a container. -/// -/// A left dock's collapse button lives in this group. -fn left_top_group(node: &PaneNode) -> Option { - match node.kind() { - PaneRef::Tabs { .. } => Some(node.id()), - PaneRef::Split { children, .. } => children.first().and_then(left_top_group), - } -} - -/// The right-most, top-most tab group. -/// -/// A vertical split picks its first child, a horizontal split picks its last. -fn right_top_group(node: &PaneNode) -> Option { - match node.kind() { - PaneRef::Tabs { .. } => Some(node.id()), - PaneRef::Split { axis, children, .. } => match axis { - gpui::Axis::Vertical => children.first(), - gpui::Axis::Horizontal => children.last(), - } - .and_then(right_top_group), - } -} - -/// The bottom or right dock whose root tab group is this one, if any. -/// -/// Base keeps a dock's last group, so the skin removes these docks as a whole. -fn dock_root_group( - shared: &SkinShared, - group: &TabGroupContext, - cx: &App, -) -> Option { - let area = shared.area().upgrade()?; - let area = area.read(cx); - - [DockPlacement::Bottom, DockPlacement::Right] - .into_iter() - .find(|placement| { - area.layout(*placement) - .is_some_and(|tree| tree.root().id() == group.node()) - }) -} - -/// Close `panel`, removing its dock instead when base would refuse an empty one. -/// -/// A bottom or right dock's only panel is that case, so the whole dock goes. -fn close_panel( - shared: &SkinShared, - group: &TabGroupContext, - panel: &Arc, - window: &mut Window, - cx: &mut App, -) { - if !panel.closable(cx) { - return; - } - - let dock = (group.panels().len() <= 1) - .then(|| dock_root_group(shared, group, cx)) - .flatten(); - - match dock { - Some(placement) => { - _ = shared.area().update(cx, |area, cx| { - area.remove_dock(placement, window, cx); - }); - } - None => group.close(panel.panel_id(cx), window, cx), - } -} - -/// One tab group's appearance, built once per container so its geometry is its own. -pub(crate) struct SignedTabGroupSkin { - shared: Rc, - scroll_handle: ScrollHandle, - /// The tab shown last frame, so a change scrolls the new one into view. - last_active_ix: Cell>, - /// Bounds of the title bar row, measured to place the title-bar drag overlay. - title_bar_bounds: Rc>>>, - /// Bounds of the empty strip after the last tab, where the drag region starts. - title_bar_strip_bounds: Rc>>>, - /// Bounds of the suffix area, where the drag region ends. - title_bar_suffix_bounds: Rc>>>, -} - -impl SignedTabGroupSkin { - pub(crate) fn new(shared: Rc) -> Self { - Self { - shared, - scroll_handle: ScrollHandle::default(), - last_active_ix: Cell::new(None), - title_bar_bounds: Rc::new(Cell::new(None)), - title_bar_strip_bounds: Rc::new(Cell::new(None)), - title_bar_suffix_bounds: Rc::new(Cell::new(None)), - } - } - - /// A group that is the left dock's only group, with one panel, draws no chrome. - /// The vendored dock rendered such a panel bare and the sidebar is one. - fn is_plain_sidebar_group(&self, group: &TabGroupContext, cx: &mut App) -> bool { - let Some(area) = self.shared.area().upgrade() else { - return false; - }; - let area = area.read(cx); - let Some(left) = area - .layout(DockPlacement::Left) - .map(|tree| tree.root().id()) - else { - return false; - }; - left == group.node() && group.panels().len() == 1 - } - - /// The tab's drag payload, or `None` when the group must not be rearranged. - /// - /// A locked group never is, a bottom or right dock root always is. - fn tab_drag(&self, group: &TabGroupContext, ix: usize, cx: &App) -> Option { - if group.is_locked() { - return None; - } - if !group.is_draggable() && dock_root_group(&self.shared, group, cx).is_none() { - return None; - } - group.drag_panel(ix, cx) - } - - /// A dock's collapse button for this group's bar, or `None` when it does not belong. - /// The icon direction depends on whether the dock is open. - fn dock_toggle_button( - &self, - placement: DockPlacement, - group: &TabGroupContext, - cx: &mut App, - ) -> Option