mirror of
https://github.com/storytold/gridcraft.git
synced 2026-11-03 00:41:56 -06:00
fix(ui): share verified shortcuts between palette and ribbon
This commit is contained in:
@@ -28,7 +28,7 @@ pub(super) fn command_search(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dial
|
||||
.into_iter()
|
||||
.filter(|c| c.enabled)
|
||||
.filter(|c| q.is_empty() || c.label.to_ascii_lowercase().contains(&q) || c.id.to_ascii_lowercase().contains(&q))
|
||||
.map(|c| (c.id.to_string(), c.label.to_string(), c.menu.join(" › "), command_search_shortcut(c.id, mac)))
|
||||
.map(|c| (c.id.to_string(), c.label.to_string(), c.menu.join(" › "), crate::widgets::command_shortcut(c.id, mac)))
|
||||
.take(40)
|
||||
.collect();
|
||||
|
||||
@@ -69,7 +69,7 @@ pub(super) fn command_search(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dial
|
||||
Some(keys) => format!("{label} ({keys}) {menu}"),
|
||||
None => format!("{label} {menu}"),
|
||||
};
|
||||
let r = ui.add(egui::Button::selectable(i == d.list_index, text).truncate().min_size(vec2(scroll_area_width, 20.0)));
|
||||
let r = ui.add(egui::Button::selectable(i == d.list_index, text.clone()).truncate().min_size(vec2(scroll_area_width, 20.0)));
|
||||
|
||||
if (down || up || home || end || pgup || pgdn) && i == d.list_index && !ui.clip_rect().contains_rect(r.rect) {
|
||||
r.scroll_to_me(Some(if down || end || pgdn { egui::Align::Max } else { egui::Align::Min }));
|
||||
@@ -80,7 +80,7 @@ pub(super) fn command_search(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dial
|
||||
*confirm = true;
|
||||
}
|
||||
|
||||
r.on_hover_text(id);
|
||||
r.on_hover_text(format!("{text}\n{id}"));
|
||||
}
|
||||
});
|
||||
if ui.input(|i| i.key_pressed(Key::Enter)) {
|
||||
@@ -92,40 +92,3 @@ pub(super) fn command_search(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dial
|
||||
app.run_or_alert(&id, json!({}));
|
||||
}
|
||||
}
|
||||
|
||||
/// Only advertises shortcuts verified against the grid keymap and ribbon.
|
||||
/// Commands without a confirmed binding are shown without a shortcut.
|
||||
fn command_search_shortcut(id: &str, mac: bool) -> Option<&'static str> {
|
||||
let (mac_keys, other_keys) = match id {
|
||||
"file.new" => ("⌘N", Some("Ctrl+N")),
|
||||
"file.open" => ("⌘O", Some("Ctrl+O")),
|
||||
"file.save" => ("⌘S", Some("Ctrl+S")),
|
||||
"file.saveAs" => ("⌘⇧S", Some("Ctrl+Shift+S")),
|
||||
"file.close" => ("⌘W", Some("Ctrl+W")),
|
||||
"edit.copy" => ("⌘C", Some("Ctrl+C")),
|
||||
"edit.cut" => ("⌘X", Some("Ctrl+X")),
|
||||
"edit.paste" => ("⌘V", Some("Ctrl+V")),
|
||||
"edit.undo" => ("⌘Z", Some("Ctrl+Z")),
|
||||
"edit.redo" => ("⌘Y", Some("Ctrl+Y")),
|
||||
"edit.selectAll" => ("⌘A", Some("Ctrl+A")),
|
||||
"edit.fillDown" => ("⌘D", Some("Ctrl+D")),
|
||||
"edit.fillRight" => ("⌘R", Some("Ctrl+R")),
|
||||
"home.bold" => ("⌘B", Some("Ctrl+B")),
|
||||
"home.italic" => ("⌘I", Some("Ctrl+I")),
|
||||
"home.underline" => ("⌘U", Some("Ctrl+U")),
|
||||
"formulas.autoSum" => ("⌘⇧T", Some("Alt+=")),
|
||||
"insert.table" => ("⌘T", Some("Ctrl+T")),
|
||||
"data.filter" => ("⌘⇧F", Some("Ctrl+Shift+L")),
|
||||
"edit.flashFill" => ("Control+E", Some("Ctrl+E")),
|
||||
"edit.pasteSpecial" => ("Ctrl+Cmd+V", Some("Ctrl+Alt+V")),
|
||||
"home.alignLeft" => ("⌘L", None),
|
||||
"home.alignCenter" => ("⌘E", None),
|
||||
"selection.row" => ("Shift+Space", Some("Shift+Space")),
|
||||
"selection.column" => ("Control+Space", Some("Ctrl+Space")),
|
||||
"view.showObjects" => ("Ctrl+6", Some("Ctrl+6")),
|
||||
"formulas.showFormulas" => ("⌘`", Some("Ctrl+`")),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
if mac { Some(mac_keys) } else { other_keys }
|
||||
}
|
||||
|
||||
@@ -15,39 +15,59 @@ fn tr<'a>(ui: &Ui, text: &'a str) -> &'a str {
|
||||
i18n::current(ui.ctx()).tr(text)
|
||||
}
|
||||
|
||||
/// Keyboard shortcuts shown after a ribbon tooltip's command name: Excel for Mac's on macOS,
|
||||
/// Excel for Windows' elsewhere (each one GridCraft binds). Only the name is translated.
|
||||
const TOOLTIP_SHORTCUTS: &[(&str, &str, &str)] = &[
|
||||
("Save", "⌘S", "Ctrl+S"),
|
||||
("Undo", "⌘Z", "Ctrl+Z"),
|
||||
("Redo", "⌘Y", "Ctrl+Y"),
|
||||
("Paste", "⌘V", "Ctrl+V"),
|
||||
("Cut", "⌘X", "Ctrl+X"),
|
||||
("Copy", "⌘C", "Ctrl+C"),
|
||||
("Bold", "⌘B", "Ctrl+B"),
|
||||
("Italic", "⌘I", "Ctrl+I"),
|
||||
("Underline", "⌘U", "Ctrl+U"),
|
||||
("AutoSum", "⌘⇧T", "Alt+="),
|
||||
("Table", "⌘T", "Ctrl+T"),
|
||||
("Insert Link", "⌘K", "Ctrl+K"),
|
||||
("Filter", "⌘⇧F", "Ctrl+Shift+L"),
|
||||
("Flash Fill", "⌘E", "Ctrl+E"),
|
||||
// The Control key, written the way each platform names it. macOS's ⌃ is supplied by its
|
||||
// system fonts; elsewhere the Windows spelling avoids a glyph no bundled face has.
|
||||
("Show Formulas", "⌃`", "Ctrl+`"),
|
||||
/// Verified shortcut labels shared by Ribbon tooltips and Command Palette.
|
||||
/// Each entry is (command ID, optional Ribbon tooltip, macOS keys, other OS keys).
|
||||
/// The dialog ID is included because Insert Link opens a UI dialog directly.
|
||||
const SHORTCUTS: &[(&str, &str, &str, Option<&str>)] = &[
|
||||
("file.new", "", "⌘N", Some("Ctrl+N")),
|
||||
("file.open", "", "⌘O", Some("Ctrl+O")),
|
||||
("file.save", "Save", "⌘S", Some("Ctrl+S")),
|
||||
("file.saveAs", "", "⌘⇧S", Some("Ctrl+Shift+S")),
|
||||
("file.close", "", "⌘W", Some("Ctrl+W")),
|
||||
("edit.copy", "Copy", "⌘C", Some("Ctrl+C")),
|
||||
("edit.cut", "Cut", "⌘X", Some("Ctrl+X")),
|
||||
("edit.paste", "Paste", "⌘V", Some("Ctrl+V")),
|
||||
("edit.undo", "Undo", "⌘Z", Some("Ctrl+Z")),
|
||||
("edit.redo", "Redo", "⌘Y", Some("Ctrl+Y")),
|
||||
("edit.selectAll", "", "⌘A", Some("Ctrl+A")),
|
||||
("edit.fillDown", "", "⌘D", Some("Ctrl+D")),
|
||||
("edit.fillRight", "", "⌘R", Some("Ctrl+R")),
|
||||
("home.bold", "Bold", "⌘B", Some("Ctrl+B")),
|
||||
("home.italic", "Italic", "⌘I", Some("Ctrl+I")),
|
||||
("home.underline", "Underline", "⌘U", Some("Ctrl+U")),
|
||||
("formulas.autoSum", "AutoSum", "⌘⇧T", Some("Alt+=")),
|
||||
("insert.table", "Table", "⌘T", Some("Ctrl+T")),
|
||||
("dialog:insertLink", "Insert Link", "⌘K", Some("Ctrl+K")),
|
||||
("data.filter", "Filter", "⌘⇧F", Some("Ctrl+Shift+L")),
|
||||
("edit.flashFill", "Flash Fill", "Control+E", Some("Ctrl+E")),
|
||||
("edit.pasteSpecial", "", "Ctrl+Cmd+V", Some("Ctrl+Alt+V")),
|
||||
("home.alignLeft", "", "⌘L", None),
|
||||
("home.alignCenter", "", "⌘E", None),
|
||||
("selection.row", "", "Shift+Space", Some("Shift+Space")),
|
||||
("selection.column", "", "Control+Space", Some("Ctrl+Space")),
|
||||
("view.showObjects", "", "Control+6", Some("Ctrl+6")),
|
||||
("formulas.showFormulas", "Show Formulas", "⌘`", Some("Ctrl+`")),
|
||||
];
|
||||
|
||||
/// The shortcut a ribbon tooltip shows after `tip`, for this platform (`mac`).
|
||||
pub fn tooltip_shortcut(tip: &str, mac: bool) -> Option<&'static str> {
|
||||
TOOLTIP_SHORTCUTS.iter().find(|(name, _, _)| *name == tip).map(|(_, m, other)| if mac { *m } else { *other })
|
||||
/// Returns a verified platform-specific shortcut for a command ID.
|
||||
pub fn command_shortcut(id: &str, mac: bool) -> Option<&'static str> {
|
||||
SHORTCUTS.iter().find(|(command, _, _, _)| *command == id).and_then(|(_, _, mac_keys, other)| if mac { Some(*mac_keys) } else { *other })
|
||||
}
|
||||
|
||||
/// Returns the same shortcut for a Ribbon tooltip label.
|
||||
pub fn tooltip_shortcut(tip: &str, mac: bool) -> Option<&'static str> {
|
||||
SHORTCUTS.iter().find(|(_, label, _, _)| !label.is_empty() && *label == tip).and_then(
|
||||
|(_, _, mac_keys, other)| {
|
||||
if mac { Some(*mac_keys) } else { *other }
|
||||
},
|
||||
)
|
||||
}
|
||||
/// A ribbon tooltip: the translated command name, then its shortcut for this platform, as Excel
|
||||
/// shows them ("Bold (Ctrl+B)" on Windows, "Bold (⌘B)" on macOS).
|
||||
fn tip_text(ui: &Ui, tip: &str) -> String {
|
||||
// A name shared with a two-line ribbon label (Czech "Automatický\nsoučet") reads as one line.
|
||||
let name = tr(ui, tip).replace('\n', " ");
|
||||
let text = match tooltip_shortcut(tip, cfg!(target_os = "macos")) {
|
||||
let text = match tooltip_shortcut(tip, ui.ctx().os() == egui::os::OperatingSystem::Mac) {
|
||||
Some(keys) => format!("{name} ({keys})"),
|
||||
None => name,
|
||||
};
|
||||
@@ -714,9 +734,17 @@ mod tooltip_shortcut_tests {
|
||||
assert_eq!(tooltip_shortcut("Filter", true), Some("⌘⇧F"));
|
||||
// Search commands has no shortcut of its own (Ctrl+Shift+Space selects all).
|
||||
assert_eq!(tooltip_shortcut("Search commands", false), None);
|
||||
for (name, mac, other) in super::TOOLTIP_SHORTCUTS {
|
||||
assert!(!other.contains(['⌘', '⇧', '⌥']), "{name}: {other}");
|
||||
assert!(mac.starts_with('⌘') || mac.starts_with('⌃'), "{name}: {mac}");
|
||||
for (id, tip, mac, other) in super::SHORTCUTS {
|
||||
assert!(!mac.is_empty(), "{id}: empty macOS mapping");
|
||||
if let Some(keys) = other {
|
||||
assert!(!keys.contains(['⌘', '⇧', '⌥']), "{id}: {keys}");
|
||||
}
|
||||
assert_eq!(super::command_shortcut(id, true), Some(*mac), "{id}");
|
||||
assert_eq!(super::command_shortcut(id, false), *other, "{id}");
|
||||
if !tip.is_empty() {
|
||||
assert_eq!(tooltip_shortcut(tip, true), Some(*mac), "{id}");
|
||||
assert_eq!(tooltip_shortcut(tip, false), *other, "{id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,3 +194,38 @@ fn command_search_selection_shortcuts_match_runtime_os() {
|
||||
h.get_by_label_contains(column_label);
|
||||
}
|
||||
}
|
||||
|
||||
/// Ribbon tooltips and the Command Palette read the same shortcut metadata.
|
||||
#[test]
|
||||
fn command_palette_and_ribbon_tooltips_share_shortcuts() {
|
||||
use gridcraft_ui_egui::widgets::{command_shortcut, tooltip_shortcut};
|
||||
|
||||
for (id, tip) in [
|
||||
("file.save", "Save"),
|
||||
("edit.undo", "Undo"),
|
||||
("edit.redo", "Redo"),
|
||||
("edit.copy", "Copy"),
|
||||
("edit.cut", "Cut"),
|
||||
("edit.paste", "Paste"),
|
||||
("home.bold", "Bold"),
|
||||
("home.italic", "Italic"),
|
||||
("home.underline", "Underline"),
|
||||
("formulas.autoSum", "AutoSum"),
|
||||
("insert.table", "Table"),
|
||||
("data.filter", "Filter"),
|
||||
("edit.flashFill", "Flash Fill"),
|
||||
("formulas.showFormulas", "Show Formulas"),
|
||||
] {
|
||||
for mac in [false, true] {
|
||||
assert_eq!(command_shortcut(id, mac), tooltip_shortcut(tip, mac), "{id} and {tip} diverged on mac={mac}");
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(command_shortcut("edit.flashFill", true), Some("Control+E"));
|
||||
assert_eq!(command_shortcut("home.alignCenter", true), Some("⌘E"));
|
||||
assert_eq!(command_shortcut("home.alignCenter", false), None);
|
||||
assert_eq!(command_shortcut("formulas.showFormulas", true), Some("⌘`"));
|
||||
assert_eq!(command_shortcut("edit.pasteSpecial", false), Some("Ctrl+Alt+V"));
|
||||
assert_eq!(command_shortcut("selection.row", false), Some("Shift+Space"));
|
||||
assert_eq!(command_shortcut("unverified.command", false), None);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user