From e6992a855d41cfda79483b5656a888213385ca8d Mon Sep 17 00:00:00 2001 From: David Mann Date: Sun, 8 Nov 2026 00:17:09 +0100 Subject: [PATCH] Background Eraser: a Tolerance set elsewhere wins over an interrupted edit (#11909) The typed-text draft remembers the setting it was stored with and is only used while that setting is unchanged, so a value set meanwhile (a tool preset while the options bar was hidden) shows from the first frame. Co-Authored-By: Claude Opus 5.5 --- ...background_eraser_tolerance_field_tests.rs | 29 +++++++++++++++++++ crates/ui-egui/src/tools/eraser_ui.rs | 11 ++++--- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/crates/ui-egui/src/background_eraser_tolerance_field_tests.rs b/crates/ui-egui/src/background_eraser_tolerance_field_tests.rs index 84160eea96..acf9d3de15 100644 --- a/crates/ui-egui/src/background_eraser_tolerance_field_tests.rs +++ b/crates/ui-egui/src/background_eraser_tolerance_field_tests.rs @@ -112,6 +112,35 @@ fn a_leading_zero_typed_key_by_key_keeps_the_text() { assert_eq!(alert(&h), None, "05 is in range"); } +#[test] +fn a_tolerance_set_elsewhere_during_an_edit_wins_over_the_typed_text() { + let mut h = app_harness(); + h.query_all_by_role(egui::accesskit::Role::SpinButton) + .find(|n| n.accesskit_node().label().as_deref() == Some("Tolerance:")) + .expect("the Tolerance field") + .click(); + h.run_steps(1); + h.key_press_modifiers(egui::Modifiers::COMMAND, egui::Key::A); + h.event(egui::Event::Text("150".into())); + h.run_steps(1); + // The bar goes away mid-edit and a preset sets the Tolerance meanwhile. + h.state_mut().ui.panels.options_bar = false; + h.run_steps(2); + h.state_mut().ui.tool_options.bg_tolerance = 30.0; + h.state_mut().ui.panels.options_bar = true; + h.run_steps(1); + // From the first frame back, the field shows the preset's value. + let shown = h + .query_all_by_role(egui::accesskit::Role::SpinButton) + .find(|n| n.accesskit_node().label().as_deref() == Some("Tolerance:")) + .expect("the Tolerance field") + .accesskit_node() + .numeric_value(); + assert_eq!(shown, Some(30.0), "the field shows the preset's value, not the typed 150"); + h.run_steps(2); + assert_eq!(tolerance(&h), 30.0, "the preset's value, not the typed 150"); +} + #[test] fn an_expression_committed_with_enter_past_the_range_alerts() { let mut h = app_harness(); diff --git a/crates/ui-egui/src/tools/eraser_ui.rs b/crates/ui-egui/src/tools/eraser_ui.rs index 1310057628..3c5b997cb3 100644 --- a/crates/ui-egui/src/tools/eraser_ui.rs +++ b/crates/ui-egui/src/tools/eraser_ui.rs @@ -155,7 +155,10 @@ fn tolerance_field(ui: &mut egui::Ui, value: &mut f32) -> bool { // its text is left alone mid-entry (`0` on the way to `05` must not turn into `1`). let draft_id = ui.make_persistent_id(egui::IdSalt::new("bg-eraser-tolerance-typed")); // The field's own range is wide, so the typed value reaches here unclamped. - let mut typed = ui.ctx().data(|d| d.get_temp::(draft_id)).unwrap_or(*value); + // A draft only stands while the setting is still the one it stored: a value set elsewhere + // meanwhile (a tool preset while the bar was hidden) wins. + let draft = ui.ctx().data(|d| d.get_temp::<(f32, f32)>(draft_id)); + let mut typed = draft.filter(|(_, stored)| *stored == *value).map_or(*value, |(typed, _)| typed); let r = widgets::value_field(ui, &mut typed, -1.0e9..=1.0e9, "%", 62.0).on_hover_text(tl!("Erase range of similar colors")); let out_id = r.id.with("out-of-range"); // An edit the field never got to finish (the options bar was hidden while it had the @@ -172,13 +175,13 @@ fn tolerance_field(ui: &mut egui::Ui, value: &mut f32) -> bool { let out = typing && !(1.0..=100.0).contains(&typed); ui.ctx().data_mut(|d| d.insert_temp(out_id, out)); if typing && r.has_focus() { - ui.ctx().data_mut(|d| d.insert_temp(draft_id, typed)); + ui.ctx().data_mut(|d| d.insert_temp(draft_id, (typed, *value))); } else { - ui.ctx().data_mut(|d| d.remove_temp::(draft_id)); + ui.ctx().data_mut(|d| d.remove_temp::<(f32, f32)>(draft_id)); } } if !r.has_focus() { - ui.ctx().data_mut(|d| d.remove_temp::(draft_id)); + ui.ctx().data_mut(|d| d.remove_temp::<(f32, f32)>(draft_id)); } r.lost_focus() && ui.ctx().data_mut(|d| d.remove_temp::(out_id)).unwrap_or(false) }