From 689ad469991df361713e50601cba67de3c646ef3 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Mon, 12 Oct 2026 18:44:45 +0700 Subject: [PATCH] A just-opened dialog gives its focus to the first enabled field, not a greyed-out one --- crates/ui-egui/src/dialogs/mod.rs | 4 ++-- crates/ui-egui/src/dialogs/tests.rs | 31 +++++++++++++++++++++++++++++ crates/ui-egui/src/widgets.rs | 11 +++++----- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/crates/ui-egui/src/dialogs/mod.rs b/crates/ui-egui/src/dialogs/mod.rs index 21325b77..cd8b38e8 100644 --- a/crates/ui-egui/src/dialogs/mod.rs +++ b/crates/ui-egui/src/dialogs/mod.rs @@ -412,8 +412,8 @@ pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) { ui.set_max_width(spec.max_width.map_or(room, |w| w.min(room))); modal::heading(ui, &heading); ui.add_space(12.0); - // Just opened: its first field takes the keyboard focus, as in the reference app (type a - // value, press Enter). Only the body's fields can take it. + // Just opened: its first enabled field takes the keyboard focus, as in the reference app + // (type a value, press Enter). Only the body's fields can take it. if focus_first { ui.data_mut(|m| m.insert_temp(widgets::dialog_focus_flag(), true)); } diff --git a/crates/ui-egui/src/dialogs/tests.rs b/crates/ui-egui/src/dialogs/tests.rs index 36bc7ae4..7a9068aa 100644 --- a/crates/ui-egui/src/dialogs/tests.rs +++ b/crates/ui-egui/src/dialogs/tests.rs @@ -566,6 +566,37 @@ fn a_dialog_opened_by_a_click_focuses_its_first_field_each_time_it_opens() { } } +/// A greyed-out field can't hold the keyboard focus, so it doesn't take a just-opened dialog's +/// either: the focus waits for the first enabled field. Blend Options on Smooth Color shows its +/// steps greyed out; choosing Specified Steps enables the field and it takes the focus (type a +/// count, press Enter). +#[test] +fn a_dialogs_focus_skips_a_greyed_out_first_field() { + let mut app = app(); + 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(); + }; + let enter = egui::Event::Key { key: egui::Key::Enter, physical_key: None, pressed: true, repeat: false, modifiers: Default::default() }; + app.run("ui.blendOptions", json!({})).unwrap(); + app.ui.dialog.as_mut().unwrap().fields.insert("spacing".into(), json!("smooth")); + for _ in 0..3 { + frame(&mut app, vec![]); + } + assert!(ctx.memory(|m| m.focused()).is_none(), "nothing to type in yet"); + app.ui.dialog.as_mut().unwrap().fields.insert("spacing".into(), json!("steps")); + frame(&mut app, vec![]); + assert!(ctx.memory(|m| m.focused()).is_some(), "the steps field takes the focus once it is enabled"); + frame(&mut app, vec![egui::Event::Text("7".into())]); + frame(&mut app, vec![enter]); + assert!(app.ui.dialog.is_none()); + let info = app.run("object.blend.info", json!({})).unwrap(); + assert_eq!((info["spacing"].clone(), info["steps"].clone()), (json!("steps"), json!(7)), "{info}"); +} + /// #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] diff --git a/crates/ui-egui/src/widgets.rs b/crates/ui-egui/src/widgets.rs index 723d4388..093852b7 100644 --- a/crates/ui-egui/src/widgets.rs +++ b/crates/ui-egui/src/widgets.rs @@ -280,16 +280,17 @@ fn step_value(ui: &Ui, resp: &Response, buf: &mut String, read: impl Fn(&str) -> } /// The flag `dialogs::show` raises while it draws the body of a dialog that has just opened; the -/// first field drawn takes it ([`take_dialog_focus`]). +/// first enabled field drawn takes it ([`take_dialog_focus`]). pub(crate) fn dialog_focus_flag() -> egui::Id { egui::Id::new("dialog-focus-first-field") } -/// If field `id` (showing `text`) is the first field of a dialog that has just opened, give it the -/// keyboard focus with all of `text` selected: typing replaces the value and Enter applies it. +/// If field `id` (showing `text`) is the first enabled field of a dialog that has just opened, give +/// it the keyboard focus with all of `text` selected: typing replaces the value and Enter applies it. pub(crate) fn take_dialog_focus(ui: &Ui, id: egui::Id, text: &str) { - // A new window's first frame only measures its contents: nothing can take focus yet. - if ui.is_sizing_pass() || ui.data_mut(|d| d.remove_temp::(dialog_focus_flag())).is_none() { + // A new window's first frame only measures its contents: nothing can take focus yet. Nor can a + // greyed-out field (egui drops its focus): the flag stays for the first field that is enabled. + if ui.is_sizing_pass() || !ui.is_enabled() || ui.data_mut(|d| d.remove_temp::(dialog_focus_flag())).is_none() { return; } focus_field(ui, id, text);