From 0765bc9bcde6e8739452ca40831192b417dde95c Mon Sep 17 00:00:00 2001 From: bflatastic Date: Mon, 12 Oct 2026 17:37:45 +0700 Subject: [PATCH] Fix #1367: a dialog opened from a menu takes the keyboard focus every time A dialog opened by a click is drawn in the frame of that click, and in that frame egui takes the focus from any field the click missed. The first time a dialog's window shows, that frame only measures it, so its first field took the focus a frame later and kept it; every later time the field took it in the click's frame and lost it at once. The field now takes it the frame after the click. --- crates/ui-egui/src/dialogs/mod.rs | 7 +++++ crates/ui-egui/src/dialogs/tests.rs | 41 +++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/crates/ui-egui/src/dialogs/mod.rs b/crates/ui-egui/src/dialogs/mod.rs index 85dff13b..21325b77 100644 --- a/crates/ui-egui/src/dialogs/mod.rs +++ b/crates/ui-egui/src/dialogs/mod.rs @@ -383,6 +383,13 @@ pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) { } m.get_temp::(focus_id).unwrap_or(false) }); + // Not in the frame of a click (the one on the menu item that opened it): egui takes the focus + // from a field the click missed, so the field takes it a frame later (#1367). + let clicked = ctx.input(|i| i.pointer.any_click()); + if focus_first && clicked { + ctx.request_repaint(); + } + let focus_first = focus_first && !clicked; let spec = spec(&d.kind); if let Some(window) = spec.window { return window(app, ctx); diff --git a/crates/ui-egui/src/dialogs/tests.rs b/crates/ui-egui/src/dialogs/tests.rs index cfba1dcb..99465e77 100644 --- a/crates/ui-egui/src/dialogs/tests.rs +++ b/crates/ui-egui/src/dialogs/tests.rs @@ -525,6 +525,47 @@ fn a_dialog_opens_with_its_first_field_focused_so_typing_and_enter_apply() { assert_eq!(node.appearance.effects.last().map(|e| e.params["scaleH"].clone()), Some(json!(50.0))); } +/// #1367: a dialog opened by a click (its menu item) is drawn in the frame of that click, where egui +/// takes the focus from a field the click missed: its first field still has the focus, each time it +/// opens. +#[test] +fn a_dialog_opened_by_a_click_focuses_its_first_field_each_time_it_opens() { + let mut app = app(); + app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 40, "height": 20})).unwrap(); + let ctx = egui::Context::default(); + theme::install_fonts(&ctx); + theme::apply(&ctx, Default::default()); + let frame = |app: &mut VectorcraftApp, events: Vec| { + let mut out = ctx.run_ui(egui::RawInput { events, ..Default::default() }, |ui| show(app, ui.ctx())); + out.textures_delta.clear(); + }; + // A click away from the dialog, as on a menu item. + let click = || { + let (pos, button, modifiers) = (egui::pos2(5.0, 5.0), egui::PointerButton::Primary, egui::Modifiers::NONE); + let press = |pressed| egui::Event::PointerButton { pos, button, pressed, modifiers }; + vec![egui::Event::PointerMoved(pos), press(true), press(false)] + }; + type Open = fn(&mut VectorcraftApp); + let opens: [(&str, Open); 2] = [ + ("Convert to Shape", |app| drop(app.run("effect.dialog", json!({"effect": "convertToShape.rectangle"})))), + ("Rasterize", |app| crate::menus::invoke(app, "object.rasterize", json!({}))), + ]; + for (name, open) in opens { + for turn in 1..=3 { + open(&mut app); + assert!(app.ui.dialog.is_some(), "{name}"); + frame(&mut app, click()); + frame(&mut app, vec![]); + frame(&mut app, vec![]); + assert!(ctx.memory(|m| m.focused()).is_some(), "{name}, turn {turn}"); + cancel(&mut app); + // A frame without the dialog, as between two uses of it. + frame(&mut app, vec![]); + assert!(ctx.memory(|m| m.focused()).is_none(), "{name}, turn {turn}"); + } + } +} + /// #1000: a count typed in Object › Create Object Mosaic… reaches the command (a whole number typed /// in a command's dialog is an integer), so the mosaic has that many columns, not the 10 × 10 grid. #[test]