diff --git a/src/app/update/dialog.rs b/src/app/update/dialog.rs index 0dd6c1fb9..dca344425 100644 --- a/src/app/update/dialog.rs +++ b/src/app/update/dialog.rs @@ -633,7 +633,11 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven iced::Task::none() } DockMsg::EdgeMenuToggle(side, id) => { - if self.dock_panel_visible(id) { + // Checked = open and docked on this edge: unchecking hides it. + // Anything else (closed, floating, on the other edge) moves + // here and opens. + let here = self.dock.location(id).map(|(s, _)| s) == Some(side); + if here && self.dock_panel_visible(id) { let task = self.on_dock(DockMsg::Close(id)); self.save_config(); return task; @@ -666,8 +670,8 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven iced::Task::none() } DockMsg::AutoCollapseToggle(id) => { - let on = !self.dock.auto_collapse(id); - self.dock.set_auto_collapse(id, on); + // Docked: auto-hide for the whole edge; floating: this pallet. + let on = self.dock.toggle_auto_hide(id); self.dock_expanded = if on { None } else { Some(id) }; self.save_config(); iced::Task::none() @@ -722,19 +726,22 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven // Ignored while dragging/resizing: the pointer is over the // drag preview, not a rail, so a hover must not collapse the // panel being dragged or repoint the resize target. - if self.dock_dragging.is_none() && self.dock_resizing.is_none() { + // Only an auto-hiding edge (or floating pallet) reacts to + // hover: it flies the hovered group out. Otherwise groups + // switch on click. + if self.dock_dragging.is_none() + && self.dock_resizing.is_none() + && self.dock.auto_hides(id) + { self.dock_expanded = Some(id); - // Hovering an auto-hiding group's icon flies it out. - if self.dock.auto_collapse(id) { - self.dock.show_group_of(id); - } + self.dock.show_group_of(id); } iced::Task::none() } DockMsg::HoverExit => { if self.dock_dragging.is_none() && self.dock_resizing.is_none() { if let Some(id) = self.dock_expanded { - if self.dock.auto_collapse(id) { + if self.dock.auto_hides(id) { self.dock_expanded = None; } } @@ -991,14 +998,15 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven } /// Whether `side` draws its shown group beside the icon strip: always, - /// unless all its pallets auto-hide and none is hovered. + /// unless the edge auto-hides and none of the group's icons is hovered. pub(crate) fn dock_edge_expanded(&self, side: crate::app::config::DockSide) -> bool { let Some(gi) = self.dock_shown_group(side) else { return false; }; - let panels = self.dock_group_visible(side, gi); - panels.iter().any(|id| !self.dock.auto_collapse(*id)) - || self.dock_expanded.is_some_and(|e| panels.contains(&e)) + !self.dock.edge_auto_hide(side) + || self + .dock_expanded + .is_some_and(|e| self.dock_group_visible(side, gi).contains(&e)) } /// Width of group `gi` on `side`: its pallets share the widest one's @@ -2010,9 +2018,9 @@ mod tests { app.show_block_palette = true; let id = crate::ui::dock::PanelId::BlockPalette; let _ = app.on_dock(crate::ui::dock::DockMsg::AutoCollapseToggle(id)); - assert!(app.dock.auto_collapse(id), "pin enables auto-collapse"); + assert!(app.dock.auto_hides(id), "pin enables auto-hide"); let _ = app.on_dock(crate::ui::dock::DockMsg::AutoCollapseToggle(id)); - assert!(!app.dock.auto_collapse(id), "second pin disables auto-collapse"); + assert!(!app.dock.auto_hides(id), "second pin disables auto-hide"); let _ = app.on_dock(crate::ui::dock::DockMsg::Close(id)); assert!(!app.show_block_palette, "close dismisses the sidebar"); } @@ -2287,18 +2295,37 @@ mod tests { } #[test] - fn dock_auto_hiding_group_shows_while_its_icon_is_hovered() { + fn dock_auto_hide_edge_shows_the_hovered_group() { use crate::app::config::DockSide; - use crate::ui::dock::{DockMsg, PanelId}; + use crate::ui::dock::{DockGroup, DockMsg, PanelId}; let mut app = dock_app(); + app.show_browser = true; + app.dock.left.push(DockGroup::single(PanelId::Browser)); + // The pin on any docked pallet turns auto-hide on for its edge. let _ = app.on_dock(DockMsg::AutoCollapseToggle(PanelId::Properties)); + assert!(app.dock.edge_auto_hide(DockSide::Left)); + assert!(!app.dock.edge_auto_hide(DockSide::Right)); assert!(!app.dock_edge_expanded(DockSide::Left)); - let _ = app.on_dock(DockMsg::Hover(PanelId::Properties)); + // Hovering an icon flies its group out. + let _ = app.on_dock(DockMsg::Hover(PanelId::Browser)); assert!(app.dock_edge_expanded(DockSide::Left)); + assert_eq!(app.dock_shown_group(DockSide::Left), Some(1)); let _ = app.on_dock(DockMsg::HoverExit); assert!(!app.dock_edge_expanded(DockSide::Left)); } + #[test] + fn dock_hover_does_not_switch_groups_without_auto_hide() { + use crate::app::config::DockSide; + use crate::ui::dock::{DockGroup, DockMsg, PanelId}; + let mut app = dock_app(); + app.show_browser = true; + app.dock.left.push(DockGroup::single(PanelId::Browser)); + let _ = app.on_dock(DockMsg::Hover(PanelId::Browser)); + assert_eq!(app.dock_shown_group(DockSide::Left), Some(0)); + assert!(app.dock_edge_expanded(DockSide::Left)); + } + #[test] fn dock_float_resize_grows_the_panel() { use crate::ui::dock::{DockMsg, FloatPanel, PanelId}; @@ -2345,6 +2372,12 @@ mod tests { // Opening it again from the same edge reuses that place. let _ = app.on_dock(DockMsg::EdgeMenuToggle(DockSide::Left, PanelId::Browser)); assert_eq!(app.dock.left.len(), 2); + // A pallet open on the other edge is not checked here: picking it + // moves it over instead of hiding it. + let _ = app.on_dock(DockMsg::EdgeMenuToggle(DockSide::Left, PanelId::BlockPalette)); + assert!(app.show_block_palette); + assert!(app.dock.right.is_empty()); + assert_eq!(app.dock.location(PanelId::BlockPalette), Some((DockSide::Left, 2))); } #[test] diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index c316dccdb..c52b94f3f 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -1940,9 +1940,13 @@ bg={bg_ms:.1}ms n={view_count}" // catcher that closes it on any click elsewhere. if let Some(side) = self.dock_edge_menu { if !self.dock_visible_groups(side).is_empty() { + // Checked = open and docked on this edge. let shown: Vec<_> = crate::ui::dock::PanelId::ALL .iter() - .map(|p| (*p, self.dock_panel_visible(*p))) + .map(|p| { + let here = self.dock.location(*p).map(|(s, _)| s) == Some(side); + (*p, here && self.dock_panel_visible(*p)) + }) .collect(); let (_, plus_top) = self.dock_strip_layout(side); let strip = crate::ui::dock::DOCK_STRIP_W; @@ -2927,7 +2931,7 @@ impl OpenCADStudio { tab: &'a DocumentTab, ) -> Element<'a, Message> { let chrome = crate::ui::dock::Chrome { - auto_collapse: self.dock.auto_collapse(id), + auto_collapse: self.dock.auto_hides(id), floating, title_hovered: self.dock_title_hover == Some(id), }; @@ -3635,7 +3639,7 @@ const DOCK_MENU_W: f32 = 230.0; /// The pallet menu of an edge's + button, styled like the right-click menu: /// one row per pallet with its icon, its name and a check mark when it is -/// shown. `shown` holds each pallet with that state. +/// open on this edge. `shown` holds each pallet with that state. fn dock_edge_menu( side: crate::app::config::DockSide, shown: &[(crate::ui::dock::PanelId, bool)], diff --git a/src/ui/dock.rs b/src/ui/dock.rs index 19e9e9678..c20513b3d 100644 --- a/src/ui/dock.rs +++ b/src/ui/dock.rs @@ -310,6 +310,9 @@ pub struct DockState { pub right: Vec, /// The group each edge shows (left, right). pub shown: (usize, usize), + /// Auto-hide per edge (left, right): the edge shows only its icon strip + /// until an icon is hovered. + pub auto_hide: (bool, bool), /// Floating panels, back → front. pub floating: Vec, /// Per-panel width / auto-collapse settings, keyed by `PanelId`. A @@ -323,6 +326,7 @@ impl Default for DockState { left: vec![DockGroup::single(PanelId::Properties)], right: vec![DockGroup::single(PanelId::BlockPalette)], shown: (0, 0), + auto_hide: (false, false), floating: Vec::new(), panels: BTreeMap::new(), } @@ -435,10 +439,41 @@ impl DockState { .clamp(DOCK_MIN_W, id.max_width().min(win_w * 0.45).max(DOCK_MIN_W)) } + /// The floating auto-hide flag of `id` (a docked pallet follows its + /// edge instead; see [`Self::auto_hides`]). pub fn auto_collapse(&self, id: PanelId) -> bool { self.settings(id).auto_collapse } + /// Whether `side` auto-hides. + pub fn edge_auto_hide(&self, side: DockSide) -> bool { + match side { + DockSide::Left => self.auto_hide.0, + DockSide::Right => self.auto_hide.1, + } + } + + /// Whether `id` auto-hides where it is: by its edge's setting when + /// docked, by its own when floating. + pub fn auto_hides(&self, id: PanelId) -> bool { + match self.location(id) { + Some((side, _)) => self.edge_auto_hide(side), + None => self.auto_collapse(id), + } + } + + /// Flip auto-hide where `id` is: its whole edge when docked, the pallet + /// itself when floating. Returns the new state. + pub fn toggle_auto_hide(&mut self, id: PanelId) -> bool { + let on = !self.auto_hides(id); + match self.location(id) { + Some((DockSide::Left, _)) => self.auto_hide.0 = on, + Some((DockSide::Right, _)) => self.auto_hide.1 = on, + None => self.set_auto_collapse(id, on), + } + on + } + /// Set the persisted width, clamped. pub fn set_width(&mut self, id: PanelId, width: f32) { let entry = self.panels.entry(id).or_insert_with(|| DockPanel::for_id(id)); @@ -1131,6 +1166,28 @@ mod tests { assert_eq!(state.left[0].weights, vec![1.0, 1.0]); } + #[test] + fn auto_hide_is_per_edge_for_docked_and_per_pallet_when_floating() { + let mut state = DockState::default(); + state.join_group(PanelId::Browser, DockSide::Left, 0, 1); + assert!(state.toggle_auto_hide(PanelId::Properties)); + // The whole left edge auto-hides, the right edge does not. + assert!(state.auto_hides(PanelId::Browser)); + assert!(!state.auto_hides(PanelId::BlockPalette)); + // A floating pallet keeps its own setting. + state.float(FloatPanel { + id: PanelId::Count, + x: 0.0, + y: 0.0, + w: 250.0, + h: 300.0, + }); + assert!(!state.auto_hides(PanelId::Count)); + assert!(state.toggle_auto_hide(PanelId::Count)); + assert!(state.auto_hides(PanelId::Count)); + assert!(!state.edge_auto_hide(DockSide::Right)); + } + #[test] fn ensure_settings_drops_duplicates_and_clamps_the_shown_group() { let mut state = DockState::default();