Keyboard Shortcuts: a shortcut on the Turkish Q quotation-mark key shows " and runs

Squashed from #4851 onto main.
This commit is contained in:
dotCooCoo
2026-10-31 19:43:16 +07:00
committed by bflatastic
parent f28de75a22
commit b13b3260cd
12 changed files with 213 additions and 33 deletions
+1 -1
View File
@@ -101,7 +101,7 @@ pub fn app_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
// Search → command palette (its shortcut opens it too).
if search {
let tip = match menus::shortcut_of("help.commandPalette") {
Some(sc) => format!("{} ({})", tl!("Search commands and tools"), menus::pretty_shortcut(sc)),
Some(sc) => format!("{} ({})", tl!("Search commands and tools"), menus::shown_shortcut(sc)),
None => tl!("Search commands and tools").to_string(),
};
if widgets::icon_button(ui, "search", &tip, app.ui.palette_open, icon).clicked() {
+2 -2
View File
@@ -214,11 +214,11 @@ pub(super) fn hint_segments(tool: &str) -> Vec<(Cow<'static, str>, bool)> {
}
match text.rsplit_once('+') {
Some((_, key)) if !key.is_empty() => {
let pretty = menus::pretty_shortcut(text);
let pretty = menus::shown_shortcut(text);
let shown = pretty.strip_suffix(key).map_or(pretty.clone(), |m| format!("{m}{}", tl!(key)));
(Cow::Owned(shown), bold)
}
_ => (Cow::Owned(menus::pretty_shortcut(tl!(text))), bold),
_ => (Cow::Owned(menus::shown_shortcut(tl!(text))), bold),
}
})
.collect()
+1 -1
View File
@@ -602,7 +602,7 @@ fn row(ui: &mut Ui, t: &Tokens, tool: &'static ToolInfo, on: bool, dimmed: bool)
let icon = Rect::from_center_size(rect.left_center() + vec2(8.0 + ICON / 2.0, 0.0), vec2(ICON, ICON));
icons::paint(ui, icons::tool_icon(tool.icon), icon, if dimmed || on { color } else { t.icon });
let painter = ui.painter().with_clip_rect(rect);
let shortcut = crate::shortcut_editor::tool_shortcut(tool.id);
let shortcut = crate::shortcut_editor::tool_shortcut(tool.id).map(crate::shortcut_editor::as_typed);
let keys_w = shortcut.as_ref().map_or(0.0, |k| {
let keys = if on {
t.text_strong
+1 -1
View File
@@ -62,7 +62,7 @@ pub(crate) use bar::{home_key, home_showing};
#[cfg(test)]
use bar::{menu_body, pointer_reaches_title};
pub use context::{context_items, context_menu_body, ruler_menu_body, ruler_unit_items};
pub use entries::{MenuEntry, Row, context_entries, entry, menu_entries, pretty_shortcut};
pub use entries::{MenuEntry, Row, context_entries, entry, menu_entries, pretty_shortcut, shown_shortcut};
pub(crate) use invoke::graph_dialog;
pub use invoke::{click_target, invoke, invoke_from_system_menu};
#[cfg(test)]
+2 -2
View File
@@ -1,4 +1,4 @@
use super::entries::{Row, entry, pretty_shortcut};
use super::entries::{Row, entry, shown_shortcut};
use super::invoke::{field_event, focused_text_field, invoke, to_field, with_held_keys};
use super::tree::menu_tree_named;
use super::{Entry, Item, Tokens, Value, VectorcraftApp, widgets};
@@ -124,7 +124,7 @@ pub(super) fn render_items(app: &VectorcraftApp, ui: &mut egui::Ui, items: &[Ite
/// One menu item: its label (with a check mark, or the room for one, under `checks`), its shortcut,
/// and Type › Font's samples.
fn render_row(app: &VectorcraftApp, ui: &mut egui::Ui, row: &Row, checks: bool, clicked: &mut Option<(String, Value)>) {
let sc = row.shortcut.map(pretty_shortcut).unwrap_or_default();
let sc = row.shortcut.map(shown_shortcut).unwrap_or_default();
let Some((id, p)) = row.command else {
ui.add_enabled_ui(false, |ui| {
ui.add(egui::Button::new(row.label.as_ref()).shortcut_text(sc));
+6
View File
@@ -88,6 +88,12 @@ pub fn pretty_shortcut(s: &str) -> String {
}
}
/// [`pretty_shortcut`] with the backtick key shown as the character it types on the layout in use
/// (`"` on Turkish Q), as Edit › Keyboard Shortcuts shows it.
pub fn shown_shortcut(s: &str) -> String {
pretty_shortcut(&crate::shortcut_editor::as_typed(s))
}
#[derive(Serialize)]
pub struct MenuEntry {
pub path: Vec<String>,
+1 -1
View File
@@ -115,7 +115,7 @@ pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
for (i, e) in matches.iter().enumerate() {
let resp = ui.add(
egui::Button::new(e.shown.as_str())
.shortcut_text(menus::pretty_shortcut(&e.shortcut))
.shortcut_text(menus::shown_shortcut(&e.shortcut))
.min_size(egui::vec2(500.0, 24.0))
.selected(i == sel),
);
+127 -12
View File
@@ -271,6 +271,57 @@ pub fn chord_from_event(key: Key, m: Modifiers) -> Option<String> {
crate::shortcuts::parse(&s).map(|_| s)
}
ui_mirror!(fn backtick_store() -> Option<String>);
/// Notes what the backtick key (left of 1) typed in `events`: the one-character text whose key, a
/// press of it without modifiers, comes right before it (egui-winit) or, when no press comes
/// before, right after it (eframe's web build with no text field focused). egui reports that key
/// as [`Key::Backtick`] on layouts where it types a character egui has no key for, such as `"` on
/// Turkish Q.
pub(crate) fn note_backtick_text(events: &[egui::Event]) {
let is_press = |e: Option<&egui::Event>| matches!(e, Some(egui::Event::Key { pressed: true, .. }));
let is_backtick =
|e: Option<&egui::Event>| matches!(e, Some(egui::Event::Key { key: Key::Backtick, pressed: true, modifiers, .. }) if modifiers.is_none());
let typed = events.iter().enumerate().rev().find_map(|(j, e)| match e {
egui::Event::Text(t) if t.chars().count() == 1 && !t.chars().any(char::is_whitespace) => {
let before = j.checked_sub(1).and_then(|j| events.get(j));
(is_backtick(before) || (!is_press(before) && is_backtick(events.get(j + 1)))).then(|| t.clone())
}
_ => None,
});
if let Some(t) = typed
&& let Ok(mut store) = backtick_store().write()
&& store.as_deref() != Some(t.as_str())
{
*store = Some(t);
}
}
/// The character the backtick key types on the layout in use, once a press of it has been seen,
/// when it is neither `` ` `` nor `~`.
pub(crate) fn backtick_text() -> Option<String> {
backtick_store().read().ok().and_then(|t| t.clone()).filter(|t| t != "`" && t != "~")
}
/// `text` as the editor shows it: an unshifted backtick key shows the character it types on the
/// layout in use (`"` on Turkish Q), so it reads as the key the user pressed.
fn on_layout(text: String, typed: Option<&str>) -> String {
let Some(typed) = typed else { return text };
match vc_chord(&text) {
Some(c) if c.key == craft_keymap::Key::Backtick && !c.mods.shift => match text.strip_suffix('~') {
Some(mods) => format!("{mods}{typed}"),
None => text,
},
_ => text,
}
}
/// `text` with the backtick key shown as the character it types on the layout in use
/// ([`backtick_text`]), as the editor shows it.
pub(crate) fn as_typed(text: &str) -> String {
on_layout(text.to_string(), backtick_text().as_deref())
}
// ---------- entries & conflicts ----------
#[derive(Clone, Debug)]
@@ -506,15 +557,17 @@ pub fn reset_one(overrides: &mut Overrides, key: &str) {
overrides.remove(&crate::keymap::stored_key(key));
}
/// Text shown for all active chords in a dialog row.
fn dialog_shortcuts_with(km: &Keymap, key: &str) -> Option<String> {
/// Text shown for all active chords in a dialog row, with the backtick key shown as `typed`
/// ([`backtick_text`]).
fn dialog_shortcuts_with(km: &Keymap, key: &str, typed: Option<&str>) -> Option<String> {
let shown = |s: String| menus::pretty_shortcut(&on_layout(s, typed));
let bindings = km.bindings(&command_of(key));
if bindings.len() > 1 {
let primary = effective_with(km, key).map(|s| menus::pretty_shortcut(&s))?;
let additional = bindings.iter().skip(1).map(|b| menus::pretty_shortcut(&crate::keymap::binding_text(b)));
let primary = effective_with(km, key).map(shown)?;
let additional = bindings.iter().skip(1).map(|b| shown(crate::keymap::binding_text(b)));
return Some(std::iter::once(primary).chain(additional).collect::<Vec<_>>().join(" · "));
}
effective_with(km, key).map(|s| menus::pretty_shortcut(&s))
effective_with(km, key).map(shown)
}
/// Overrides of a preset (both presets are the registry defaults for now).
@@ -698,6 +751,8 @@ pub fn is_recording(app: &VectorcraftApp) -> bool {
struct DialogView {
/// The dialog's `overrides` field this was built from.
source: Value,
/// The [`backtick_text`] the texts show.
backtick: Option<String>,
keymap: Keymap,
texts: Vec<Option<String>>,
conflicts: usize,
@@ -706,17 +761,19 @@ struct DialogView {
impl DialogView {
fn new(overrides: Overrides) -> DialogView {
let keymap = keymap_with(&overrides);
let texts = entries().iter().map(|e| dialog_shortcuts_with(&keymap, &e.key)).collect();
let backtick = backtick_text();
let texts = entries().iter().map(|e| dialog_shortcuts_with(&keymap, &e.key, backtick.as_deref())).collect();
let conflicts = conflicts_in(&keymap).len();
DialogView { source: stored::to_value(keymap.overrides()), keymap, texts, conflicts }
DialogView { source: stored::to_value(keymap.overrides()), backtick, keymap, texts, conflicts }
}
/// The view of `d`'s working set, from the cache unless the set changed. The dialog's
/// `overrides` field is left in its stored form.
/// The view of `d`'s working set, from the cache unless the set or the backtick key's
/// character changed. The dialog's `overrides` field is left in its stored form.
fn of(ctx: &egui::Context, d: &mut Dialog) -> Arc<DialogView> {
let id = egui::Id::unique("dialog-shortcuts-view");
let source = d.fields.get("overrides").unwrap_or(&Value::Null);
if let Some(v) = ctx.data(|m| m.get_temp::<Arc<DialogView>>(id)).filter(|v| v.source == *source) {
let backtick = backtick_text();
if let Some(v) = ctx.data(|m| m.get_temp::<Arc<DialogView>>(id)).filter(|v| v.source == *source && v.backtick == backtick) {
return v;
}
let v = Arc::new(DialogView::new(dialog_overrides(d)));
@@ -729,6 +786,7 @@ impl DialogView {
pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
let Some(mut d) = app.ui.dialog.clone() else { return };
let t = Tokens::get(ctx);
ctx.input(|i| note_backtick_text(&i.events));
// The working set, as a keymap: copied only by an edit this frame.
let view = DialogView::of(ctx, &mut d);
let mut km = Cow::Borrowed(&view.keymap);
@@ -823,7 +881,7 @@ pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
for (i, e) in entries().iter().enumerate().filter(|(_, e)| e.is_tool == tools && listed(km.overrides(), e)) {
let sc = match &km {
Cow::Borrowed(_) => view.texts.get(i).cloned().flatten(),
Cow::Owned(km) => dialog_shortcuts_with(km, &e.key),
Cow::Owned(km) => dialog_shortcuts_with(km, &e.key, view.backtick.as_deref()),
};
let matches = e.label.to_lowercase().contains(&q)
|| tl!(&e.label).to_lowercase().contains(&q)
@@ -1010,7 +1068,10 @@ fn record(d: &mut Dialog, km: &mut Keymap, key: &str, chord: &str) {
"__message".into(),
json!(crate::i18n::fmt(
tl!("⚠ {chord} was already used by {names} — it has been removed there."),
&[("chord", &menus::pretty_shortcut(chord)), ("names", &names.join(", "))]
&[
("chord", &menus::pretty_shortcut(&on_layout(chord.to_string(), backtick_text().as_deref()))),
("names", &names.join(", "))
]
)),
);
d.fields.insert("__conflict".into(), json!(first));
@@ -1126,6 +1187,60 @@ mod tests {
assert_eq!(crate::keymap::override_text(&dialog_overrides(&d), "tool:groupSelection").as_deref(), Some("Shift+A"));
}
/// On Turkish Q the key left of 1 types `"`, which egui reports as the backtick key: the
/// recorded shortcut is that key, saved as `~` as before, and its row shows `"`. On a US
/// layout the row still shows `~`.
#[test]
fn the_backtick_key_shows_what_it_types() {
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
open(&mut app);
let ctx = egui::Context::default();
theme::install_fonts(&ctx);
let at = entries().iter().position(|e| e.key == "edit.clear").unwrap();
let press = |key| egui::Event::Key { key, physical_key: Some(key), pressed: true, repeat: false, modifiers: Modifiers::NONE };
let text = |t: &str| egui::Event::Text(t.into());
let mut record_with = |events: Vec<egui::Event>| {
app.ui.dialog.as_mut().unwrap().fields.insert("__recording".into(), json!("edit.clear"));
let mut out = ctx.run_ui(egui::RawInput { events, ..Default::default() }, |ui| show(&mut app, ui.ctx()));
out.textures_delta.clear();
let d = app.ui.dialog.clone().unwrap();
let shown = DialogView::new(dialog_overrides(&d)).texts[at].clone();
(crate::keymap::override_text(&dialog_overrides(&d), "edit.clear"), shown)
};
assert_eq!(record_with(vec![press(Key::Backtick), text("\"")]), (Some("~".to_string()), Some("\"".to_string())));
assert_eq!(record_with(vec![press(Key::Backtick), text("`")]), (Some("~".to_string()), Some("~".to_string())));
// eframe's web build with no text field focused sends the text before its key.
assert_eq!(record_with(vec![text("\""), press(Key::Backtick)]), (Some("~".to_string()), Some("\"".to_string())));
// A text right after another key's press is that key's.
note_backtick_text(&[press(Key::Num2), text("'"), press(Key::Backtick)]);
assert_eq!(backtick_text().as_deref(), Some("\""));
note_backtick_text(&[text("`"), press(Key::Backtick)]);
assert_eq!(backtick_text(), None);
// Only the unshifted key types that character.
assert_eq!(on_layout("Shift+~".into(), Some("\"")), "Shift+~");
assert_eq!(on_layout("Cmd+~".into(), Some("\"")), "Cmd+\"");
assert_eq!(on_layout("Cmd+K".into(), Some("\"")), "Cmd+K");
}
#[test]
fn menus_and_tooltips_show_the_backtick_key_as_it_types() {
let mut ui = UiState::default();
crate::keymap::set_override(&mut ui.shortcut_overrides, "edit.clear", "Cmd+~");
crate::keymap::set_override(&mut ui.shortcut_overrides, "tool:measure", "~");
sync(&ui);
let measure = vectorcraft_tools::tool_info("measure").unwrap();
let press = |key| egui::Event::Key { key, physical_key: Some(key), pressed: true, repeat: false, modifiers: Modifiers::NONE };
let clear = || menus::shown_shortcut(menus::shortcut_of("edit.clear").unwrap());
note_backtick_text(&[press(Key::Backtick), egui::Event::Text("`".into())]);
assert_eq!(clear(), menus::pretty_shortcut("Cmd+~"));
assert!(crate::toolbar::tip(measure).ends_with("(~)"));
note_backtick_text(&[press(Key::Backtick), egui::Event::Text("\"".into())]);
assert_eq!(clear(), menus::pretty_shortcut("Cmd+\""));
assert!(crate::toolbar::tip(measure).ends_with("(\")"), "{}", crate::toolbar::tip(measure));
assert_eq!(menus::shown_shortcut("Shift+~"), menus::pretty_shortcut("Shift+~"));
sync(&UiState::default());
}
#[test]
fn saved_modifier_only_overrides_give_the_default_back() {
let mut ui = UiState::default();
+17 -11
View File
@@ -169,6 +169,7 @@ pub fn handle(app: &mut VectorcraftApp, ctx: &egui::Context) {
if !crate::take_frame_once(&mut app.shortcuts_frames, frame) {
return;
}
ctx.input(|i| crate::shortcut_editor::note_backtick_text(&i.events));
if !(ctx.input(|input| input.focused) || app.forwarded_frame && ctx.viewport_id() == egui::ViewportId::ROOT) {
crate::ime::controls_without_editor(app, ctx);
return;
@@ -357,25 +358,26 @@ fn remember_escape_pair(ctx: &egui::Context, key: Key, escape_pair: Option<bool>
fn single_key_shortcuts(app: &mut VectorcraftApp, ctx: &egui::Context) {
// Single-key tool shortcuts (no Cmd/Ctrl/Alt). With a layout without Latin letters (Persian,
// Arabic, Russian, Greek…) the key's position stands in for what it types (#793).
let events: Vec<(String, Modifiers)> = ctx.input(|i| {
// A text's key is the press that typed it ([`typing_presses`]).
let events: Vec<(String, Modifiers, Option<Key>)> = ctx.input(|i| {
let typed_by = typing_presses(&i.events);
i.events
.iter()
.zip(typed_by)
.filter_map(|(e, by)| match e {
egui::Event::Text(t) => {
let physical = match by.and_then(|k| i.events.get(k)) {
Some(egui::Event::Key { physical_key, .. }) => *physical_key,
_ => None,
let (physical, key) = match by.and_then(|k| i.events.get(k)) {
Some(egui::Event::Key { key, physical_key, .. }) => (*physical_key, Some(*key)),
_ => (None, None),
};
Some((latin(t, physical), i.modifiers))
Some((latin(t, physical), i.modifiers, key))
}
_ => None,
})
.collect()
});
let keymap = crate::shortcut_editor::current();
for (text, m) in events {
for (text, m, pressed) in events {
if m.command || m.alt || m.ctrl {
continue;
}
@@ -384,11 +386,15 @@ fn single_key_shortcuts(app: &mut VectorcraftApp, ctx: &egui::Context) {
// A shifted punctuation character can also be written with its Shift (the Curvature tool's
// Shift+~ arrives as `~`).
let shifted = m.shift.then(|| format!("Shift+{text}"));
let hits: Vec<CommandRef> = [Some(key), Some(text), shifted]
.into_iter()
.flatten()
.filter_map(|t| crate::keymap::vc_chord(&t).and_then(|c| crate::keymap::lookup(&keymap.keymap, &c)))
.collect();
let mut chords: Vec<craft_keymap::Chord> =
[Some(key), Some(text), shifted].into_iter().flatten().filter_map(|t| crate::keymap::vc_chord(&t)).collect();
// A character no chord names (`"`, which the backtick key types on Turkish Q) is read as
// the key that typed it, as the Keyboard Shortcuts editor records that press.
if chords.is_empty() {
let typed_by = pressed.and_then(|k| crate::shortcut_editor::chord_from_event(k, Modifiers { shift: m.shift, ..Modifiers::NONE }));
chords.extend(typed_by.and_then(|t| crate::keymap::vc_chord(&t)));
}
let hits: Vec<CommandRef> = chords.iter().filter_map(|c| crate::keymap::lookup(&keymap.keymap, c)).collect();
// Single-key command shortcuts (X, Shift+X, D, /, Shift+D, F, Shift+F by default) before
// tools.
let Some(hit) = hits.iter().find(|c| !c.id.starts_with("tool:")).or(hits.first()) else { continue };
+49
View File
@@ -1744,6 +1744,55 @@ fn a_repeated_keys_text_is_no_shortcut_press() {
assert_eq!(only_text.len(), 1);
}
/// A shortcut on the backtick key runs whatever character the key types: `"` on Turkish Q,
/// which egui reports as the backtick key, and `` ` `` on a US layout. A shortcut saved as `~`
/// is that key's.
#[test]
fn a_shortcut_on_the_backtick_key_runs_on_every_layout() {
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
app.session.execute("file.new", &json!({"width": 200, "height": 200})).unwrap();
let recorded = crate::shortcut_editor::chord_from_event(Key::Backtick, Modifiers::NONE).unwrap();
assert_eq!(recorded, "~");
crate::keymap::set_override(&mut app.ui.shortcut_overrides, "edit.clear", "~");
let count = |app: &VectorcraftApp| app.session.doc().unwrap().doc.layers[0].children().map_or(0, |c| c.len());
let press = egui::Event::Key { key: Key::Backtick, physical_key: Some(Key::Backtick), pressed: true, repeat: false, modifiers: Modifiers::NONE };
for typed in ["\"", "`"] {
let id = app.session.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 20, "height": 20})).unwrap()["id"].clone();
app.session.execute("select.set", &json!({"ids": [id]})).unwrap();
assert_eq!(count(&app), 1);
frame(&mut app, vec![press.clone(), egui::Event::Text(typed.into())]);
assert_eq!(count(&app), 0, "the key typing {typed} runs Edit › Clear");
}
// A character no key names, typed by a key with no shortcut, runs nothing.
let id = app.session.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 20, "height": 20})).unwrap()["id"].clone();
app.session.execute("select.set", &json!({"ids": [id]})).unwrap();
let num2 = egui::Event::Key { key: Key::Num2, physical_key: Some(Key::Num2), pressed: true, repeat: false, modifiers: Modifiers::NONE };
frame(&mut app, vec![num2, egui::Event::Text("\"".into())]);
assert_eq!(count(&app), 1);
}
/// eframe's web build with no text field focused sends the text before its key: the key after
/// the `"` it typed is read when no press comes right before it.
#[test]
fn a_shortcut_on_the_backtick_key_runs_when_its_text_comes_first() {
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
app.session.execute("file.new", &json!({"width": 200, "height": 200})).unwrap();
crate::keymap::set_override(&mut app.ui.shortcut_overrides, "edit.clear", "~");
let count = |app: &VectorcraftApp| app.session.doc().unwrap().doc.layers[0].children().map_or(0, |c| c.len());
let press = egui::Event::Key { key: Key::Backtick, physical_key: Some(Key::Backtick), pressed: true, repeat: false, modifiers: Modifiers::NONE };
let select_new = |app: &mut VectorcraftApp| {
let id = app.session.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 20, "height": 20})).unwrap()["id"].clone();
app.session.execute("select.set", &json!({"ids": [id]})).unwrap();
};
select_new(&mut app);
frame(&mut app, vec![egui::Event::Text("\"".into()), press.clone()]);
assert_eq!(count(&app), 0, "the key after the text typed it");
// An earlier press that is not next to the text does not stand in for its key.
select_new(&mut app);
frame(&mut app, vec![press, egui::Event::ModifiersChanged(Modifiers::NONE), egui::Event::Text("\"".into())]);
assert_eq!(count(&app), 1);
}
/// Shortcuts reach tools, panels (`window.panel {panel}`) and commands through the keymap,
/// and a held Cmd+= keeps zooming in.
#[test]
+5 -2
View File
@@ -68,7 +68,7 @@ pub const BASIC: &[(&str, &[&[&str]])] = &[
pub(crate) fn tip(t: &ToolInfo) -> String {
match crate::shortcut_editor::tool_shortcut(t.id) {
Some(s) => format!("{} ({})", tl!(t.label), s),
Some(s) => format!("{} ({})", tl!(t.label), crate::shortcut_editor::as_typed(s)),
None => tl!(t.label).to_string(),
}
}
@@ -694,7 +694,10 @@ const ROW_END: f32 = 18.0;
/// The name of `tool` in `lang` and its shortcut in brackets, as a flyout row draws them.
fn row_text(ui: &Ui, lang: crate::i18n::Lang, tool: &ToolInfo, color: Color32) -> RowText {
let lay = |s: String| ui.painter().layout_no_wrap(s, egui::FontId::proportional(13.0), color);
(lay(crate::i18n::tr(lang, tool.label).to_string()), crate::shortcut_editor::tool_shortcut(tool.id).map(|sc| lay(format!("({sc})"))))
(
lay(crate::i18n::tr(lang, tool.label).to_string()),
crate::shortcut_editor::tool_shortcut(tool.id).map(|sc| lay(format!("({})", crate::shortcut_editor::as_typed(sc)))),
)
}
/// The width of a flyout's rows: 250 pt, or what its longest name needs to stay clear of its
+1
View File
@@ -633,6 +633,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
- Shortcut sets other apps save as text (`.kys`) open in Import Shortcut Set from File › Open, from Edit › Keyboard Shortcuts › Import… in the desktop app, or from a drop on the window. It matches each entry to a tool, command or panel by name and decodes the file's key and modifier codes where VectorCraft's default shortcuts agree on them; the user answers the rest, and Apply fills the Keyboard Shortcuts dialog's working set. The answers are kept with the preferences for the next import (`shortcutSet.read`, `importMatches.*`).
- On Windows and Linux the app bar is the window's title bar (its own minimize, maximize and close buttons, drag to move, edges to resize), with the dragon app icon as the brand mark; dialogs and menus size to their content. As on a native menu bar, once one menu is open, moving the pointer onto another title opens that one.
- Edit › Keyboard Shortcuts lists items of the menu bar and panel menus, as the reference app's [Customize keyboard shortcuts](https://web.archive.org/web/20221205131209id_/https://helpx.adobe.com/illustrator/using/customizing-keyboard-shortcuts.html) page describes for its Menu Commands. A menu command with no default shortcut that needs a value a key press does not give, such as File › Document Setup › Units (`document.setUnits {units}`), is listed only while the user has changed its shortcut. A command stays listed when a menu bar item runs it without a value or when it opens a dialog run without one, as the Variables panel's commands do.
- A shortcut on the key left of 1 runs whatever character that key types, and Edit › Keyboard Shortcuts, the menus in the window, the command palette and tool tips show it as that character once the key has been pressed: `"` on a Turkish Q layout, `~` on a US layout as before. Shortcuts saved as `~` keep working (#4559).
- On Windows a maximized window stays inside the work area with the taskbar on any edge (#3242, #3723).
- On Windows a maximized window stops one pixel short of each monitor edge where an auto-hidden taskbar sits.
- **Tools:**