diff --git a/crates/ui/src/docking/floating_tests.rs b/crates/ui/src/docking/floating_tests.rs index 1ecdb730..85015888 100644 --- a/crates/ui/src/docking/floating_tests.rs +++ b/crates/ui/src/docking/floating_tests.rs @@ -48,17 +48,25 @@ fn workspace() -> Layout { struct Content { native: bool, keep: Vec, + /// Canvas 0 accepts side splits, as an app that opens panels beside it does. + beside: bool, + drawn: Vec, } impl DockContent for Content { - fn body(&mut self, ui: &mut egui::Ui, _: &u32) { + fn body(&mut self, ui: &mut egui::Ui, panel: &u32) { + self.drawn.push(*panel); ui.label("Body"); } fn logical_label(&self, panel: &u32) -> String { format!("Panel {panel}") } fn permissions(&self, panel: &u32) -> Permissions { - if *panel == 0 { Permissions::PROTECTED } else { Permissions::default() } + match *panel { + 0 if self.beside => Permissions { accepts_splits: true, ..Permissions::PROTECTED }, + 0 => Permissions::PROTECTED, + _ => Permissions::default(), + } } fn allow_native(&self, _: &u32) -> bool { self.native @@ -459,6 +467,19 @@ fn hide_panels_keeps_protected_panels_and_the_except_set() { assert_eq!(d.title(6).min, pos2(300.0, 84.0), "below the resize band"); } +#[test] +fn a_canvas_that_accepts_splits_is_still_protected() { + let mut d = Driver::new(workspace(), Profile::Dock); + d.content.beside = true; + d.hide = Hide::All; + d.step(vec![]); + d.content.drawn.clear(); + d.step(vec![]); + assert_eq!(d.content.drawn, vec![0], "hiding every panel keeps the canvas"); + assert!(d.tab(0).is_none()); + assert!(d.response(DockArea::::hit_id(Id::new(AREA), &Hit::DragBar(0))).is_none()); +} + #[test] fn native_window_falls_back_in_window_when_viewports_are_embedded() { let mut layout = workspace(); diff --git a/crates/ui/src/docking/model.rs b/crates/ui/src/docking/model.rs index 3ca0c100..e79bf096 100644 --- a/crates/ui/src/docking/model.rs +++ b/crates/ui/src/docking/model.rs @@ -346,6 +346,12 @@ impl Permissions { /// uses `Permissions { accepts_splits: true, ..Permissions::PROTECTED }`. pub const PROTECTED: Self = Self { movable: false, floatable: false, closable: false, accepts_tabs: false, accepts_splits: false }; + /// A canvas: it can neither move, close nor take tabs. `accepts_splits` and `floatable` + /// do not matter, so a canvas that opens panels beside it still counts. + pub fn is_protected(self) -> bool { + !self.movable && !self.closable && !self.accepts_tabs + } + /// Lock Workspace: nothing moves, floats or closes; tab and split drops keep their policy. /// [`super::Workspace::apply`] applies this when the workspace is locked; an application /// with a global lock preference applies it in its own permissions callback. diff --git a/crates/ui/src/docking/render/frame.rs b/crates/ui/src/docking/render/frame.rs index 0296daf6..8b90fe99 100644 --- a/crates/ui/src/docking/render/frame.rs +++ b/crates/ui/src/docking/render/frame.rs @@ -6,7 +6,7 @@ use egui::{Context, Pos2, Rect, pos2, vec2}; use super::{GroupKind, Renderer, floating::StackHit, group::TabGroup}; use crate::docking::{ - Action, Column, DockContent, DockEvent, Floating, Group, HidePanels, Layout, Node, Permissions, Profile, Zone, + Action, Column, DockContent, DockEvent, Floating, Group, HidePanels, Layout, Node, Profile, Zone, dock_model::{Place, node_at}, }; @@ -29,7 +29,7 @@ pub(in crate::docking) fn view<'a, P: Clone + Eq + Hash>( hide: HidePanels<'_, P>, content: &dyn DockContent

, ) -> (Cow<'a, Layout

>, FrameState

) { - let protected = |p: &P| content.permissions(p) == Permissions::PROTECTED; + let protected = |p: &P| content.permissions(p).is_protected(); let hidden = |p: &P| match hide { HidePanels::None => false, HidePanels::All => !protected(p), diff --git a/crates/ui/src/docking/render/mod.rs b/crates/ui/src/docking/render/mod.rs index 869c8d0c..3d579f67 100644 --- a/crates/ui/src/docking/render/mod.rs +++ b/crates/ui/src/docking/render/mod.rs @@ -231,7 +231,7 @@ impl Renderer<'_, P> { /// A lone protected or bare panel draws no tab strip and no drag bar. pub(in crate::docking) fn stripless

(panels: &[P], content: &dyn DockContent

) -> bool { match panels { - [panel] => content.permissions(panel) == Permissions::PROTECTED || content.bare(panel), + [panel] => content.permissions(panel).is_protected() || content.bare(panel), _ => false, } }