diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c38a1dc49d..5188b07025f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w * Added linked axis support for plots via `plot::LinkedAxisGroup` ([#1184](https://github.com/emilk/egui/pull/1184)). * Added `Response::on_hover_text_at_pointer` as a convenience akin to `Response::on_hover_text` ([1179](https://github.com/emilk/egui/pull/1179)). * Added `ui.weak(text)`. +* Added `Slider::step_by` ([1255](https://github.com/emilk/egui/pull/1225)). ### Changed 🔧 * ⚠️ `Context::input` and `Ui::input` now locks a mutex. This can lead to a dead-lock is used in an `if let` binding! diff --git a/egui/src/widgets/slider.rs b/egui/src/widgets/slider.rs index dd32fccca5b..0dd3e76e7eb 100644 --- a/egui/src/widgets/slider.rs +++ b/egui/src/widgets/slider.rs @@ -71,6 +71,8 @@ pub struct Slider<'a> { suffix: String, text: String, text_color: Option, + /// Sets the minimal step of the widget value + step: Option, min_decimals: usize, max_decimals: Option, } @@ -113,6 +115,7 @@ impl<'a> Slider<'a> { suffix: Default::default(), text: Default::default(), text_color: None, + step: None, min_decimals: 0, max_decimals: None, } @@ -199,6 +202,16 @@ impl<'a> Slider<'a> { self } + /// Sets the minimal change of the value. + /// Value `0.0` effectively disables the feature. If the new value is out of range + /// and `clamp_to_range` is enabled, you would not have the ability to change the value. + /// + /// Default: `0.0` (disabled). + pub fn step_by(mut self, step: f64) -> Self { + self.step = if step != 0.0 { Some(step) } else { None }; + self + } + // TODO: we should also have a "min precision". /// Set a minimum number of decimals to display. /// Normally you don't need to pick a precision, as the slider will intelligently pick a precision for you. @@ -255,6 +268,9 @@ impl<'a> Slider<'a> { if let Some(max_decimals) = self.max_decimals { value = emath::round_to_decimals(value, max_decimals); } + if let Some(step) = self.step { + value = (value / step).round() * step; + } set(&mut self.get_set_value, value); } @@ -330,14 +346,22 @@ impl<'a> Slider<'a> { let prev_value = self.get_value(); let prev_position = self.position_from_value(prev_value, position_range.clone()); let new_position = prev_position + kb_step; - let new_value = if self.smart_aim { - let aim_radius = ui.input().aim_radius(); - emath::smart_aim::best_in_range_f64( - self.value_from_position(new_position - aim_radius, position_range.clone()), - self.value_from_position(new_position + aim_radius, position_range.clone()), - ) - } else { - self.value_from_position(new_position, position_range.clone()) + let new_value = match self.step { + Some(step) => prev_value + (kb_step as f64 * step), + None if self.smart_aim => { + let aim_radius = ui.input().aim_radius(); + emath::smart_aim::best_in_range_f64( + self.value_from_position( + new_position - aim_radius, + position_range.clone(), + ), + self.value_from_position( + new_position + aim_radius, + position_range.clone(), + ), + ) + } + _ => self.value_from_position(new_position, position_range.clone()), }; self.set_value(new_value); } @@ -438,10 +462,19 @@ impl<'a> Slider<'a> { } fn value_ui(&mut self, ui: &mut Ui, position_range: RangeInclusive) { + // If `DragValue` is controlled from the keyboard and `step` is defined, set speed to `step` + let change = ui.input().num_presses(Key::ArrowUp) as i32 + + ui.input().num_presses(Key::ArrowRight) as i32 + - ui.input().num_presses(Key::ArrowDown) as i32 + - ui.input().num_presses(Key::ArrowLeft) as i32; + let speed = match self.step { + Some(step) if change != 0 => step, + _ => self.current_gradient(&position_range), + }; let mut value = self.get_value(); ui.add( DragValue::new(&mut value) - .speed(self.current_gradient(&position_range)) + .speed(speed) .clamp_range(self.clamp_range()) .min_decimals(self.min_decimals) .max_decimals_opt(self.max_decimals) diff --git a/egui_demo_lib/src/apps/demo/sliders.rs b/egui_demo_lib/src/apps/demo/sliders.rs index 78e0cdb1015..12405b9b927 100644 --- a/egui_demo_lib/src/apps/demo/sliders.rs +++ b/egui_demo_lib/src/apps/demo/sliders.rs @@ -11,6 +11,8 @@ pub struct Sliders { pub logarithmic: bool, pub clamp_to_range: bool, pub smart_aim: bool, + pub step: f64, + pub use_steps: bool, pub integer: bool, pub vertical: bool, pub value: f64, @@ -24,6 +26,8 @@ impl Default for Sliders { logarithmic: true, clamp_to_range: false, smart_aim: true, + step: 10.0, + use_steps: false, integer: false, vertical: false, value: 10.0, @@ -55,6 +59,8 @@ impl super::View for Sliders { logarithmic, clamp_to_range, smart_aim, + step, + use_steps, integer, vertical, value, @@ -79,6 +85,7 @@ impl super::View for Sliders { SliderOrientation::Horizontal }; + let istep = if *use_steps { *step } else { 0.0 }; if *integer { let mut value_i32 = *value as i32; ui.add( @@ -87,7 +94,8 @@ impl super::View for Sliders { .clamp_to_range(*clamp_to_range) .smart_aim(*smart_aim) .orientation(orientation) - .text("i32 demo slider"), + .text("i32 demo slider") + .step_by(istep), ); *value = value_i32 as f64; } else { @@ -97,7 +105,8 @@ impl super::View for Sliders { .clamp_to_range(*clamp_to_range) .smart_aim(*smart_aim) .orientation(orientation) - .text("f64 demo slider"), + .text("f64 demo slider") + .step_by(istep), ); ui.label( @@ -128,6 +137,14 @@ impl super::View for Sliders { ui.separator(); + ui.checkbox(use_steps, "Use steps"); + ui.label("When enabled, the minimal value change would be restricted to a given step."); + if *use_steps { + ui.add(egui::DragValue::new(step).speed(1.0)); + } + + ui.separator(); + ui.horizontal(|ui| { ui.label("Slider type:"); ui.radio_value(integer, true, "i32");