Merge pull request #1395 from storytold/dialog-focus-first-enabled

A just-opened dialog gives its focus to the first enabled field, not a greyed-out one
This commit is contained in:
BFlat 2026-10-12 19:30:53 +07:00 committed by GitHub
commit e22a11eda8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 39 additions and 7 deletions

View File

@ -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));
}

View File

@ -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<egui::Event>| {
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]

View File

@ -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::<bool>(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::<bool>(dialog_focus_flag())).is_none() {
return;
}
focus_field(ui, id, text);