feat(xref): row-level context menu actions and unified split-button toolbar

Right-click actions bound to the row under the cursor, split-button toolbar groups with shared borders and hover states, and a notice when an operation needs a selection first.
This commit is contained in:
Karim Jerbi committed 2026-09-15 23:56:31 +01:00
1 parent 494a872e8d
commit 22a9bd602c
28 files changed
+352 -168

No files matched your search

+12 -4
View File
@@ -712,6 +712,8 @@ pub(super) struct OpenCADStudio {
/// with the last pointer position. Mirrors the Layers Name-column drag.
pub(crate) xref_col_drag: Option<usize>,
pub(crate) xref_col_last: Option<iced::Point>,
/// Table/lower-pane split divider drag in progress.
pub(crate) xref_split_drag: bool,
/// Docked Insert Block panel state (search, preview size, cached thumbnails).
pub(crate) block_palette: crate::ui::window::block_palette::BlockPalette,
/// Reference Manager palette state (display-only in Task 7).
@@ -2369,6 +2371,12 @@ pub enum Message {
XrefColMove(iced::Point),
/// Pointer released during a column drag.
XrefColRelease,
/// Start a table/lower-pane split divider drag.
XrefSplitGrab,
/// Pointer moved (panel space) during a split drag.
XrefSplitMove(iced::Point),
/// Pointer released during a split drag.
XrefSplitRelease,
/// Flip the palette's details/preview lower pane.
XrefManagerTogglePreview,
/// Toggle the Attach dropdown menu.
@@ -2389,10 +2397,9 @@ pub enum Message {
XrefManagerToggleExpand(u64),
/// Selection-scoped palette operation (detach/unload/reload/overlay/pathtype).
XrefManagerOp(crate::ui::window::xref_manager::XrefPaletteOp),
/// Details-pane "new path" draft text.
XrefManagerPathInput(String),
/// Apply the "new path" draft to the anchor entry.
XrefManagerPathApply,
/// Row-scoped palette operation: selects row `index`, then applies the
/// operation to it.
XrefRowOp(usize, crate::ui::window::xref_manager::XrefPaletteOp),
/// Prefill the command line for Find & Replace across references
/// (`XREF Path Find <old> <new>`); the CLI parses and runs it.
XrefFindReplacePrompt,
@@ -3772,6 +3779,7 @@ impl OpenCADStudio {
dock_drag_target: None,
xref_col_drag: None,
xref_col_last: None,
xref_split_drag: false,
show_file_tabs: true,
show_layout_tabs: true,
last_point: None,
+3 -37
View File
@@ -686,6 +686,7 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
self.dock_drag_target = None;
self.xref_col_drag = None;
self.xref_col_last = None;
self.xref_split_drag = false;
iced::Task::none()
}
}
@@ -908,6 +909,8 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
.collect()
};
if picked.is_empty() {
self.command_line
.push_info(crate::t!("Select a reference first.").as_ref());
return;
}
let needs_host = matches!(
@@ -1241,43 +1244,6 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
self.post_ref_op(i);
self.refresh_xref_manager();
}
/// Apply the details-pane path draft to the anchor entry (verbatim
/// store, same engine fn as `XREF Path`).
pub(crate) fn xref_manager_path_apply(&mut self) {
if cfg!(target_arch = "wasm32") {
self.command_line.push_error(crate::t!("Reference changes are not available on web — the reference list is read-only.").as_ref());
return;
}
let i = self.active_tab;
let anchor = self.xref_manager.anchor.and_then(|a| {
self.xref_manager
.entries
.get(a)
.filter(|_| !self.xref_manager.nested.contains(&a))
.map(|e| (e.key, e.name.clone()))
});
let Some((key, _)) = anchor else {
return;
};
let new_raw = self.xref_manager.path_input.clone();
if new_raw.is_empty() {
return;
}
self.push_undo_snapshot(i, "XREF-PATH");
match crate::io::xref::set_ref_path(&mut self.tabs[i].scene.document, key, &new_raw)
{
Ok(name) => {
self.command_line.push_output(crate::tf!(
"XREF: Path set for \"{}\" — Reload to apply.",
name
).as_ref());
self.post_ref_op(i);
}
Err(msg) => self.command_line.push_error(msg.as_str()),
}
self.refresh_xref_manager();
}
}
#[cfg(test)]
+23 -6
View File
@@ -2192,6 +2192,25 @@ impl OpenCADStudio {
self.xref_col_last = None;
Task::none()
}
Message::XrefSplitGrab => {
self.xref_split_drag = true;
self.xref_col_last = None;
Task::none()
}
Message::XrefSplitMove(p) => {
if self.xref_split_drag {
if let Some(last) = self.xref_col_last {
self.xref_manager.drag_table_by(p.y - last.y);
}
self.xref_col_last = Some(p);
}
Task::none()
}
Message::XrefSplitRelease => {
self.xref_split_drag = false;
self.xref_col_last = None;
Task::none()
}
Message::XrefManagerTogglePreview => {
self.xref_manager.show_preview ^= true;
Task::none()
@@ -2284,12 +2303,10 @@ impl OpenCADStudio {
self.xref_manager_op(op);
Task::none()
}
Message::XrefManagerPathInput(text) => {
self.xref_manager.path_input = text;
Task::none()
}
Message::XrefManagerPathApply => {
self.xref_manager_path_apply();
Message::XrefRowOp(index, op) => {
// Row-scoped op: select the row first, then run the op.
self.xref_manager.right_click_select(index);
self.xref_manager_op(op);
Task::none()
}
Message::XrefFindReplacePrompt => {
+2 -1
View File
@@ -1875,7 +1875,8 @@ bg={bg_ms:.1}ms n={view_count}"
let workspace: Element<'_, Message> = row(parts).width(Fill).height(Fill).into();
let any_dragging = self.dock_dragging.is_some();
let any_resizing = self.dock_resizing.is_some() || self.xref_col_drag.is_some();
let any_resizing =
self.dock_resizing.is_some() || self.xref_col_drag.is_some() || self.xref_split_drag;
let workspace = if any_dragging {
let id = self.dock_dragging.expect("guarded by any_dragging");
let side = self.dock_drag_target.map(|(s, _)| s).unwrap_or(