From a8797fc8e2237e87b6c4c0a998e18efce6d2e34e Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 8 Feb 2022 00:02:48 +0300 Subject: [PATCH 1/8] Add step parameter to egui::Slider --- egui/src/widgets/slider.rs | 60 ++++++++++++++++--- egui_demo_lib/src/apps/demo/widget_gallery.rs | 17 +++++- 2 files changed, 67 insertions(+), 10 deletions(-) diff --git a/egui/src/widgets/slider.rs b/egui/src/widgets/slider.rs index dd32fccca5b..ab5e8cff0d0 100644 --- a/egui/src/widgets/slider.rs +++ b/egui/src/widgets/slider.rs @@ -71,6 +71,7 @@ pub struct Slider<'a> { suffix: String, text: String, text_color: Option, + step: Option, min_decimals: usize, max_decimals: Option, } @@ -113,6 +114,7 @@ impl<'a> Slider<'a> { suffix: Default::default(), text: Default::default(), text_color: None, + step: None, min_decimals: 0, max_decimals: None, } @@ -199,6 +201,28 @@ impl<'a> Slider<'a> { self } + /// Sets the minimal change of the value. + /// If `clamp_to_range` is enabled, `step` must be less than a maximum between the start and + /// the end of the range; otherwise, the step would be left unchanged. + /// Without `clamp_to_range` enabled, `step` can be any value. + pub fn step_by(mut self, step: f64) -> Self { + if self.clamp_to_range { + let max = self.range.end().abs().max(self.range.start().abs()); + if step.abs() <= max { + self.step = Some(step); + } + } else { + self.step = Some(step); + } + self + } + + /// Disable the `step_by` feature + pub fn disable_step(mut self) -> Self { + self.step = 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. @@ -247,13 +271,20 @@ impl<'a> Slider<'a> { } fn set_value(&mut self, mut value: f64) { - if self.clamp_to_range { - let start = *self.range.start(); - let end = *self.range.end(); - value = value.clamp(start.min(end), start.max(end)); - } - if let Some(max_decimals) = self.max_decimals { - value = emath::round_to_decimals(value, max_decimals); + if let Some(step) = self.step { + let remainer = value % step; + if remainer != 0.0 { + value -= remainer; + }; + } else { + if self.clamp_to_range { + let start = *self.range.start(); + let end = *self.range.end(); + value = value.clamp(start.min(end), start.max(end)); + } + if let Some(max_decimals) = self.max_decimals { + value = emath::round_to_decimals(value, max_decimals); + } } set(&mut self.get_set_value, value); } @@ -330,7 +361,9 @@ 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 new_value = if let Some(step) = self.step { + prev_value + (kb_step as f64 * step) + } else 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()), @@ -438,10 +471,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) + ui.input().num_presses(Key::ArrowRight) + - ui.input().num_presses(Key::ArrowDown) + - ui.input().num_presses(Key::ArrowLeft); + let speed = if self.step.is_some() && change != 0 { + self.step.unwrap() + } else { + 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/widget_gallery.rs b/egui_demo_lib/src/apps/demo/widget_gallery.rs index 48628c2f8d9..3e3254b4e26 100644 --- a/egui_demo_lib/src/apps/demo/widget_gallery.rs +++ b/egui_demo_lib/src/apps/demo/widget_gallery.rs @@ -14,6 +14,7 @@ pub struct WidgetGallery { boolean: bool, radio: Enum, scalar: f32, + step: f64, string: String, color: egui::Color32, animate_progress_bar: bool, @@ -29,6 +30,7 @@ impl Default for WidgetGallery { boolean: false, radio: Enum::First, scalar: 42.0, + step: 0.0, string: Default::default(), color: egui::Color32::LIGHT_BLUE.linear_multiply(0.5), animate_progress_bar: false, @@ -99,6 +101,7 @@ impl WidgetGallery { boolean, radio, scalar, + step, string, color, animate_progress_bar, @@ -166,8 +169,20 @@ impl WidgetGallery { }); ui.end_row(); + ui.add(doc_link_label("Slider step", "Slider")); + ui.add(egui::Slider::new(step, 0.0..=90.0)); + ui.end_row(); + ui.add(doc_link_label("Slider", "Slider")); - ui.add(egui::Slider::new(scalar, 0.0..=360.0).suffix("°")); + if *step != 0.0 { + ui.add( + egui::Slider::new(scalar, 0.0..=360.0) + .suffix("°") + .step_by(*step), + ); + } else { + ui.add(egui::Slider::new(scalar, 0.0..=360.0).suffix("°")); + } ui.end_row(); ui.add(doc_link_label("DragValue", "DragValue")); From 70add82e833c2db15f98d070ed3d3600af56f9fd Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 8 Feb 2022 00:45:20 +0300 Subject: [PATCH 2/8] Better code style --- egui/src/widgets/slider.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/egui/src/widgets/slider.rs b/egui/src/widgets/slider.rs index ab5e8cff0d0..ddc0e048410 100644 --- a/egui/src/widgets/slider.rs +++ b/egui/src/widgets/slider.rs @@ -472,13 +472,13 @@ 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) + ui.input().num_presses(Key::ArrowRight) - - ui.input().num_presses(Key::ArrowDown) - - ui.input().num_presses(Key::ArrowLeft); - let speed = if self.step.is_some() && change != 0 { - self.step.unwrap() - } else { - self.current_gradient(&position_range) + 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( From 4271cd2fe0879520dd7121ab81b454ca864e943c Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 8 Feb 2022 01:08:07 +0300 Subject: [PATCH 3/8] Cleaner and shorter conditionals --- egui/src/widgets/slider.rs | 40 +++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/egui/src/widgets/slider.rs b/egui/src/widgets/slider.rs index ddc0e048410..cc0562d5a00 100644 --- a/egui/src/widgets/slider.rs +++ b/egui/src/widgets/slider.rs @@ -206,14 +206,12 @@ impl<'a> Slider<'a> { /// the end of the range; otherwise, the step would be left unchanged. /// Without `clamp_to_range` enabled, `step` can be any value. pub fn step_by(mut self, step: f64) -> Self { - if self.clamp_to_range { - let max = self.range.end().abs().max(self.range.start().abs()); - if step.abs() <= max { - self.step = Some(step); - } - } else { - self.step = Some(step); - } + let max = self.range.end().abs().max(self.range.start().abs()); + let step = match self.clamp_to_range { + true if (step.abs() > max) => None, + _ => Some(step), + }; + self.step = step; self } @@ -361,16 +359,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 let Some(step) = self.step { - prev_value + (kb_step as f64 * step) - } else 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); } From 1dead2941dc8626c6184d9b97027909837a5d480 Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 8 Feb 2022 01:47:32 +0300 Subject: [PATCH 4/8] Add note into changelog --- CHANGELOG.md | 1 + egui/src/widgets/slider.rs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2a583b05b9..3265811394f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w * Added `Plot::allow_boxed_zoom()`, `Plot::boxed_zoom_pointer()` for boxed zooming on plots ([#1188](https://github.com/emilk/egui/pull/1188)). * 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 `Slider::step_by` and `Slider::disable_step()` ([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 cc0562d5a00..c0b6f3c76e6 100644 --- a/egui/src/widgets/slider.rs +++ b/egui/src/widgets/slider.rs @@ -71,6 +71,7 @@ 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, @@ -205,6 +206,8 @@ impl<'a> Slider<'a> { /// If `clamp_to_range` is enabled, `step` must be less than a maximum between the start and /// the end of the range; otherwise, the step would be left unchanged. /// Without `clamp_to_range` enabled, `step` can be any value. + /// + /// Default: `None`. pub fn step_by(mut self, step: f64) -> Self { let max = self.range.end().abs().max(self.range.start().abs()); let step = match self.clamp_to_range { From 3cb049e74d8f1acfa5939115b76d669cdef66d22 Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 8 Feb 2022 03:45:33 +0300 Subject: [PATCH 5/8] Fixes for the review --- egui/src/widgets/slider.rs | 38 ++++++++----------- egui_demo_lib/src/apps/demo/sliders.rs | 21 +++++++++- egui_demo_lib/src/apps/demo/widget_gallery.rs | 17 +-------- 3 files changed, 35 insertions(+), 41 deletions(-) diff --git a/egui/src/widgets/slider.rs b/egui/src/widgets/slider.rs index c0b6f3c76e6..04f77078fd5 100644 --- a/egui/src/widgets/slider.rs +++ b/egui/src/widgets/slider.rs @@ -203,27 +203,20 @@ impl<'a> Slider<'a> { } /// Sets the minimal change of the value. - /// If `clamp_to_range` is enabled, `step` must be less than a maximum between the start and - /// the end of the range; otherwise, the step would be left unchanged. - /// Without `clamp_to_range` enabled, `step` can be any 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: `None`. + /// Default: `0.0` (disabled). pub fn step_by(mut self, step: f64) -> Self { - let max = self.range.end().abs().max(self.range.start().abs()); - let step = match self.clamp_to_range { - true if (step.abs() > max) => None, - _ => Some(step), + let step = if step != 0.0 { + Some(step) + } else { + None }; self.step = step; self } - /// Disable the `step_by` feature - pub fn disable_step(mut self) -> Self { - self.step = 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. @@ -272,20 +265,19 @@ impl<'a> Slider<'a> { } fn set_value(&mut self, mut value: f64) { + if self.clamp_to_range { + let start = *self.range.start(); + let end = *self.range.end(); + value = value.clamp(start.min(end), start.max(end)); + } + if let Some(max_decimals) = self.max_decimals { + value = emath::round_to_decimals(value, max_decimals); + } if let Some(step) = self.step { let remainer = value % step; if remainer != 0.0 { value -= remainer; }; - } else { - if self.clamp_to_range { - let start = *self.range.start(); - let end = *self.range.end(); - value = value.clamp(start.min(end), start.max(end)); - } - if let Some(max_decimals) = self.max_decimals { - value = emath::round_to_decimals(value, max_decimals); - } } set(&mut self.get_set_value, value); } 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"); diff --git a/egui_demo_lib/src/apps/demo/widget_gallery.rs b/egui_demo_lib/src/apps/demo/widget_gallery.rs index 3e3254b4e26..48628c2f8d9 100644 --- a/egui_demo_lib/src/apps/demo/widget_gallery.rs +++ b/egui_demo_lib/src/apps/demo/widget_gallery.rs @@ -14,7 +14,6 @@ pub struct WidgetGallery { boolean: bool, radio: Enum, scalar: f32, - step: f64, string: String, color: egui::Color32, animate_progress_bar: bool, @@ -30,7 +29,6 @@ impl Default for WidgetGallery { boolean: false, radio: Enum::First, scalar: 42.0, - step: 0.0, string: Default::default(), color: egui::Color32::LIGHT_BLUE.linear_multiply(0.5), animate_progress_bar: false, @@ -101,7 +99,6 @@ impl WidgetGallery { boolean, radio, scalar, - step, string, color, animate_progress_bar, @@ -169,20 +166,8 @@ impl WidgetGallery { }); ui.end_row(); - ui.add(doc_link_label("Slider step", "Slider")); - ui.add(egui::Slider::new(step, 0.0..=90.0)); - ui.end_row(); - ui.add(doc_link_label("Slider", "Slider")); - if *step != 0.0 { - ui.add( - egui::Slider::new(scalar, 0.0..=360.0) - .suffix("°") - .step_by(*step), - ); - } else { - ui.add(egui::Slider::new(scalar, 0.0..=360.0).suffix("°")); - } + ui.add(egui::Slider::new(scalar, 0.0..=360.0).suffix("°")); ui.end_row(); ui.add(doc_link_label("DragValue", "DragValue")); From 22ed1d426493109d4d84b8e8aa71602addc6237f Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 8 Feb 2022 04:01:48 +0300 Subject: [PATCH 6/8] Changelog corrected (removed disable_step) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3265811394f..d90fb64b4da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w * Added `Plot::allow_boxed_zoom()`, `Plot::boxed_zoom_pointer()` for boxed zooming on plots ([#1188](https://github.com/emilk/egui/pull/1188)). * 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 `Slider::step_by` and `Slider::disable_step()` ([1255](https://github.com/emilk/egui/pull/1225)) +* 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! From b5e4daaf507af7f2a5003bf888126833a043d231 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 9 Feb 2022 15:55:14 +0100 Subject: [PATCH 7/8] Simpler stepping math --- egui/src/widgets/slider.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/egui/src/widgets/slider.rs b/egui/src/widgets/slider.rs index 04f77078fd5..8ab03147960 100644 --- a/egui/src/widgets/slider.rs +++ b/egui/src/widgets/slider.rs @@ -274,10 +274,7 @@ impl<'a> Slider<'a> { value = emath::round_to_decimals(value, max_decimals); } if let Some(step) = self.step { - let remainer = value % step; - if remainer != 0.0 { - value -= remainer; - }; + value = (value / step).round() * step; } set(&mut self.get_set_value, value); } From efb1ea864aaacb5b6c3a09daf5e27211b3d8e9a9 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 11 Feb 2022 18:59:13 +0300 Subject: [PATCH 8/8] Fix formatting --- egui/src/widgets/slider.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/egui/src/widgets/slider.rs b/egui/src/widgets/slider.rs index 8ab03147960..0dd3e76e7eb 100644 --- a/egui/src/widgets/slider.rs +++ b/egui/src/widgets/slider.rs @@ -203,17 +203,12 @@ impl<'a> Slider<'a> { } /// Sets the minimal change of the value. - /// Value `0.0` effectively disables the feature. If the new value is out of range + /// 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 { - let step = if step != 0.0 { - Some(step) - } else { - None - }; - self.step = step; + self.step = if step != 0.0 { Some(step) } else { None }; self }