diff --git a/crates/ui-egui/src/shortcut_box.rs b/crates/ui-egui/src/shortcut_box.rs index 34c46a9b..ca3da74d 100644 --- a/crates/ui-egui/src/shortcut_box.rs +++ b/crates/ui-egui/src/shortcut_box.rs @@ -23,6 +23,10 @@ pub struct ShortcutBox { index: usize, /// What is typed. pub text: String, + /// Ask the field for focus on the next frame. Set when the box opens, then cleared. + /// Requesting focus on every frame restarts the text edit's held key, so a Wayland + /// repeat never sees the release and the key keeps repeating after the app loses focus (#724). + focus: bool, /// Most recent first. pub recent: Vec, pub pinned: Vec, @@ -119,6 +123,7 @@ pub fn open(app: &mut SolveApp, at: Option) { app.sbox.at = at; app.sbox.index = 0; app.sbox.text.clear(); + app.sbox.focus = true; } fn run(app: &mut SolveApp, id: &str) { @@ -132,10 +137,15 @@ fn run(app: &mut SolveApp, id: &str) { pub fn show(app: &mut SolveApp, ctx: &egui::Context) { if !app.ui.palette_open { app.sbox.at = None; + app.sbox.focus = false; return; } let t = Tokens::get(); let screen = ctx.content_rect(); + // Opened without `open` (a feature edit that falls through to the box): focus once. + if app.sbox.at.is_none() { + app.sbox.focus = true; + } let at = *app.sbox.at.get_or_insert_with(|| ctx.input(|i| i.pointer.latest_pos()).unwrap_or(screen.center())); let list = search(app, &app.sbox.text); let (up, down, enter, esc) = ctx.input(|i| { @@ -181,7 +191,9 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) { .desired_width(W - 20.0) .id(egui::Id::new("sc_sbox_text")), ); - te.request_focus(); + if std::mem::take(&mut app.sbox.focus) { + te.request_focus(); + } if te.changed() { app.sbox.index = 0; } @@ -281,6 +293,35 @@ mod tests { assert_eq!(score("xyz", "Extrude"), 0); } + fn frame(app: &mut SolveApp, ctx: &egui::Context) { + let raw = egui::RawInput { screen_rect: Some(egui::Rect::from_min_size(Pos2::ZERO, vec2(800.0, 600.0))), ..Default::default() }; + let mut out = ctx.run_ui(raw, |ui| show(app, ui.ctx())); + out.textures_delta.clear(); + } + + /// The field takes focus when the box opens and is not asked again on later frames: asking on + /// every frame restarts the text edit's held key, which kept a Wayland key repeat running (#724). + #[test] + fn the_search_field_asks_for_focus_once_per_opening() { + let mut app = SolveApp::new(solidcraft_engine::Session::default(), Default::default()); + let ctx = egui::Context::default(); + let field = egui::Id::new("sc_sbox_text"); + open(&mut app, None); + frame(&mut app, &ctx); + frame(&mut app, &ctx); + assert!(ctx.memory(|m| m.has_focus(field)), "the field has focus once the box opens"); + ctx.memory_mut(|m| m.surrender_focus(field)); + frame(&mut app, &ctx); + frame(&mut app, &ctx); + assert!(!ctx.memory(|m| m.has_focus(field)), "the field asked for focus again while open"); + app.ui.palette_open = false; + frame(&mut app, &ctx); + open(&mut app, None); + frame(&mut app, &ctx); + frame(&mut app, &ctx); + assert!(ctx.memory(|m| m.has_focus(field)), "reopening the box focuses the field again"); + } + #[test] fn search_finds_by_name_id_and_panel_and_puts_recent_first() { let mut app = SolveApp::new(solidcraft_engine::Session::default(), Default::default());