Options: commit iteration fields only while iteration is on; accept the locale's decimal comma for Maximum Change

Fields still holding unconfirmed text when the checkbox turns iteration off are no longer committed,
and Maximum Change parses with the interface's decimal separator as well as a dot.
This commit is contained in:
Harrison Booth
2026-10-24 15:47:17 -05:00
parent 6bdde35133
commit 3ef94601d5
2 changed files with 37 additions and 7 deletions
+9 -7
View File
@@ -1001,13 +1001,14 @@ fn options(app: &mut SheetApp, ui: &mut Ui) {
app.run_or_alert("formulas.calculationOptions", json!({"iterative": iterative}));
}
// Maximum Iterations and Maximum Change apply only while iteration is on, as in Excel.
ui.add_enabled_ui(calc.iterative, |ui| {
ui.add_enabled_ui(iterative, |ui| {
ui.horizontal(|ui| {
ui.add_space(24.0);
egui::Grid::new("backstage_options_iteration").num_columns(2).spacing(vec2(12.0, 6.0)).show(ui, |ui| {
ui.label(lang.tr("Maximum Iterations:"));
let id = ui.make_persistent_id("backstage_max_iterations");
if let Some(text) = crate::widgets::committed_text(ui, id, &calc.max_iterations.to_string(), Some(80.0)) {
// Text left in the field when iteration is switched off (it loses focus) is dropped.
if let Some(text) = crate::widgets::committed_text(ui, id, &calc.max_iterations.to_string(), Some(80.0)).filter(|_| iterative) {
match parse_max_iterations(&text) {
Some(n) if n != calc.max_iterations => {
app.run_or_alert("formulas.calculationOptions", json!({"maxIterations": n}));
@@ -1019,8 +1020,8 @@ fn options(app: &mut SheetApp, ui: &mut Ui) {
ui.end_row();
ui.label(lang.tr("Maximum Change:"));
let id = ui.make_persistent_id("backstage_max_change");
if let Some(text) = crate::widgets::committed_text(ui, id, &calc.max_change.to_string(), Some(80.0)) {
match parse_max_change(&text) {
if let Some(text) = crate::widgets::committed_text(ui, id, &calc.max_change.to_string(), Some(80.0)).filter(|_| iterative) {
match parse_max_change(&text, lang.formula_locale()) {
Some(d) if d != calc.max_change => {
app.run_or_alert("formulas.calculationOptions", json!({"maxChange": d}));
}
@@ -1051,9 +1052,10 @@ fn parse_max_iterations(text: &str) -> Option<u32> {
(1..=gridcraft_engine::model::CalcSettings::MAX_ITERATIONS).contains(&n).then_some(n)
}
/// Maximum Change as typed: a finite number of 0 or more, or `None`.
fn parse_max_change(text: &str) -> Option<f64> {
let d = text.trim().parse::<f64>().ok()?;
/// Maximum Change as typed: a finite number of 0 or more, or `None`. The interface's decimal
/// separator is accepted as well as a dot.
fn parse_max_change(text: &str, locale: gridcraft_engine::formula::FormulaLocale) -> Option<f64> {
let d = text.trim().replace(locale.decimal_separator(), ".").parse::<f64>().ok()?;
(d.is_finite() && d >= 0.0).then_some(d)
}
+28
View File
@@ -415,6 +415,34 @@ fn options_sets_maximum_iterations_and_change_only_while_iteration_is_on() {
assert_eq!(calc(&h).max_change, 0.01);
let change = field(&h, "Maximum Change:");
assert_eq!(h.get_all_by_role(Role::TextInput).find(|n| n.rect() == change).unwrap().value().as_deref(), Some("0.01"));
// A value typed but not confirmed with Enter is dropped when the checkbox switches iteration off.
let iterations = field(&h, "Maximum Iterations:");
h.input_mut().events.push(egui::Event::PointerMoved(iterations.center()));
h.run_steps(1);
h.get_all_by_role(Role::TextInput).find(|n| n.rect() == iterations).unwrap().click();
h.run_steps(2);
h.key_press_modifiers(egui::Modifiers::COMMAND, egui::Key::A);
h.input_mut().events.push(egui::Event::Text("777".into()));
h.run_steps(2);
h.get_by_role_and_label(Role::CheckBox, "Enable iterative calculation").click();
h.run_steps(3);
assert!(!calc(&h).iterative);
assert_eq!(calc(&h).max_iterations, 32767, "an unconfirmed entry is not committed once iteration is off");
assert!(h.state().message.is_none());
// Spanish accepts a decimal comma in Maximum Change, and still a dot.
h.get_by_role_and_label(Role::CheckBox, "Enable iterative calculation").click();
h.run_steps(3);
h.state_mut().run("app.language.set", serde_json::json!({"language": Language::Es.code()})).unwrap();
h.state_mut().run("file.options", serde_json::json!({})).unwrap();
h.run_steps(3);
let change = field(&h, "Cambio máximo:");
retype(&mut h, change, "0,02");
assert_eq!(calc(&h).max_change, 0.02);
retype(&mut h, change, "0.5");
assert_eq!(calc(&h).max_change, 0.5);
assert!(h.state().message.is_none());
}
/// Share › Export as Web Page (and the native File menu's Save as Web Page) asks for a name in its