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
+10
-1
@@ -798,7 +798,16 @@ impl CommandLine {
|
||||
let mut col = column![].spacing(0).width(Length::Fill);
|
||||
for (idx, cmd) in matches.iter().enumerate() {
|
||||
let is_selected = idx == cursor;
|
||||
let row = button(text(cmd.clone()).size(11))
|
||||
// Every row keeps the icon's width so names line up.
|
||||
let icon: Element<'_, Message> =
|
||||
match crate::modules::registry::command_icon(cmd) {
|
||||
Some(bytes) => crate::ui::icons::semantic(bytes, 14.0),
|
||||
None => Space::new().width(14.0).into(),
|
||||
};
|
||||
let label = row![icon, text(cmd.clone()).size(11)]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center);
|
||||
let row = button(label)
|
||||
.on_press(Message::CommandSuggestionPick(cmd.clone()))
|
||||
.width(Length::Fill)
|
||||
.padding([2, 8])
|
||||
|
||||
@@ -314,6 +314,15 @@ pub fn semantic<'a, M: 'a>(bytes: &'static [u8], size: f32) -> Element<'a, M> {
|
||||
})
|
||||
}
|
||||
|
||||
/// [`semantic`] faded like disabled menu text.
|
||||
pub fn semantic_disabled<'a, M: 'a>(bytes: &'static [u8], size: f32) -> Element<'a, M> {
|
||||
Element::new(SemanticIcon {
|
||||
bytes,
|
||||
size,
|
||||
opacity: 0.42,
|
||||
})
|
||||
}
|
||||
|
||||
fn semantic_handle(bytes: &'static [u8], theme: &Theme) -> svg::Handle {
|
||||
let key = SemanticCacheKey {
|
||||
address: bytes.as_ptr() as usize,
|
||||
@@ -654,6 +663,14 @@ pub fn zoom_icon() -> &'static [u8] {
|
||||
NAV_ZOOM
|
||||
}
|
||||
|
||||
pub fn undo_icon() -> &'static [u8] {
|
||||
UNDO
|
||||
}
|
||||
|
||||
pub fn redo_icon() -> &'static [u8] {
|
||||
REDO
|
||||
}
|
||||
|
||||
/// Layer visibility icon bytes (on / off).
|
||||
pub fn layer_visible(visible: bool) -> &'static [u8] {
|
||||
if visible {
|
||||
@@ -709,6 +726,10 @@ pub fn themed_disabled_arrow_down<'a, M: 'a>(size: f32) -> Element<'a, M> {
|
||||
themed_disabled(TRI_DOWN, size)
|
||||
}
|
||||
|
||||
pub fn themed_disabled_arrow_right<'a, M: 'a>(size: f32) -> Element<'a, M> {
|
||||
themed_disabled(TRI_RIGHT, size)
|
||||
}
|
||||
|
||||
pub fn themed_home<'a, M: 'a>(size: f32) -> Element<'a, M> {
|
||||
themed(HOME, size)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -126,6 +126,15 @@ const PANELS: &[Panel] = &[
|
||||
},
|
||||
];
|
||||
|
||||
/// Command, label and icon of every tool in the expanded Draw and Modify
|
||||
/// panels.
|
||||
pub(crate) fn tools() -> impl Iterator<Item = (&'static str, &'static str, &'static [u8])> {
|
||||
PANELS
|
||||
.iter()
|
||||
.flat_map(|panel| panel.tools)
|
||||
.map(|tool| (tool.command, tool.label, tool.icon))
|
||||
}
|
||||
|
||||
fn panel_for_dropdown(id: &str) -> Option<Panel> {
|
||||
PANELS.iter().copied().find(|panel| {
|
||||
panel.id == id
|
||||
|
||||
@@ -22,6 +22,7 @@ use crate::ui::properties::{linetype_display_name, lw_options, LinetypeItem};
|
||||
|
||||
mod widgets;
|
||||
mod draw_panel;
|
||||
pub(crate) use draw_panel::tools as panel_tools;
|
||||
mod modify_panel;
|
||||
mod color_dropdown;
|
||||
use widgets::{StyleContext, *};
|
||||
|
||||
Reference in new issue
Block a user