Dock: drop dead pallet-width setters, split on_dock, share span maths

- Only docked pallets have a width divider, so the Width drag and
  WidthReset no longer carry a branch for an undocked pallet; remove the
  then unused DockState::set_width / reset_width. Per-pallet widths are
  still read (older configs, floating size). Tests now size groups.
- on_dock hands hover messages to dock_hover and the release to
  dock_drag_release.
- dock_slot_spans and dock_landing share dock_open_spans.

No behaviour change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_013RyGiMXcsf9HTKfqDxaQGt
This commit is contained in:
Claude committed 2026-10-10 12:34:02 +00:00
1 parent 02404301a5
commit cc17d10ca9
4 files changed
+111 -107

No files matched your search

+1 -1
View File
@@ -186,7 +186,7 @@ fn dock_icon_press_switches_the_shown_group() {
assert_eq!(app.dock_shown_group(DockSide::Left), Some(1));
assert_eq!(app.dock_slot_spans(DockSide::Left), vec![(0, 0.0, 900.0)]);
// Each group keeps its own width.
app.dock.set_width(PanelId::Browser, 400.0);
app.dock.set_group_width(DockSide::Left, 1, 400.0);
assert_eq!(app.dock_column_width(DockSide::Left), 400.0);
let _ = app.on_dock(DockMsg::IconPress(PanelId::BlockPalette));
let _ = app.on_dock(DockMsg::DragRelease);
+90 -65
View File
@@ -30,7 +30,7 @@ impl OpenCADStudio {
/// whichever panel the message names.
pub(crate) fn on_dock(&mut self, m: crate::ui::dock::DockMsg) -> iced::Task<Message> {
use crate::app::config::DockSide;
use crate::ui::dock::{DockDrag, DockMsg, DropTarget, FloatPanel, HoverPending};
use crate::ui::dock::{DockDrag, DockMsg, FloatPanel};
match m {
DockMsg::DockGrab(id) => self.dock_begin(DockDrag::moving(id, None), Some(id)),
DockMsg::IconPress(id) => {
@@ -66,12 +66,11 @@ impl OpenCADStudio {
self.save_config();
}
DockMsg::WidthReset(id) => {
// Only docked pallets have the divider that sends this.
if let Some((side, gi)) = self.dock.location(id) {
self.dock.reset_group_width(side, gi);
} else {
self.dock.reset_width(id);
self.save_config();
}
self.save_config();
}
DockMsg::TitleHover(id) => self.dock_title_hover = Some(id),
DockMsg::TitleHoverEnd(id) => {
@@ -150,6 +149,22 @@ impl OpenCADStudio {
self.save_config();
}
DockMsg::Close(id) => return self.dock_set_open(id, false),
DockMsg::Hover(_)
| DockMsg::HoverEnd(_)
| DockMsg::HoverExit
| DockMsg::HoverStay
| DockMsg::HoverSettled(_) => return self.dock_hover(m),
DockMsg::DragMove(point) => self.dock_drag_move(point),
DockMsg::DragRelease => self.dock_drag_release(),
}
iced::Task::none()
}
/// Pointer entering / leaving strip icons, edge columns and floating
/// frames, and the delayed auto-hide reveal / hide that follows.
fn dock_hover(&mut self, m: crate::ui::dock::DockMsg) -> iced::Task<Message> {
use crate::ui::dock::{DockMsg, HoverPending};
match m {
DockMsg::Hover(id) => {
self.dock_icon_hover = Some(id);
// Only an auto-hiding edge (or floating pallet) reacts to
@@ -198,54 +213,57 @@ impl OpenCADStudio {
}
}
}
DockMsg::DragMove(point) => self.dock_drag_move(point),
DockMsg::DragRelease => {
let mut changed = false;
match self.dock_drag.take() {
Some(DockDrag::Move {
group: Some((side, gi)),
target: Some(DropTarget::Edge { side: to, index }),
..
}) => changed = self.dock.move_group(side, gi, to, index),
Some(DockDrag::Move {
panel,
group: None,
target: Some(target),
..
}) => {
changed = match target {
DropTarget::Edge { side, index } => self.dock.dock(panel, side, index),
DropTarget::Join { side, group, index } => {
self.dock.join_group(panel, side, group, index)
}
DropTarget::Float { x, y } => {
let (w, h) = self.dock_float_size(panel);
// A floating panel keeps its docking choice.
let docking = self.dock.float_rect(panel).is_none_or(|f| f.docking);
self.dock.float(FloatPanel {
docking,
..FloatPanel::new(panel, x, y, w, h)
})
}
};
}
Some(DockDrag::Move { .. } | DockDrag::LayerColumn) | None => {}
Some(DockDrag::Width(_) | DockDrag::Split { .. } | DockDrag::FloatSize { .. }) => {
changed = true;
}
}
if changed {
self.save_config();
}
self.dock_drag_last = None;
self.xref_col_drag = None;
self.xref_col_last = None;
self.xref_split_drag = false;
}
_ => {}
}
iced::Task::none()
}
/// Pointer released: land what was dragged and save a changed layout.
fn dock_drag_release(&mut self) {
use crate::ui::dock::{DockDrag, DropTarget, FloatPanel};
let mut changed = false;
match self.dock_drag.take() {
Some(DockDrag::Move {
group: Some((side, gi)),
target: Some(DropTarget::Edge { side: to, index }),
..
}) => changed = self.dock.move_group(side, gi, to, index),
Some(DockDrag::Move {
panel,
group: None,
target: Some(target),
..
}) => {
changed = match target {
DropTarget::Edge { side, index } => self.dock.dock(panel, side, index),
DropTarget::Join { side, group, index } => {
self.dock.join_group(panel, side, group, index)
}
DropTarget::Float { x, y } => {
let (w, h) = self.dock_float_size(panel);
// A floating panel keeps its docking choice.
let docking = self.dock.float_rect(panel).is_none_or(|f| f.docking);
self.dock.float(FloatPanel {
docking,
..FloatPanel::new(panel, x, y, w, h)
})
}
};
}
Some(DockDrag::Move { .. } | DockDrag::LayerColumn) | None => {}
Some(DockDrag::Width(_) | DockDrag::Split { .. } | DockDrag::FloatSize { .. }) => {
changed = true;
}
}
if changed {
self.save_config();
}
self.dock_drag_last = None;
self.xref_col_drag = None;
self.xref_col_last = None;
self.xref_split_drag = false;
}
/// Pointer motion while something in the dock is dragged.
fn dock_drag_move(&mut self, point: iced::Point) {
// NOTE: xref column drags intentionally do NOT ride this path: its
@@ -353,18 +371,12 @@ impl OpenCADStudio {
}
DockDrag::Width(id) => {
let Some(last) = last else { return };
let dx = point.x - last.x;
match self.dock.location(id) {
// The divider sizes the panel's group.
Some((side, gi)) => {
let delta = if side == DockSide::Left { dx } else { -dx };
let cur = self.dock_group_width(side, gi) + delta;
self.dock.set_group_width(side, gi, cur);
}
None => {
let cur = self.dock.settings(id).width + dx;
self.dock.set_width(id, cur);
}
// The divider sizes the docked pallet's group.
if let Some((side, gi)) = self.dock.location(id) {
let dx = point.x - last.x;
let delta = if side == DockSide::Left { dx } else { -dx };
let cur = self.dock_group_width(side, gi) + delta;
self.dock.set_group_width(side, gi, cur);
}
}
}
@@ -634,13 +646,26 @@ impl OpenCADStudio {
&self,
side: crate::app::config::DockSide,
) -> Vec<(usize, f32, f32)> {
let Some(gi) = self.dock_shown_group(side) else {
return Vec::new();
};
match self.dock_shown_group(side) {
Some(gi) => self.dock_open_spans(&self.dock.groups(side)[gi], None),
None => Vec::new(),
}
}
/// The open pallets of `group` (plus `extra`, a pallet about to land
/// there), each as (position in the group, top, bottom) down the
/// workspace height.
pub(crate) fn dock_open_spans(
&self,
group: &crate::ui::dock::DockGroup,
extra: Option<crate::ui::dock::PanelId>,
) -> Vec<(usize, f32, f32)> {
let (_, avail) = self.dock_workspace_size();
let group = &self.dock.groups(side)[gi];
let open: Vec<usize> = (0..group.panels.len())
.filter(|i| self.dock_panel_visible(group.panels[*i]))
.filter(|i| {
let id = group.panels[*i];
Some(id) == extra || self.dock_panel_visible(id)
})
.collect();
let weights: Vec<f32> = open.iter().map(|i| group.weights[*i]).collect();
open.into_iter()
+4 -7
View File
@@ -690,13 +690,10 @@ impl OpenCADStudio {
}
let (side, gi) = after.location(id)?;
let group = &after.groups(side)[gi];
let open: Vec<usize> = (0..group.panels.len())
.filter(|i| group.panels[*i] == id || self.dock_panel_visible(group.panels[*i]))
.collect();
let weights: Vec<f32> = open.iter().map(|i| group.weights[*i]).collect();
let at = open.iter().position(|i| group.panels[*i] == id)?;
let (_, avail) = self.dock_workspace_size();
let (top, bottom) = *crate::ui::dock::slot_spans(&weights, avail).get(at)?;
let (_, top, bottom) = self
.dock_open_spans(group, Some(id))
.into_iter()
.find(|(i, _, _)| group.panels[*i] == id)?;
Some((top, bottom, after.group_width_px(side, gi, self.win_size.0)))
}
+16 -34
View File
@@ -18,9 +18,9 @@ use std::collections::BTreeMap;
pub enum DockMsg {
/// Begin dragging `panel` to another side / position.
DockGrab(PanelId),
/// Begin resizing `panel`'s width.
/// Begin resizing the width of docked `panel`'s group.
ResizeGrab(PanelId),
/// Reset `panel`'s width to its default.
/// Reset the width of docked `panel`'s group to its default.
WidthReset(PanelId),
/// Toggle `panel`'s auto-collapse (pin) behavior.
AutoCollapseToggle(PanelId),
@@ -582,18 +582,6 @@ impl DockState {
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));
entry.width = width.clamp(DOCK_MIN_W, id.max_width());
}
/// Reset width to the panel's default.
pub fn reset_width(&mut self, id: PanelId) {
let entry = self.panels.entry(id).or_insert_with(|| DockPanel::for_id(id));
entry.width = id.default_width();
}
pub fn set_auto_collapse(&mut self, id: PanelId, on: bool) {
let entry = self.panels.entry(id).or_insert_with(|| DockPanel::for_id(id));
entry.auto_collapse = on;
@@ -846,7 +834,7 @@ pub enum DockDrag {
target: Option<DropTarget>,
grab: iced::Vector,
},
/// Sizing the width of `panel`'s group (or of `panel`, floating).
/// Sizing the width of docked `panel`'s group.
Width(PanelId),
/// Moving the splitter between pallets `upper` and `lower` of a group.
Split {
@@ -1192,11 +1180,12 @@ mod tests {
// Wider default for the column-rich references table, double the maximum.
// (The 45%-of-window rule still dominates on narrow windows.)
assert_eq!(state.width(PanelId::ExternalReferences, 3000.0), 460.0);
state.set_width(PanelId::ExternalReferences, 5000.0);
assert_eq!(state.width(PanelId::ExternalReferences, 3000.0), DOCK_MAX_W * 2.0);
state.dock(PanelId::ExternalReferences, DockSide::Right, 1);
state.set_group_width(DockSide::Right, 1, 5000.0);
assert_eq!(state.group_width_px(DockSide::Right, 1, 3000.0), DOCK_MAX_W * 2.0);
// Other panels keep the shared maximum.
state.set_width(PanelId::BlockPalette, 5000.0);
assert_eq!(state.width(PanelId::BlockPalette, 3000.0), DOCK_MAX_W);
state.set_group_width(DockSide::Right, 0, 5000.0);
assert_eq!(state.group_width_px(DockSide::Right, 0, 3000.0), DOCK_MAX_W);
}
#[test]
@@ -1247,32 +1236,23 @@ mod tests {
assert!(!state.dock(PanelId::Properties, DockSide::Left, 0));
}
#[test]
fn set_width_clamps() {
let mut state = DockState::default();
state.set_width(PanelId::BlockPalette, 10.0);
assert_eq!(state.width(PanelId::BlockPalette, 1600.0), DOCK_MIN_W);
state.set_width(PanelId::BlockPalette, 5000.0);
assert_eq!(state.width(PanelId::BlockPalette, 1600.0), DOCK_MAX_W);
}
#[test]
fn width_respects_maximum_window_fraction() {
let mut state = DockState::default();
state.set_width(PanelId::BlockPalette, 500.0);
state.set_group_width(DockSide::Right, 0, 500.0);
// Window too narrow -> capped by the 0.45 fraction, not DOCK_MAX_W.
assert_eq!(state.width(PanelId::BlockPalette, 800.0), DOCK_MAX_W.min(360.0));
assert_eq!(state.group_width_px(DockSide::Right, 0, 800.0), DOCK_MAX_W.min(360.0));
}
#[test]
fn width_does_not_panic_when_window_minimized() {
let mut state = DockState::default();
state.set_width(PanelId::BlockPalette, 500.0);
state.set_group_width(DockSide::Right, 0, 500.0);
// A minimized or not-yet-laid-out window reports width 0; the clamp
// must fall back to DOCK_MIN_W instead of panicking on min > max.
assert_eq!(state.width(PanelId::BlockPalette, 0.0), DOCK_MIN_W);
assert_eq!(state.group_width_px(DockSide::Right, 0, 0.0), DOCK_MIN_W);
// Deleted right below the minimum dock width behaves the same way.
assert_eq!(state.width(PanelId::BlockPalette, 100.0), DOCK_MIN_W);
assert_eq!(state.group_width_px(DockSide::Right, 0, 100.0), DOCK_MIN_W);
}
#[test]
@@ -1303,7 +1283,7 @@ mod tests {
#[test]
fn join_group_stacks_and_shows_the_group() {
let mut state = DockState::default();
state.set_width(PanelId::Properties, 320.0);
state.set_group_width(DockSide::Left, 0, 320.0);
// Below Properties in the left group.
assert!(state.join_group(PanelId::BlockPalette, DockSide::Left, 0, 1));
assert!(state.right.is_empty(), "its old group disappears");
@@ -1412,6 +1392,8 @@ mod tests {
assert_eq!(state.group_width(DockSide::Right, 0), 260.0);
state.set_group_width(DockSide::Left, 0, 5000.0);
assert_eq!(state.group_width(DockSide::Left, 0), DOCK_MAX_W);
state.set_group_width(DockSide::Left, 0, 10.0);
assert_eq!(state.group_width(DockSide::Left, 0), DOCK_MIN_W);
state.reset_group_width(DockSide::Left, 0);
assert_eq!(state.group_width(DockSide::Left, 0), 250.0);
}