Merge pull request #366 from theironchef/fix/tab-keeps-shortcuts-working

Tab no longer leaves the keyboard focus on a hidden field
This commit is contained in:
BFlat 2026-10-08 06:01:47 +07:00 committed by GitHub
commit 831a5e5c96
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 49 additions and 0 deletions

View File

@ -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).

View File

@ -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<egui::Event>| {
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());