diff --git a/crates/ui-egui/src/widgets.rs b/crates/ui-egui/src/widgets.rs index c55f72c4f..f88688f92 100644 --- a/crates/ui-egui/src/widgets.rs +++ b/crates/ui-egui/src/widgets.rs @@ -293,6 +293,18 @@ mod spinner_modifier_tests { assert_eq!(spinner_step(0.0, 1.0, 0.0, false, egui::Modifiers::SHIFT, &[], &pt), 0.0); } + #[test] + fn plain_and_command_steps_go_to_the_next_multiple_as_arrow_keys_do() { + // As ↑/↓ in the field (#2881): a plain step goes to the next whole number, Ctrl/Cmd to the + // next tenth, rounded to the places fields keep; Shift adds or takes ten. + assert_eq!(spinner_step(1.37, 1.0, 0.0, true, egui::Modifiers::NONE, &[], &pt), 2.0); + assert_eq!(spinner_step(1.37, 1.0, 0.0, false, egui::Modifiers::NONE, &[], &pt), 1.0); + assert_eq!(spinner_step(1.37, 1.0, 0.0, true, egui::Modifiers::COMMAND, &[], &pt), 1.4); + assert_eq!(spinner_step(1.37, 1.0, 0.0, false, egui::Modifiers::COMMAND, &[], &pt), 1.3); + assert_eq!(spinner_step(1.37, 1.0, 0.0, true, egui::Modifiers::SHIFT, &[], &pt), 11.37); + assert_eq!(spinner_step(0.37, 1.0, 0.0, false, egui::Modifiers::NONE, &[], &pt), 0.0); + } + /// A plain click on the spinner stops at each stop; Shift and Ctrl/Cmd steps do not. #[test] fn a_plain_spinner_step_stops_at_the_stops() { @@ -303,7 +315,7 @@ mod spinner_modifier_tests { assert_eq!(spinner_step(0.1, 1.0, 0.0, false, plain, &stops, &pt), 0.0); assert_eq!(spinner_step(14.0, 1.0, 0.0, true, plain, &stops, &pt), 15.0); assert_eq!(spinner_step(14.0, 1.0, 0.0, false, egui::Modifiers::SHIFT, &stops, &pt), 4.0); - assert!((spinner_step(0.52, 1.0, 0.0, false, egui::Modifiers::COMMAND, &stops, &pt) - 0.42).abs() < 1e-9); + assert_eq!(spinner_step(0.52, 1.0, 0.0, true, egui::Modifiers::COMMAND, &stops, &pt), 0.6); } /// A stop the spinner's text shows as the value counts as reached: 2.004 pt and the 2 pt stop diff --git a/crates/ui-egui/src/widgets/numeric.rs b/crates/ui-egui/src/widgets/numeric.rs index 7fd6d7322..c12144a99 100644 --- a/crates/ui-egui/src/widgets/numeric.rs +++ b/crates/ui-egui/src/widgets/numeric.rs @@ -645,7 +645,9 @@ fn spin_generic( /// Spinner buttons follow the same modifier sizes as ↑/↓ in a focused number field: /// Shift ×10, Ctrl/Cmd ÷10, otherwise the caller's normal step, which also stops at each of /// `stops` ([`step_through`]). A stop that `show` writes with the same text as the value counts as -/// reached, as in the field. +/// reached, as in the field. As with ↑/↓, a plain or Ctrl/Cmd step goes to the next multiple of its +/// size ([`step_to_multiple`]), a Shift step adds or takes ten steps, and the result is rounded to +/// [`STEP_DECIMALS`] places (#4490); a stop keeps its places. pub(super) fn spinner_step( value: f64, step: f64, @@ -655,12 +657,17 @@ pub(super) fn spinner_step( stops: &[f64], show: &dyn Fn(f64) -> String, ) -> f64 { - let (mods, multiplier) = STEPS.iter().find(|(m, _)| modifiers.matches_logically(*m)).map_or((egui::Modifiers::NONE, 1.0), |&(m, f)| (m, f)); - if mods == egui::Modifiers::NONE && !stops.is_empty() { - return step_through(value, step, if up { 1.0 } else { -1.0 }, stops, shown_alike(show)).max(min); - } - let delta = step * multiplier; - (value + if up { delta } else { -delta }).max(min) + let (mods, multiplier) = STEPS.iter().find(|(m, _)| modifiers.matches_logically(*m)).copied().unwrap_or((egui::Modifiers::NONE, 1.0)); + let size = step * multiplier; + let n = if up { 1.0 } else { -1.0 }; + let v = if mods == egui::Modifiers::SHIFT { + value + size * n + } else if mods == egui::Modifiers::NONE && !stops.is_empty() { + step_through(value, size, n, stops, shown_alike(show)) + } else { + step_to_multiple(value, size, n) + }; + (if stops.contains(&v) { v } else { round_to(v, STEP_DECIMALS) }).max(min) } /// A number field that may be empty (Stroke dash/gap): `value` (points) shows in `unit` without