From 8e20bdfe2d2a5fcf8eb9905d59c2b5fb2939bad4 Mon Sep 17 00:00:00 2001 From: aiongg Date: Mon, 19 Oct 2026 04:40:50 +0000 Subject: [PATCH] keymap: panel menus, toggles and aliases on craft-keymap 6e136c9 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The importer marks Panel Menus items as panel-menu entries. - Show/Hide commands and the other show-and-hide toggles are marked as toggles, so Show and Hide names match only them. - The Preferences alias names its submenu (Preferences: General…). - The import test checks an alias match (Exit). --- Cargo.lock | 2 +- Cargo.toml | 2 +- crates/ui-egui/src/keys.rs | 8 +++++- crates/ui-egui/src/shortcut_import.rs | 37 +++++++++++++++++++-------- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 863e963d9..9743d4872 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -827,7 +827,7 @@ dependencies = [ [[package]] name = "craft-keymap" version = "0.1.0" -source = "git+https://github.com/aiongg/craft-libs?rev=66f22029ba71dad29dcda156f01f680763aa6659#66f22029ba71dad29dcda156f01f680763aa6659" +source = "git+https://github.com/aiongg/craft-libs?rev=6e136c9114d4461e791f0599c4976fc0c01e0048#6e136c9114d4461e791f0599c4976fc0c01e0048" dependencies = [ "egui", "serde", diff --git a/Cargo.toml b/Cargo.toml index 0936e75a1..776f89724 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ repository = "https://github.com/storytold/designcraft" # Shared writer; the immutable revision must be published before this consumer. craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "c3487320b2d1de657b860b3c8abb44e1f2b32bd8", version = "=0.1.0", default-features = false } # Shared keyboard bindings: chords, overrides, conflicts, egui dispatch. -craft-keymap = { git = "https://github.com/aiongg/craft-libs", rev = "66f22029ba71dad29dcda156f01f680763aa6659", version = "=0.1.0", features = ["egui"] } +craft-keymap = { git = "https://github.com/aiongg/craft-libs", rev = "6e136c9114d4461e791f0599c4976fc0c01e0048", version = "=0.1.0", features = ["egui"] } # internal designcraft-geom = { path = "crates/geom" } designcraft-color = { path = "crates/color" } diff --git a/crates/ui-egui/src/keys.rs b/crates/ui-egui/src/keys.rs index ee85a0400..8600dbae5 100644 --- a/crates/ui-egui/src/keys.rs +++ b/crates/ui-egui/src/keys.rs @@ -36,7 +36,7 @@ const PANEL_KEYS: &[(&str, &str)] = &[ /// Other names shortcut sets use for a command (InDesign's feature names), by command id. const IMPORT_ALIASES: &[(&str, &str)] = &[ ("app.quit", "Exit"), - ("app.preferences", "General"), + ("app.preferences", "Preferences: General…"), ("app.insertTableDialog", "Insert Table"), ("view.togglePresentation", "Presentation"), ("color.defaultFillStroke", "Apply Default Fill and Stroke Colors"), @@ -50,6 +50,11 @@ const IMPORT_ALIASES: &[(&str, &str)] = &[ ("window.previousDocument", "Previous Window"), ]; +/// Commands that show and hide something without "Show/Hide" in their label. A shortcut set may +/// name a toggle by what it does now (`Hide Hidden Characters`); the importer matches such names +/// only to toggles. +const TOGGLES: &[&str] = &["view.hiddenCharacters", "view.tagMarkers"]; + /// Extra menu paths for import, where an alias would leave two commands on one name: InDesign's /// Tools area has Apply Color, which is also `object.color`'s label. const IMPORT_PATHS: &[(&str, &[&str])] = &[("color.applyColor", &["Tools", "Apply Color"])]; @@ -146,6 +151,7 @@ fn build_catalogue() -> Vec { continue; } let id = b.command.id.as_str(); + b.is_toggle = b.label.starts_with("Show/Hide ") || TOGGLES.contains(&id); b.aliases.extend(IMPORT_ALIASES.iter().filter(|(c, _)| *c == id).map(|(_, a)| a.to_string())); b.paths.extend(IMPORT_PATHS.iter().filter(|(c, _)| *c == id).map(|(_, p)| p.iter().map(|s| s.to_string()).collect())); } diff --git a/crates/ui-egui/src/shortcut_import.rs b/crates/ui-egui/src/shortcut_import.rs index e413369d6..d5161bf65 100644 --- a/crates/ui-egui/src/shortcut_import.rs +++ b/crates/ui-egui/src/shortcut_import.rs @@ -145,20 +145,30 @@ fn strip_suffix_ignore_case<'a>(s: &'a str, suffix: &str) -> Option<&'a str> { } /// The set as `craft_keymap::import` reads it. An area named ` Menu` is that menu; the -/// Tools area is the Tools menu, where items named `… Tool` select tools. Items of other areas -/// (panel menus, text and tables, views) have no menu, so they match only by a one-segment name. -/// The keys are read with Mac modifier names when any key in the set uses one. +/// Tools area is the Tools menu, where items named `… Tool` select tools. Items of the Panel Menus +/// area name their panel first (`Character: All Caps`) and match a command under that panel. +/// Items of other areas (text and tables, views) have no menu, so they match only by a one-segment +/// name. The keys are read with Mac modifier names when any key in the set uses one. fn foreign(set: &ParsedSet) -> ForeignSet { + #[derive(Clone, Copy, Default)] + struct Area<'a> { + menu: Option<&'a str>, + tools: bool, + panel_menu: bool, + } let flavor = Flavor::detect(set.items.iter().flat_map(|i| &i.entries).map(|e| e.keys.as_str())); - let menus: Vec<(Option<&str>, bool)> = set + let areas: Vec = set .areas .iter() .map(|a| { let a = a.trim(); if a.eq_ignore_ascii_case("tools") { - (Some(a), true) + Area { menu: Some(a), tools: true, panel_menu: false } + } else if a.eq_ignore_ascii_case("panel menus") { + Area { panel_menu: true, ..Area::default() } } else { - (strip_suffix_ignore_case(a, " menu").map(str::trim).filter(|m| !m.is_empty() && !m.contains(':')), false) + let menu = strip_suffix_ignore_case(a, " menu").map(str::trim).filter(|m| !m.is_empty() && !m.contains(':')); + Area { menu, ..Area::default() } } }) .collect(); @@ -166,9 +176,12 @@ fn foreign(set: &ParsedSet) -> ForeignSet { .items .iter() .map(|item| { - let (menu, tools) = item.area.and_then(|a| menus.get(a)).copied().unwrap_or((None, false)); - let mut entry = ForeignEntry::new(menu, item.name.split(": ").map(str::trim)).at_line(item.line); - entry.is_tool = tools && strip_suffix_ignore_case(&item.name, " tool").is_some(); + let area = item.area.and_then(|a| areas.get(a)).copied().unwrap_or_default(); + let mut entry = ForeignEntry::new(area.menu, item.name.split(": ").map(str::trim)).at_line(item.line); + if area.panel_menu { + entry = entry.in_panel_menu(); + } + entry.is_tool = area.tools && strip_suffix_ignore_case(&item.name, " tool").is_some(); entry.keys = item.entries.iter().map(|e| ForeignKey::read(Some(&e.context), &e.keys, flavor)).collect(); entry }) @@ -313,6 +326,8 @@ mod tests { \tColor: Swatches --- Default: Shift+Ctrl+F2\r\n\ Product area : Tools\r\n\ \tPen Tool --- Default: Shift+Ctrl+F3\r\n\ + Product area : File Menu\r\n\ + \tExit --- Default: Ctrl+Alt+F12\r\n\ garbage line without a separator\r\n"; let r = menus::run_ui(&mut app, "window.importShortcuts", &json!({"text": set})).unwrap().unwrap(); assert_eq!(r["set"], "My Set"); @@ -343,9 +358,11 @@ mod tests { // A Window menu panel item and a Tools item apply to the panel and the tool. assert_eq!(crate::keys::shortcut(&app.keymap, &crate::keys::panel("swatches")).as_deref(), Some("Cmd+Shift+F2")); assert_eq!(crate::keys::shortcut(&app.keymap, &crate::keys::tool("pen")).as_deref(), Some("Cmd+Shift+F3")); + // InDesign's name for a command, as an alias. + assert_eq!(menus::shortcut_of(&app, "app.quit").as_deref(), Some("Cmd+Alt+F12")); // The numeric keypad key is bound as the main key of the same name, and reported. assert_eq!(r["approximated"][0]["item"], "File > Close", "{r}"); - assert_eq!(r["imported"], 6, "{r}"); + assert_eq!(r["imported"], 7, "{r}"); // Reset Shortcuts is the way back. menus::run_ui(&mut app, "window.resetShortcuts", &json!({})).unwrap().unwrap(); assert_eq!(menus::shortcut_of(&app, "app.palette").as_deref(), Some("Cmd+Enter"));