forked from 1b-agent/opencadstudio-upstream
Dock: floating groups, floating title bar menu, translations
- A docked group floats as one window: drag its edge band over the drawing or double-click the band. Floating windows hold a stack of pallets with splitters; the vertical title bar shows each pallet's icon (press one to drag that pallet out). Dragging the bar moves the whole window; dropping it on an edge docks or joins it as a group. - Right-click a floating title bar: Pallets submenu (add or hide pallets in the window), Dock left, Dock right. - Close on a floating bar closes every pallet in the window; its pin sets auto-hide for the whole window. - Older configs with single floating pallets still load. - Translate the new dock texts (Auto-hide, Dock left/right, New group, Pallets, Browser, Node Graph) in all 21 languages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013RyGiMXcsf9HTKfqDxaQGt
This commit is contained in:
27 files changed
+1024
-225
No files matched your search
+167
-42
@@ -98,22 +98,79 @@ impl OpenCADStudio {
|
||||
_ => strip + self.dock_column_width(DockSide::Left) + 40.0,
|
||||
};
|
||||
self.dock_drag = None;
|
||||
if self.dock.float(FloatPanel {
|
||||
id,
|
||||
x: x.max(0.0),
|
||||
y: 40.0,
|
||||
w,
|
||||
h,
|
||||
}) {
|
||||
if self.dock.float(FloatPanel::new(id, x.max(0.0), 40.0, w, h)) {
|
||||
self.save_config();
|
||||
}
|
||||
}
|
||||
DockMsg::GroupFloat(side, gi) => {
|
||||
// Float beside the edge it was docked on, near the top.
|
||||
let (ww, _) = self.dock_workspace_size();
|
||||
let w = self.dock_group_width(side, gi);
|
||||
let strip = crate::ui::dock::DOCK_STRIP_W;
|
||||
let x = match side {
|
||||
DockSide::Right => ww - strip - self.dock_column_width(side) - w - 60.0,
|
||||
DockSide::Left => strip + self.dock_column_width(side) + 40.0,
|
||||
};
|
||||
self.dock_drag = None;
|
||||
let h = self.dock_default_float_h();
|
||||
if self.dock.float_group(side, gi, x.max(0.0), 40.0, w, h) {
|
||||
self.save_config();
|
||||
}
|
||||
}
|
||||
DockMsg::DockTo(id, side) => {
|
||||
self.dock_drag = None;
|
||||
if self.dock.dock(id, side, usize::MAX) {
|
||||
self.dock_float_menu = None;
|
||||
if self.dock.dock_window(id, side, usize::MAX) {
|
||||
self.save_config();
|
||||
}
|
||||
}
|
||||
DockMsg::FloatGrab(id) => self.dock_begin(DockDrag::moving_window(id), Some(id)),
|
||||
DockMsg::FloatClose(id) => {
|
||||
let panels = self
|
||||
.dock
|
||||
.float_rect(id)
|
||||
.map(|f| f.group.panels.clone())
|
||||
.unwrap_or_default();
|
||||
let mut tasks = Vec::new();
|
||||
for p in panels {
|
||||
if self.dock_panel_visible(p) {
|
||||
tasks.push(self.dock_set_open(p, false));
|
||||
}
|
||||
}
|
||||
return iced::Task::batch(tasks);
|
||||
}
|
||||
DockMsg::FloatSplitGrab(panel, upper, lower) => {
|
||||
self.dock.raise_float(panel);
|
||||
self.dock_begin(DockDrag::FloatSplit { panel, upper, lower }, None);
|
||||
}
|
||||
DockMsg::FloatSplitReset(id) => {
|
||||
self.dock.reset_float_splits(id);
|
||||
self.save_config();
|
||||
}
|
||||
DockMsg::FloatMenu(id) => {
|
||||
self.dock_float_menu = id;
|
||||
self.dock_float_menu_pallets = false;
|
||||
if let Some(id) = id {
|
||||
self.dock.raise_float(id);
|
||||
}
|
||||
}
|
||||
DockMsg::FloatMenuPallets(open) => self.dock_float_menu_pallets = open,
|
||||
DockMsg::FloatMenuToggle(window, id) => {
|
||||
// Checked = open in this window: unchecking hides it.
|
||||
// Anything else (closed, docked, in another window) moves
|
||||
// into the window and opens; adding ends the menu's job.
|
||||
let here = self.dock.float_rect(window).is_some_and(|f| f.contains(id));
|
||||
if here && self.dock_panel_visible(id) {
|
||||
let task = self.dock_set_open(id, false);
|
||||
self.save_config();
|
||||
return task;
|
||||
}
|
||||
self.dock.add_to_float(id, window);
|
||||
self.dock_float_menu = None;
|
||||
let task = self.dock_set_open(id, true);
|
||||
self.save_config();
|
||||
return task;
|
||||
}
|
||||
DockMsg::EdgeMenu(side) => self.dock_edge_menu = side,
|
||||
DockMsg::EdgeMenuToggle(side, id) => {
|
||||
// Checked = open and docked on this edge: unchecking hides it.
|
||||
@@ -194,6 +251,31 @@ impl OpenCADStudio {
|
||||
target: Some(DropTarget::Edge { side: to, index }),
|
||||
..
|
||||
}) => changed = self.dock.move_group(side, gi, to, index),
|
||||
Some(DockDrag::Move {
|
||||
group: Some((side, gi)),
|
||||
target: Some(DropTarget::Float { x, y }),
|
||||
..
|
||||
}) => {
|
||||
let w = self.dock_group_width(side, gi);
|
||||
let h = self.dock_default_float_h();
|
||||
changed = self.dock.float_group(side, gi, x, y, w, h);
|
||||
}
|
||||
Some(DockDrag::Move {
|
||||
panel,
|
||||
window: true,
|
||||
target: Some(target),
|
||||
..
|
||||
}) => {
|
||||
changed = match target {
|
||||
DropTarget::Edge { side, index } => {
|
||||
self.dock.dock_window(panel, side, index)
|
||||
}
|
||||
DropTarget::Join { side, group, index } => {
|
||||
self.dock.join_window(panel, side, group, index)
|
||||
}
|
||||
DropTarget::Float { x, y } => self.dock.move_float(panel, x, y),
|
||||
};
|
||||
}
|
||||
Some(DockDrag::Move {
|
||||
panel,
|
||||
group: None,
|
||||
@@ -207,18 +289,17 @@ impl OpenCADStudio {
|
||||
}
|
||||
DropTarget::Float { x, y } => {
|
||||
let (w, h) = self.dock_float_size(panel);
|
||||
self.dock.float(FloatPanel {
|
||||
id: panel,
|
||||
x,
|
||||
y,
|
||||
w,
|
||||
h,
|
||||
})
|
||||
self.dock.float(FloatPanel::new(panel, x, y, w, h))
|
||||
}
|
||||
};
|
||||
}
|
||||
Some(DockDrag::Move { .. } | DockDrag::LayerColumn) | None => {}
|
||||
Some(DockDrag::Width(_) | DockDrag::Split { .. } | DockDrag::FloatSize { .. }) => {
|
||||
Some(
|
||||
DockDrag::Width(_)
|
||||
| DockDrag::Split { .. }
|
||||
| DockDrag::FloatSize { .. }
|
||||
| DockDrag::FloatSplit { .. },
|
||||
) => {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
@@ -251,6 +332,7 @@ impl OpenCADStudio {
|
||||
DockDrag::Move {
|
||||
panel,
|
||||
group,
|
||||
window,
|
||||
origin,
|
||||
target,
|
||||
grab,
|
||||
@@ -258,10 +340,12 @@ impl OpenCADStudio {
|
||||
let origin = origin.unwrap_or(point);
|
||||
let moved = (point.x - origin.x).hypot(point.y - origin.y);
|
||||
let started = target.is_some() || moved >= DOCK_DRAG_THRESHOLD;
|
||||
// Hold a floating panel where it was grabbed; a docked one by
|
||||
// its title bar.
|
||||
// Hold a floating window where it was grabbed; a docked
|
||||
// pallet (or one pulled out of a floating group) by its
|
||||
// title bar.
|
||||
let grab = if target.is_none() && started {
|
||||
match self.dock.float_rect(panel) {
|
||||
let whole = |f: &&crate::ui::dock::FloatPanel| window || f.group.panels.len() == 1;
|
||||
match self.dock.float_rect(panel).filter(whole) {
|
||||
Some(f) => iced::Vector::new(origin.x - f.x, origin.y - f.y),
|
||||
None => iced::Vector::new(
|
||||
(self.dock.width(panel, self.win_size.0) * 0.5).min(120.0),
|
||||
@@ -272,11 +356,11 @@ impl OpenCADStudio {
|
||||
grab
|
||||
};
|
||||
let target = if started {
|
||||
// A whole group only moves between group positions: it
|
||||
// cannot join another group or float.
|
||||
// A whole docked group moves between group positions or
|
||||
// floats; it cannot join another group.
|
||||
match (group, self.dock_drop_target(point, grab)) {
|
||||
(Some(_), found @ DropTarget::Edge { .. }) | (None, found) => Some(found),
|
||||
(Some(_), _) => None,
|
||||
(Some(_), DropTarget::Join { .. }) => None,
|
||||
(_, found) => Some(found),
|
||||
}
|
||||
} else {
|
||||
target
|
||||
@@ -284,6 +368,7 @@ impl OpenCADStudio {
|
||||
self.dock_drag = Some(DockDrag::Move {
|
||||
panel,
|
||||
group,
|
||||
window,
|
||||
origin: Some(origin),
|
||||
target,
|
||||
grab,
|
||||
@@ -322,12 +407,30 @@ impl OpenCADStudio {
|
||||
return;
|
||||
};
|
||||
let dx = point.x - last.x;
|
||||
self.dock.resize_float(
|
||||
panel,
|
||||
if from_left { f.w - dx } else { f.w + dx },
|
||||
f.h + point.y - last.y,
|
||||
from_left,
|
||||
);
|
||||
let (w, h) = (if from_left { f.w - dx } else { f.w + dx }, f.h + point.y - last.y);
|
||||
self.dock.resize_float(panel, w, h, from_left);
|
||||
}
|
||||
DockDrag::FloatSplit { panel, upper, lower } => {
|
||||
let (Some(last), Some(f)) = (last, self.dock.float_rect(panel)) else {
|
||||
return;
|
||||
};
|
||||
let total: f32 = self
|
||||
.dock_float_visible(f)
|
||||
.iter()
|
||||
.filter_map(|p| f.group.panels.iter().position(|q| q == p))
|
||||
.filter_map(|i| f.group.weights.get(i))
|
||||
.sum();
|
||||
let avail = f.h;
|
||||
if avail > 0.0 && total > 0.0 {
|
||||
let per_px = total / avail;
|
||||
self.dock.shift_float_split(
|
||||
panel,
|
||||
upper,
|
||||
lower,
|
||||
(point.y - last.y) * per_px,
|
||||
crate::ui::dock::GROUP_MIN_H * per_px,
|
||||
);
|
||||
}
|
||||
}
|
||||
DockDrag::LayerColumn => {
|
||||
let Some(last) = last else { return };
|
||||
@@ -362,13 +465,12 @@ impl OpenCADStudio {
|
||||
self.dock_drag = Some(drag);
|
||||
self.dock_drag_last = None;
|
||||
self.dock_edge_menu = None;
|
||||
self.dock_float_menu = None;
|
||||
if peek.is_some() {
|
||||
self.dock_peek = peek;
|
||||
}
|
||||
}
|
||||
|
||||
/// Bring `id` into view: show its group on its edge and, where it
|
||||
/// auto-hides, keep it revealed until the pointer leaves.
|
||||
/// Arm a delayed reveal (`Some`) or hide (`None`); it applies when its
|
||||
/// timer reports back unless a newer hover superseded it.
|
||||
fn dock_hover_arm(&mut self, target: Option<crate::ui::dock::PanelId>) -> iced::Task<Message> {
|
||||
@@ -386,6 +488,8 @@ impl OpenCADStudio {
|
||||
self.dock_hover_pending = None;
|
||||
}
|
||||
|
||||
/// Bring `id` into view: show its group on its edge and, where it
|
||||
/// auto-hides, keep it revealed until the pointer leaves.
|
||||
pub(crate) fn dock_reveal(&mut self, id: crate::ui::dock::PanelId) {
|
||||
self.dock.show_group_of(id);
|
||||
self.dock_peek = Some(id);
|
||||
@@ -486,13 +590,8 @@ impl OpenCADStudio {
|
||||
if !self.dock.is_placed(PanelId::Layers) {
|
||||
let (ww, wh) = self.dock_workspace_size();
|
||||
let (w, h) = self.dock_float_size(PanelId::Layers);
|
||||
self.dock.float(FloatPanel {
|
||||
id: PanelId::Layers,
|
||||
x: ((ww - w) * 0.5).max(0.0),
|
||||
y: ((wh - h) * 0.5).max(0.0),
|
||||
w,
|
||||
h,
|
||||
});
|
||||
let (x, y) = (((ww - w) * 0.5).max(0.0), ((wh - h) * 0.5).max(0.0));
|
||||
self.dock.float(FloatPanel::new(PanelId::Layers, x, y, w, h));
|
||||
self.save_config();
|
||||
}
|
||||
self.dock.raise_float(PanelId::Layers);
|
||||
@@ -642,11 +741,37 @@ impl OpenCADStudio {
|
||||
if let Some(f) = self.dock.float_rect(id) {
|
||||
return (f.w, f.h);
|
||||
}
|
||||
(self.dock.width(id, self.win_size.0), self.dock_default_float_h())
|
||||
}
|
||||
|
||||
/// Height a pallet or group floats at first: a comfortable share of the
|
||||
/// workspace height.
|
||||
pub(crate) fn dock_default_float_h(&self) -> f32 {
|
||||
let (_, avail) = self.dock_workspace_size();
|
||||
(
|
||||
self.dock.width(id, self.win_size.0),
|
||||
(avail * 0.6).clamp(crate::ui::dock::FLOAT_MIN_H, 520.0),
|
||||
)
|
||||
(avail * 0.6).clamp(crate::ui::dock::FLOAT_MIN_H, 520.0)
|
||||
}
|
||||
|
||||
/// The pallets floating window `f` draws, top → bottom: its open ones,
|
||||
/// less one being dragged out of it.
|
||||
pub(crate) fn dock_float_visible(
|
||||
&self,
|
||||
f: &crate::ui::dock::FloatPanel,
|
||||
) -> Vec<crate::ui::dock::PanelId> {
|
||||
let pulled = match self.dock_drag {
|
||||
Some(crate::ui::dock::DockDrag::Move {
|
||||
panel,
|
||||
window: false,
|
||||
target: Some(_),
|
||||
..
|
||||
}) => Some(panel),
|
||||
_ => None,
|
||||
};
|
||||
f.group
|
||||
.panels
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|p| self.dock_panel_visible(*p) && Some(*p) != pulled)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Where a dragged pallet lands if released at workspace point `p`:
|
||||
|
||||
Reference in new issue
Block a user