forked from 1b-agent/opencadstudio-upstream
Dock: auto-hide per edge, hover only flies out when auto-hiding, menu per edge
- Auto-hide is a setting of the left / right edge rather than of each pallet: the pin on any docked pallet turns it on for its whole edge, which then shows only its icon strip until an icon is hovered. A floating pallet keeps its own auto-hide. - Hovering the strip icons only shows a group while the edge auto-hides; otherwise groups switch on click. - The + menu checks only the pallets open on that edge. Picking one that is open elsewhere (the other edge, floating) moves it to this edge; unchecking a checked one hides it. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013RyGiMXcsf9HTKfqDxaQGt
This commit is contained in:
3 files changed
+115
-21
No files matched your search
+51
-18
@@ -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]
|
||||
|
||||
+7
-3
@@ -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)],
|
||||
|
||||
@@ -310,6 +310,9 @@ pub struct DockState {
|
||||
pub right: Vec<DockGroup>,
|
||||
/// 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<FloatPanel>,
|
||||
/// 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();
|
||||
|
||||
Reference in new issue
Block a user