From 746a271aeba4f3043d6221a58467830e66de58d0 Mon Sep 17 00:00:00 2001 From: theironchef <6278036+theironchef@users.noreply.github.com> Date: Wed, 7 Oct 2026 13:32:00 -0700 Subject: [PATCH 1/2] Tab no longer leaves the keyboard focus on a hidden field Tab is the Panels shortcut (show/hide all panels), but egui's own focus navigation read the same Tab press at the start of the frame and moved the keyboard focus on to a field. The shortcut handler returns early while a field has the keyboard, so after one Tab a second Tab, tool letters and Ctrl chords did nothing until a click or Esc, and in the Type tool the letter typed after a Tab was lost. When no field has the keyboard, the handler now cancels the focus move for that frame; in dialogs, the command palette and panel fields, Tab still moves between fields. Co-Authored-By: Claude Sonnet 5.5 --- crates/ui-egui/src/shortcuts.rs | 48 +++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/crates/ui-egui/src/shortcuts.rs b/crates/ui-egui/src/shortcuts.rs index f2daf869..b39cf895 100644 --- a/crates/ui-egui/src/shortcuts.rs +++ b/crates/ui-egui/src/shortcuts.rs @@ -228,6 +228,12 @@ pub fn handle(app: &mut VectorcraftApp, ctx: &egui::Context) { if typing { return; } + // Tab is ours when no field has the keyboard (it shows and hides the panels, or goes to the + // Type tool): left to egui, it would also move the focus on to a field, and every key after it + // would count as typing. + if ctx.input(|i| i.key_pressed(Key::Tab)) { + ctx.memory_mut(|m| m.move_focus(egui::FocusDirection::None)); + } // Type tool editing: text, IME composition and editing keys go to the tool. if app.session.tool_wants_text() { type_text(app, ctx); @@ -425,6 +431,48 @@ mod tests { out } + /// Tab shows and hides the panels, and in the Type tool it types a tab, without moving the + /// keyboard focus on to a field: the keys after it (a tool letter, the next letter typed) still + /// work. + #[test] + fn tab_leaves_the_keyboard_to_the_shortcuts_and_the_type_tool() { + let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 200, "height": 200})).unwrap(); + let ctx = egui::Context::default(); + let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1600.0, 850.0)); + let run = |app: &mut VectorcraftApp, events: Vec| { + let mut out = ctx.run_ui(egui::RawInput { events, screen_rect: Some(screen), ..Default::default() }, |ui| { + app.logic(ui.ctx()); + app.ui(ui); + }); + out.textures_delta.clear(); + }; + let press = |key| egui::Event::Key { key, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::NONE }; + for _ in 0..3 { + run(&mut app, vec![]); + } + assert!(app.ui.dock && app.ui.toolbar); + run(&mut app, vec![press(Key::Tab)]); + run(&mut app, vec![]); + assert!(!app.ui.dock && !app.ui.toolbar, "Tab hid the panels"); + run(&mut app, vec![press(Key::Tab)]); + run(&mut app, vec![]); + assert!(app.ui.dock && app.ui.toolbar, "a second Tab shows them again"); + run(&mut app, vec![press(Key::P), egui::Event::Text("p".into())]); + assert_eq!(app.session.tool_id(), "pen", "and a tool letter after it works"); + // The Type tool: a, Tab, b is one text. + app.select_tool("type"); + let view = app.view_info(); + for kind in [vectorcraft_tools::PointerKind::Down, vectorcraft_tools::PointerKind::Up] { + app.session.pointer(&vectorcraft_tools::PointerEvent::new(kind, 50.0, 50.0), view).unwrap(); + } + run(&mut app, vec![egui::Event::Text("a".into())]); + run(&mut app, vec![press(Key::Tab)]); + run(&mut app, vec![egui::Event::Text("b".into())]); + let doc = app.session.execute("document.inspect", &json!({})).unwrap(); + assert_eq!(doc["layers"][0]["children"][0]["name"], "a\tb"); + } + #[test] fn copy_and_paste_events_use_the_system_clipboard() { let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default()); From aad734e264ba4ea4e684a24a27c2b6b1ed987c22 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Thu, 8 Oct 2026 05:01:14 +0700 Subject: [PATCH 2/2] ROADMAP: Tab leaves the keyboard to the shortcuts (review of #366) Co-Authored-By: Claude Opus 5.5 --- ROADMAP.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ROADMAP.md b/ROADMAP.md index 12c91b81..a631fc66 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -103,6 +103,7 @@ grade by behaviour against `plan/illustrator/`, not by whether a menu item exist - Save / Don't Save / Cancel before closing or quitting with unsaved documents (tabs, Close, Close All, Quit, window close). - Four brightness themes, persistent preferences. - Dialogs open with their first field focused and its value selected: type a value and press Enter (Move, Rotate, Scale…). Number fields in panels and dialogs step with Up/Down (Shift: ten, Ctrl/Cmd: a tenth of their unit), applied at once. + - Tab shows and hides the panels (in the Type tool it types a tab) without moving the keyboard focus onto a field, so the next shortcut or letter still works; in dialogs and panel fields it moves between fields. - 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. - **Tools:** - **Selection:** Selection, Direct/Group Selection, Magic Wand, Lasso. Dragging a path segment with Direct Selection bends it when it's curved (its anchors stay) and moves its two anchors when it's straight, one undo step; the fill, Shift or Alt still pick the whole path (Alt-drag copies it).