diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index 017270fc..8ad88349 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -736,7 +736,7 @@ impl Slider<'_> { let prev_value = self.get_value(); let prev_position = self.position_from_value(prev_value, position_range); let new_position = prev_position + ui_point_per_step * kb_step; - let new_value = match self.step { + let mut new_value = match self.step { Some(step) => prev_value + (kb_step as f64 * step), None if self.smart_aim => { let aim_radius = 0.49 * ui_point_per_step; // Chosen so we don't include `prev_value` in the search. @@ -747,6 +747,19 @@ impl Slider<'_> { } _ => self.value_from_position(new_position, position_range), }; + if let Some(max_decimals) = self.max_decimals { + // self.set_value rounds, so ensure we reach at the least the next breakpoint + // note: we give it a little bit of leeway due to floating point errors. (0.1 isn't representable in binary) + // 'set_value' will round it to the nearest value. + let min_increment = 1.0 / (10.0_f64.powi(max_decimals as i32)); + new_value = if new_value > prev_value { + f64::max(new_value, prev_value + min_increment * 1.001) + } else if new_value < prev_value { + f64::min(new_value, prev_value - min_increment * 1.001) + } else { + new_value + }; + } self.set_value(new_value); } diff --git a/crates/egui_kittest/tests/regression_tests.rs b/crates/egui_kittest/tests/regression_tests.rs index 5107799f..5c7b4fc5 100644 --- a/crates/egui_kittest/tests/regression_tests.rs +++ b/crates/egui_kittest/tests/regression_tests.rs @@ -1,6 +1,8 @@ -use egui::accesskit::Role; +use egui::accesskit::{self, Role}; use egui::{Button, ComboBox, Image, Vec2, Widget as _}; -use egui_kittest::{kittest::Queryable as _, Harness, SnapshotResults}; +#[cfg(all(feature = "wgpu", feature = "snapshot"))] +use egui_kittest::SnapshotResults; +use egui_kittest::{kittest::Queryable as _, Harness}; #[test] pub fn focus_should_skip_over_disabled_buttons() { @@ -89,6 +91,7 @@ fn test_combobox() { harness.run(); + #[cfg(all(feature = "wgpu", feature = "snapshot"))] let mut results = SnapshotResults::new(); #[cfg(all(feature = "wgpu", feature = "snapshot"))] @@ -112,3 +115,45 @@ fn test_combobox() { // Popup should be closed now assert!(harness.query_by_label("Item 2").is_none()); } + +/// `https://github.com/emilk/egui/issues/7065` +#[test] +pub fn slider_should_move_with_fixed_decimals() { + let mut value: f32 = 1.0; + + let mut harness = Harness::new_ui(|ui| { + // Movement on arrow-key is relative to slider width; make the slider wide so the movement becomes small. + ui.spacing_mut().slider_width = 2000.0; + ui.add(egui::Slider::new(&mut value, 0.1..=10.0).fixed_decimals(2)); + }); + + harness.key_press(egui::Key::Tab); + harness.run(); + + let actual_slider = harness.get_by_role(accesskit::Role::SpinButton); + assert_eq!(actual_slider.value(), Some("1.00".to_owned())); + + harness.key_press(egui::Key::ArrowRight); + harness.run(); + + let actual_slider = harness.get_by_role(accesskit::Role::SpinButton); + assert_eq!(actual_slider.value(), Some("1.01".to_owned())); + + harness.key_press(egui::Key::ArrowRight); + harness.run(); + + let actual_slider = harness.get_by_role(accesskit::Role::SpinButton); + assert_eq!(actual_slider.value(), Some("1.02".to_owned())); + + harness.key_press(egui::Key::ArrowLeft); + harness.run(); + + let actual_slider = harness.get_by_role(accesskit::Role::SpinButton); + assert_eq!(actual_slider.value(), Some("1.01".to_owned())); + + harness.key_press(egui::Key::ArrowLeft); + harness.run(); + + let actual_slider = harness.get_by_role(accesskit::Role::SpinButton); + assert_eq!(actual_slider.value(), Some("1.00".to_owned())); +}