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 <[email protected]>
This commit is contained in:
theironchefandClaude Sonnet 5.5 authored and bflatastic committed 2026-10-08 05:35:54 +07:00
1 parent 256e938fff
commit 746a271aeb
1 file changed
+48
+48
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());