forked from 1b-agent/opencadstudio-upstream
feat(ui): show ribbon command icons in menus and suggestions
The ribbon definition beside each command stays the one source of its label and icon. One cached lookup (modules::registry::ribbon_commands) walks the core ribbon and the expanded Draw/Modify panels; the shortcut list, the right-click menu, the grip menu and the command-line suggestions read it. - Context menu: command rows carry their ribbon icon; Undo/Redo and Isolate use interface glyphs, Draw Order its ribbon icon; icons and the caret fade on disabled rows. - Grip menu: Stretch, Move, Rotate, Scale, Mirror and Copy icons. - Suggestions: an icon column, aligned when a command has none. - Shortcut list: dropdown entries were read as (label, command) though the ribbon stores (command, label), so they never got a label or icon. Reworked from #1449. Co-Authored-By: MarcelP102 <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
6b37476687
commit
7ed6a362dc
8 files changed
+195
-81
No files matched your search
@@ -50,8 +50,10 @@ pub enum SubmenuId {
|
||||
pub enum MenuIcon {
|
||||
Snap(SnapType),
|
||||
Mtp,
|
||||
Pan,
|
||||
Zoom,
|
||||
/// Monochrome interface glyph, drawn in the text colour.
|
||||
Chrome(&'static [u8]),
|
||||
/// A command's ribbon icon, drawn in its own colours.
|
||||
Tool(&'static [u8]),
|
||||
}
|
||||
|
||||
/// What a grip-menu row does (the grip-mode shortcut menu of commercial solutions).
|
||||
@@ -146,6 +148,12 @@ impl MenuItem {
|
||||
self
|
||||
}
|
||||
|
||||
/// The command's ribbon icon, when the ribbon has one.
|
||||
fn command_icon(mut self, command: &str) -> Self {
|
||||
self.icon = command_icon(command);
|
||||
self
|
||||
}
|
||||
|
||||
fn hint(mut self, hint: impl Into<String>) -> Self {
|
||||
self.hint = Some(hint.into());
|
||||
self
|
||||
@@ -179,11 +187,16 @@ pub enum MenuRow {
|
||||
Submenu {
|
||||
id: SubmenuId,
|
||||
label: String,
|
||||
icon: Option<MenuIcon>,
|
||||
items: Vec<MenuItem>,
|
||||
open: bool,
|
||||
},
|
||||
}
|
||||
|
||||
fn command_icon(command: &str) -> Option<MenuIcon> {
|
||||
crate::modules::registry::command_icon(command).map(MenuIcon::Tool)
|
||||
}
|
||||
|
||||
/// A keyboard-navigable row in display order: either an item or a submenu
|
||||
/// header. Separators and the children of collapsed submenus are skipped.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@@ -470,6 +483,7 @@ fn command_rows(
|
||||
rows.push(MenuRow::Submenu {
|
||||
id: SubmenuId::SnapOverrides,
|
||||
label: t!("Snap Overrides").into_owned(),
|
||||
icon: None,
|
||||
items: snap_override_items(),
|
||||
open: open_submenu == Some(SubmenuId::SnapOverrides),
|
||||
});
|
||||
@@ -486,6 +500,7 @@ fn recent_input_submenu(items: Vec<MenuItem>, open_submenu: Option<SubmenuId>) -
|
||||
MenuRow::Submenu {
|
||||
id: SubmenuId::RecentInput,
|
||||
label: t!("Recent Input").into_owned(),
|
||||
icon: None,
|
||||
items,
|
||||
open: open_submenu == Some(SubmenuId::RecentInput),
|
||||
}
|
||||
@@ -528,14 +543,14 @@ fn navigation_rows(transparent: bool) -> Vec<MenuRow> {
|
||||
vec![
|
||||
MenuRow::Item(
|
||||
MenuItem::new(t!("Pan").into_owned(), MenuAction::Command(format!("{prefix}PAN")))
|
||||
.icon(MenuIcon::Pan),
|
||||
.icon(MenuIcon::Chrome(crate::ui::icons::pan_icon())),
|
||||
),
|
||||
MenuRow::Item(
|
||||
MenuItem::new(
|
||||
t!("Zoom").into_owned(),
|
||||
MenuAction::Command(format!("{prefix}ZOOM DYNAMIC")),
|
||||
)
|
||||
.icon(MenuIcon::Zoom),
|
||||
.icon(MenuIcon::Chrome(crate::ui::icons::zoom_icon())),
|
||||
),
|
||||
]
|
||||
}
|
||||
@@ -552,7 +567,9 @@ fn idle_rows(
|
||||
props_open: bool,
|
||||
open_submenu: Option<SubmenuId>,
|
||||
) -> Vec<MenuRow> {
|
||||
let cmd = |label: String, name: &str| MenuItem::new(label, MenuAction::Command(name.to_string()));
|
||||
let cmd = |label: String, name: &str| {
|
||||
MenuItem::new(label, MenuAction::Command(name.to_string())).command_icon(name)
|
||||
};
|
||||
let mut rows = Vec::new();
|
||||
|
||||
// ── Repeat / Recent Input ──────────────────────────────────────────
|
||||
@@ -562,6 +579,7 @@ fn idle_rows(
|
||||
t!("Repeat %{last}", last = last.clone()).into_owned(),
|
||||
MenuAction::Command(last.to_uppercase()),
|
||||
)
|
||||
.command_icon(last)
|
||||
.default(),
|
||||
));
|
||||
}
|
||||
@@ -569,7 +587,9 @@ fn idle_rows(
|
||||
recent_cmds
|
||||
.iter()
|
||||
.take(RECENT_LIMIT)
|
||||
.map(|c| MenuItem::new(c.clone(), MenuAction::Command(c.to_uppercase())))
|
||||
.map(|c| {
|
||||
MenuItem::new(c.clone(), MenuAction::Command(c.to_uppercase())).command_icon(c)
|
||||
})
|
||||
.collect(),
|
||||
open_submenu,
|
||||
));
|
||||
@@ -579,6 +599,7 @@ fn idle_rows(
|
||||
rows.push(MenuRow::Submenu {
|
||||
id: SubmenuId::Clipboard,
|
||||
label: t!("Clipboard").into_owned(),
|
||||
icon: None,
|
||||
items: vec![
|
||||
cmd(t!("Cut").into_owned(), "CUTCLIP").enabled(has_selection),
|
||||
cmd(t!("Copy").into_owned(), "COPYCLIP").enabled(has_selection),
|
||||
@@ -602,19 +623,25 @@ fn idle_rows(
|
||||
None => t!("Redo").into_owned(),
|
||||
};
|
||||
rows.push(MenuRow::Item(
|
||||
MenuItem::new(undo, MenuAction::Undo).hint("Ctrl+Z").enabled(undo_label.is_some()),
|
||||
MenuItem::new(undo, MenuAction::Undo)
|
||||
.icon(MenuIcon::Chrome(crate::ui::icons::undo_icon()))
|
||||
.hint("Ctrl+Z")
|
||||
.enabled(undo_label.is_some()),
|
||||
));
|
||||
rows.push(MenuRow::Item(
|
||||
MenuItem::new(redo, MenuAction::Redo).hint("Ctrl+Y").enabled(redo_label.is_some()),
|
||||
MenuItem::new(redo, MenuAction::Redo)
|
||||
.icon(MenuIcon::Chrome(crate::ui::icons::redo_icon()))
|
||||
.hint("Ctrl+Y")
|
||||
.enabled(redo_label.is_some()),
|
||||
));
|
||||
rows.push(MenuRow::Separator);
|
||||
|
||||
// ── Selection edit block (the "Edit" shortcut menu of commercial solutions) ──
|
||||
if has_selection {
|
||||
rows.push(MenuRow::Item(MenuItem::new(
|
||||
t!("Erase").into_owned(),
|
||||
MenuAction::DeleteSelected,
|
||||
)));
|
||||
rows.push(MenuRow::Item(
|
||||
MenuItem::new(t!("Erase").into_owned(), MenuAction::DeleteSelected)
|
||||
.command_icon("ERASE"),
|
||||
));
|
||||
rows.push(MenuRow::Item(cmd(t!("Move").into_owned(), "MOVE")));
|
||||
rows.push(MenuRow::Item(cmd(t!("Copy Selection").into_owned(), "COPY")));
|
||||
rows.push(MenuRow::Item(cmd(t!("Scale").into_owned(), "SCALE")));
|
||||
@@ -623,6 +650,7 @@ fn idle_rows(
|
||||
rows.push(MenuRow::Submenu {
|
||||
id: SubmenuId::DrawOrder,
|
||||
label: t!("Draw Order").into_owned(),
|
||||
icon: command_icon("DRAWORDER_FRONT"),
|
||||
items: vec![
|
||||
cmd(t!("Bring to Front").into_owned(), "DRAWORDER F"),
|
||||
cmd(t!("Send to Back").into_owned(), "DRAWORDER B"),
|
||||
@@ -652,6 +680,7 @@ fn idle_rows(
|
||||
rows.push(MenuRow::Submenu {
|
||||
id: SubmenuId::Isolate,
|
||||
label: t!("Isolate").into_owned(),
|
||||
icon: Some(MenuIcon::Chrome(crate::ui::icons::ST_ISOLATE)),
|
||||
items: vec![
|
||||
cmd(t!("Isolate Objects").into_owned(), "ISOLATEOBJECTS").enabled(has_selection),
|
||||
cmd(t!("Hide Objects").into_owned(), "HIDEOBJECTS").enabled(has_selection),
|
||||
@@ -697,17 +726,29 @@ fn idle_rows(
|
||||
|
||||
// ── Panels ─────────────────────────────────────────────────────────
|
||||
rows.push(MenuRow::Item(
|
||||
MenuItem::new(t!("Properties").into_owned(), MenuAction::Properties).checked(props_open),
|
||||
MenuItem::new(t!("Properties").into_owned(), MenuAction::Properties)
|
||||
.command_icon("PROPERTIES")
|
||||
.checked(props_open),
|
||||
));
|
||||
rows.push(MenuRow::Item(
|
||||
MenuItem::new(t!("Options...").into_owned(), MenuAction::Options).command_icon("OPTIONS"),
|
||||
));
|
||||
rows.push(MenuRow::Item(MenuItem::new(
|
||||
t!("Options...").into_owned(),
|
||||
MenuAction::Options,
|
||||
)));
|
||||
rows
|
||||
}
|
||||
|
||||
fn grip_rows(grip: &GripMenuContext) -> Vec<MenuRow> {
|
||||
let g = |label: String, cmd: GripMenuCmd| MenuItem::new(label, MenuAction::Grip(cmd));
|
||||
let g = |label: String, cmd: GripMenuCmd| {
|
||||
let command = match cmd {
|
||||
GripMenuCmd::Stretch => "STRETCH",
|
||||
GripMenuCmd::Move => "MOVE",
|
||||
GripMenuCmd::Rotate => "ROTATE",
|
||||
GripMenuCmd::Scale => "SCALE",
|
||||
GripMenuCmd::Mirror => "MIRROR",
|
||||
GripMenuCmd::CopyToggle => "COPY",
|
||||
GripMenuCmd::BasePoint | GripMenuCmd::Undo | GripMenuCmd::Exit => "",
|
||||
};
|
||||
MenuItem::new(label, MenuAction::Grip(cmd)).command_icon(command)
|
||||
};
|
||||
vec![
|
||||
MenuRow::Item(MenuItem::new(t!("Enter").into_owned(), MenuAction::Enter).hint("⏎").default()),
|
||||
MenuRow::Separator,
|
||||
|
||||
Reference in new issue
Block a user