From 5e2b3d4a51be97c77124dcd60e5ee3bed1b19826 Mon Sep 17 00:00:00 2001 From: Jonatan Pettersson Date: Wed, 20 Sep 2023 20:56:50 +0200 Subject: [PATCH 1/7] Enhance `Slider` and `VerticalSlider` functionality * Add optional default behavior * Add a `default` field * Add a `default()` method to set the `default` field * A double-click, ctrl-click or command-click will set the slider to the default value * Add optional fine-grained control * Add an optional `step_fine` field * Add a `step_fine()` method to set the `step_fine` field * Use `step_fine` in place of `step` while shift is pressed * Add increment/decrement via up/down keys * Update `Slider` and `VerticalSlider` examples --- examples/slider/src/main.rs | 33 +++++-- widget/src/slider.rs | 164 ++++++++++++++++++++++++++++++---- widget/src/vertical_slider.rs | 163 +++++++++++++++++++++++++++++---- 3 files changed, 322 insertions(+), 38 deletions(-) diff --git a/examples/slider/src/main.rs b/examples/slider/src/main.rs index e83804c223..e9820aed43 100644 --- a/examples/slider/src/main.rs +++ b/examples/slider/src/main.rs @@ -12,13 +12,21 @@ pub enum Message { pub struct Slider { slider_value: u8, + slider_default: u8, + slider_step: u8, + slider_step_fine: u8, } impl Sandbox for Slider { type Message = Message; fn new() -> Slider { - Slider { slider_value: 50 } + Slider { + slider_value: 50, + slider_default: 50, + slider_step: 5, + slider_step_fine: 1, + } } fn title(&self) -> String { @@ -35,14 +43,25 @@ impl Sandbox for Slider { fn view(&self) -> Element { let value = self.slider_value; + let default = self.slider_default; + let step = self.slider_step; + let step_fine = self.slider_step_fine; - let h_slider = - container(slider(0..=100, value, Message::SliderChanged)) - .width(250); + let h_slider = container( + slider(0..=100, value, Message::SliderChanged) + .default(default) + .step(step) + .step_fine(step_fine), + ) + .width(250); - let v_slider = - container(vertical_slider(0..=100, value, Message::SliderChanged)) - .height(200); + let v_slider = container( + vertical_slider(0..=100, value, Message::SliderChanged) + .default(default) + .step(step) + .step_fine(step_fine), + ) + .height(200); let text = text(format!("{value}")); diff --git a/widget/src/slider.rs b/widget/src/slider.rs index 5c3b63841e..3b7de8b94b 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -2,8 +2,10 @@ //! //! A [`Slider`] has some local [`State`]. use crate::core::event::{self, Event}; +use crate::core::keyboard; +use crate::core::keyboard::key::{self, Key}; use crate::core::layout; -use crate::core::mouse; +use crate::core::mouse::{self, click}; use crate::core::renderer; use crate::core::touch; use crate::core::widget::tree::{self, Tree}; @@ -49,7 +51,9 @@ where { range: RangeInclusive, step: T, + step_fine: Option, value: T, + default: Option, on_change: Box Message + 'a>, on_release: Option, width: Length, @@ -92,8 +96,10 @@ where Slider { value, + default: None, range, step: T::from(1), + step_fine: None, on_change: Box::new(on_change), on_release: None, width: Length::Fill, @@ -102,6 +108,13 @@ where } } + /// Sets the optional default value for the [`Slider`]. + /// If set, [`Slider`] will reset to this value when doubled-clicked, ctrl-clicked, or command-clicked. + pub fn default(mut self, default: impl Into) -> Self { + self.default = Some(default.into()); + self + } + /// Sets the release message of the [`Slider`]. /// This is called when the mouse is released from the slider. /// @@ -136,6 +149,13 @@ where self.step = step.into(); self } + + /// Sets the optional fine-grained step size for the [`Slider`]. + /// If set, this value is used as the step size while shift is pressed. + pub fn step_fine(mut self, step_fine: impl Into) -> Self { + self.step_fine = Some(step_fine.into()); + self + } } impl<'a, T, Message, Theme, Renderer> Widget @@ -188,8 +208,10 @@ where shell, tree.state.downcast_mut::(), &mut self.value, + self.default, &self.range, self.step, + self.step_fine, self.on_change.as_ref(), &self.on_release, ) @@ -253,8 +275,10 @@ pub fn update( shell: &mut Shell<'_, Message>, state: &mut State, value: &mut T, + default: Option, range: &RangeInclusive, step: T, + step_fine: Option, on_change: &dyn Fn(T) -> Message, on_release: &Option, ) -> event::Status @@ -264,14 +288,19 @@ where { let is_dragging = state.is_dragging; - let mut change = |cursor_position: Point| { + let change_cursor_position = |cursor_position: Point| -> Option { let bounds = layout.bounds(); let new_value = if cursor_position.x <= bounds.x { - *range.start() + Some(*range.start()) } else if cursor_position.x >= bounds.x + bounds.width { - *range.end() + Some(*range.end()) } else { - let step = step.into(); + let step = match step_fine { + Some(step_fine) if state.keyboard_modifiers.shift() => { + step_fine.into() + } + _ => step.into(), + }; let start = (*range.start()).into(); let end = (*range.end()).into(); @@ -281,17 +310,67 @@ where let steps = (percent * (end - start) / step).round(); let value = steps * step + start; - if let Some(value) = T::from_f64(value) { - value - } else { - return; + T::from_f64(value) + }; + + new_value + }; + + let increment = |value: T| -> Option { + let step = match step_fine { + Some(step_fine) if state.keyboard_modifiers.shift() => { + step_fine.into() + } + _ => step.into(), + }; + + let steps = (value.into() / step).round(); + let new_value = step * (steps + f64::from(1)); + + if new_value > (*range.end()).into() { + return Some(*range.end()); + } + + T::from_f64(new_value) + }; + + let decrement = |value: T| -> Option { + let step = match step_fine { + Some(step_fine) if state.keyboard_modifiers.shift() => { + step_fine.into() } + _ => step.into(), }; - if ((*value).into() - new_value.into()).abs() > f64::EPSILON { - shell.publish((on_change)(new_value)); + let steps = (value.into() / step).round(); + let new_value = step * (steps - f64::from(1)); + + if new_value < (*range.start()).into() { + return Some(*range.start()); + } + + T::from_f64(new_value) + }; + + enum Change { + Default, + CursorPosition(Point), + Increment, + Decrement, + } - *value = new_value; + let mut change = |change: Change| { + if let Some(new_value) = match change { + Change::Default => default, + Change::CursorPosition(point) => change_cursor_position(point), + Change::Increment => increment(*value), + Change::Decrement => decrement(*value), + } { + if ((*value).into() - new_value.into()).abs() > f64::EPSILON { + shell.publish((on_change)(new_value)); + + *value = new_value; + } } }; @@ -300,8 +379,31 @@ where | Event::Touch(touch::Event::FingerPressed { .. }) => { if let Some(cursor_position) = cursor.position_over(layout.bounds()) { - change(cursor_position); - state.is_dragging = true; + let click = + mouse::Click::new(cursor_position, state.last_click); + + match click.kind() { + click::Kind::Single => { + if state.keyboard_modifiers.control() + || state.keyboard_modifiers.command() + { + change(Change::Default); + state.is_dragging = false; + } else { + change(Change::CursorPosition(cursor_position)); + state.is_dragging = true; + } + } + click::Kind::Double => { + change(Change::Default); + state.is_dragging = false; + } + mouse::click::Kind::Triple => { + state.is_dragging = false; + } + } + + state.last_click = Some(click); return event::Status::Captured; } @@ -321,11 +423,31 @@ where Event::Mouse(mouse::Event::CursorMoved { .. }) | Event::Touch(touch::Event::FingerMoved { .. }) => { if is_dragging { - let _ = cursor.position().map(change); + let _ = cursor + .position() + .map(|point| change(Change::CursorPosition(point))); return event::Status::Captured; } } + Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => { + if cursor.position_over(layout.bounds()).is_some() { + match key { + Key::Named(key::Named::ArrowUp) => { + change(Change::Increment); + } + Key::Named(key::Named::ArrowDown) => { + change(Change::Decrement); + } + _ => (), + } + + return event::Status::Captured; + } + } + Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { + state.keyboard_modifiers = modifiers; + } _ => {} } @@ -451,9 +573,11 @@ pub fn mouse_interaction( } /// The local state of a [`Slider`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[derive(Debug, Clone, Copy, Default)] pub struct State { is_dragging: bool, + last_click: Option, + keyboard_modifiers: keyboard::Modifiers, } impl State { @@ -462,3 +586,11 @@ impl State { State::default() } } + +impl PartialEq for State { + fn eq(&self, other: &Self) -> bool { + self.is_dragging == other.is_dragging + } +} + +impl Eq for State {} diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index d3086a81f5..c16727b60b 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -7,8 +7,11 @@ pub use crate::style::slider::{Appearance, Handle, HandleShape, StyleSheet}; use crate::core; use crate::core::event::{self, Event}; +use crate::core::keyboard; +use crate::core::keyboard::key::{self, Key}; use crate::core::layout::{self, Layout}; use crate::core::mouse; +use crate::core::mouse::click; use crate::core::renderer; use crate::core::touch; use crate::core::widget::tree::{self, Tree}; @@ -46,7 +49,9 @@ where { range: RangeInclusive, step: T, + step_fine: Option, value: T, + default: Option, on_change: Box Message + 'a>, on_release: Option, width: f32, @@ -89,8 +94,10 @@ where VerticalSlider { value, + default: None, range, step: T::from(1), + step_fine: None, on_change: Box::new(on_change), on_release: None, width: Self::DEFAULT_WIDTH, @@ -99,6 +106,13 @@ where } } + /// Sets the optional default value for the [`VerticalSlider`]. + /// If set, [`VerticalSlider`] will reset to this value when doubled-clicked, ctrl-clicked, or command-clicked. + pub fn default(mut self, default: impl Into) -> Self { + self.default = Some(default.into()); + self + } + /// Sets the release message of the [`VerticalSlider`]. /// This is called when the mouse is released from the slider. /// @@ -133,6 +147,13 @@ where self.step = step; self } + + /// Sets the optional fine-grained step size for the [`VerticalSlider`]. + /// If set, this value is used as the step size while shift is pressed. + pub fn step_fine(mut self, step_fine: impl Into) -> Self { + self.step_fine = Some(step_fine.into()); + self + } } impl<'a, T, Message, Theme, Renderer> Widget @@ -185,8 +206,10 @@ where shell, tree.state.downcast_mut::(), &mut self.value, + self.default, &self.range, self.step, + self.step_fine, self.on_change.as_ref(), &self.on_release, ) @@ -251,8 +274,10 @@ pub fn update( shell: &mut Shell<'_, Message>, state: &mut State, value: &mut T, + default: Option, range: &RangeInclusive, step: T, + step_fine: Option, on_change: &dyn Fn(T) -> Message, on_release: &Option, ) -> event::Status @@ -262,15 +287,20 @@ where { let is_dragging = state.is_dragging; - let mut change = |cursor_position: Point| { + let change_cursor_position = |cursor_position: Point| -> Option { let bounds = layout.bounds(); let new_value = if cursor_position.y >= bounds.y + bounds.height { - *range.start() + Some(*range.start()) } else if cursor_position.y <= bounds.y { - *range.end() + Some(*range.end()) } else { - let step = step.into(); + let step = match step_fine { + Some(step_fine) if state.keyboard_modifiers.shift() => { + step_fine.into() + } + _ => step.into(), + }; let start = (*range.start()).into(); let end = (*range.end()).into(); @@ -281,17 +311,67 @@ where let steps = (percent * (end - start) / step).round(); let value = steps * step + start; - if let Some(value) = T::from_f64(value) { - value - } else { - return; + T::from_f64(value) + }; + + new_value + }; + + let increment = |value: T| -> Option { + let step = match step_fine { + Some(step_fine) if state.keyboard_modifiers.shift() => { + step_fine.into() + } + _ => step.into(), + }; + + let steps = (value.into() / step).round(); + let new_value = step * (steps + f64::from(1)); + + if new_value > (*range.end()).into() { + return Some(*range.end()); + } + + T::from_f64(new_value) + }; + + let decrement = |value: T| -> Option { + let step = match step_fine { + Some(step_fine) if state.keyboard_modifiers.shift() => { + step_fine.into() } + _ => step.into(), }; - if ((*value).into() - new_value.into()).abs() > f64::EPSILON { - shell.publish((on_change)(new_value)); + let steps = (value.into() / step).round(); + let new_value = step * (steps - f64::from(1)); + + if new_value < (*range.start()).into() { + return Some(*range.start()); + } + + T::from_f64(new_value) + }; + + enum Change { + Default, + CursorPosition(Point), + Increment, + Decrement, + } - *value = new_value; + let mut change = |change: Change| { + if let Some(new_value) = match change { + Change::Default => default, + Change::CursorPosition(point) => change_cursor_position(point), + Change::Increment => increment(*value), + Change::Decrement => decrement(*value), + } { + if ((*value).into() - new_value.into()).abs() > f64::EPSILON { + shell.publish((on_change)(new_value)); + + *value = new_value; + } } }; @@ -300,8 +380,31 @@ where | Event::Touch(touch::Event::FingerPressed { .. }) => { if let Some(cursor_position) = cursor.position_over(layout.bounds()) { - change(cursor_position); - state.is_dragging = true; + let click = + mouse::Click::new(cursor_position, state.last_click); + + match click.kind() { + click::Kind::Single => { + if state.keyboard_modifiers.control() + || state.keyboard_modifiers.command() + { + change(Change::Default); + state.is_dragging = false; + } else { + change(Change::CursorPosition(cursor_position)); + state.is_dragging = true; + } + } + click::Kind::Double => { + change(Change::Default); + state.is_dragging = false; + } + mouse::click::Kind::Triple => { + state.is_dragging = false; + } + } + + state.last_click = Some(click); return event::Status::Captured; } @@ -321,11 +424,31 @@ where Event::Mouse(mouse::Event::CursorMoved { .. }) | Event::Touch(touch::Event::FingerMoved { .. }) => { if is_dragging { - let _ = cursor.position().map(change); + let _ = cursor + .position() + .map(|point| change(Change::CursorPosition(point))); return event::Status::Captured; } } + Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => { + if cursor.position_over(layout.bounds()).is_some() { + match key { + Key::Named(key::Named::ArrowUp) => { + change(Change::Increment); + } + Key::Named(key::Named::ArrowDown) => { + change(Change::Decrement); + } + _ => (), + } + + return event::Status::Captured; + } + } + Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { + state.keyboard_modifiers = modifiers; + } _ => {} } @@ -451,9 +574,11 @@ pub fn mouse_interaction( } /// The local state of a [`VerticalSlider`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[derive(Debug, Clone, Copy, Default)] pub struct State { is_dragging: bool, + last_click: Option, + keyboard_modifiers: keyboard::Modifiers, } impl State { @@ -462,3 +587,11 @@ impl State { State::default() } } + +impl PartialEq for State { + fn eq(&self, other: &Self) -> bool { + self.is_dragging == other.is_dragging + } +} + +impl Eq for State {} From 00716a159a371d070c5fb7d9ed2c660348343a33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 31 Jan 2024 21:38:16 +0100 Subject: [PATCH 2/7] Simplify `Change` enum in `slider` logic and remove double-click behavior --- widget/src/slider.rs | 76 ++++++++-------------------------- widget/src/vertical_slider.rs | 77 ++++++++--------------------------- 2 files changed, 35 insertions(+), 118 deletions(-) diff --git a/widget/src/slider.rs b/widget/src/slider.rs index 3b7de8b94b..3d1d08ba33 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -5,7 +5,7 @@ use crate::core::event::{self, Event}; use crate::core::keyboard; use crate::core::keyboard::key::{self, Key}; use crate::core::layout; -use crate::core::mouse::{self, click}; +use crate::core::mouse; use crate::core::renderer; use crate::core::touch; use crate::core::widget::tree::{self, Tree}; @@ -287,8 +287,9 @@ where Message: Clone, { let is_dragging = state.is_dragging; + let current_value = *value; - let change_cursor_position = |cursor_position: Point| -> Option { + let locate = |cursor_position: Point| -> Option { let bounds = layout.bounds(); let new_value = if cursor_position.x <= bounds.x { Some(*range.start()) @@ -352,25 +353,11 @@ where T::from_f64(new_value) }; - enum Change { - Default, - CursorPosition(Point), - Increment, - Decrement, - } + let change = |new_value: T| { + if ((*value).into() - new_value.into()).abs() > f64::EPSILON { + shell.publish((on_change)(new_value)); - let mut change = |change: Change| { - if let Some(new_value) = match change { - Change::Default => default, - Change::CursorPosition(point) => change_cursor_position(point), - Change::Increment => increment(*value), - Change::Decrement => decrement(*value), - } { - if ((*value).into() - new_value.into()).abs() > f64::EPSILON { - shell.publish((on_change)(new_value)); - - *value = new_value; - } + *value = new_value; } }; @@ -379,32 +366,14 @@ where | Event::Touch(touch::Event::FingerPressed { .. }) => { if let Some(cursor_position) = cursor.position_over(layout.bounds()) { - let click = - mouse::Click::new(cursor_position, state.last_click); - - match click.kind() { - click::Kind::Single => { - if state.keyboard_modifiers.control() - || state.keyboard_modifiers.command() - { - change(Change::Default); - state.is_dragging = false; - } else { - change(Change::CursorPosition(cursor_position)); - state.is_dragging = true; - } - } - click::Kind::Double => { - change(Change::Default); - state.is_dragging = false; - } - mouse::click::Kind::Triple => { - state.is_dragging = false; - } + if state.keyboard_modifiers.command() { + let _ = default.map(change); + state.is_dragging = false; + } else { + let _ = locate(cursor_position).map(change); + state.is_dragging = true; } - state.last_click = Some(click); - return event::Status::Captured; } } @@ -423,9 +392,7 @@ where Event::Mouse(mouse::Event::CursorMoved { .. }) | Event::Touch(touch::Event::FingerMoved { .. }) => { if is_dragging { - let _ = cursor - .position() - .map(|point| change(Change::CursorPosition(point))); + let _ = cursor.position().and_then(locate).map(change); return event::Status::Captured; } @@ -434,10 +401,10 @@ where if cursor.position_over(layout.bounds()).is_some() { match key { Key::Named(key::Named::ArrowUp) => { - change(Change::Increment); + let _ = increment(current_value).map(change); } Key::Named(key::Named::ArrowDown) => { - change(Change::Decrement); + let _ = decrement(current_value).map(change); } _ => (), } @@ -573,10 +540,9 @@ pub fn mouse_interaction( } /// The local state of a [`Slider`]. -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct State { is_dragging: bool, - last_click: Option, keyboard_modifiers: keyboard::Modifiers, } @@ -586,11 +552,3 @@ impl State { State::default() } } - -impl PartialEq for State { - fn eq(&self, other: &Self) -> bool { - self.is_dragging == other.is_dragging - } -} - -impl Eq for State {} diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index c16727b60b..168d784881 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -11,7 +11,6 @@ use crate::core::keyboard; use crate::core::keyboard::key::{self, Key}; use crate::core::layout::{self, Layout}; use crate::core::mouse; -use crate::core::mouse::click; use crate::core::renderer; use crate::core::touch; use crate::core::widget::tree::{self, Tree}; @@ -286,8 +285,9 @@ where Message: Clone, { let is_dragging = state.is_dragging; + let current_value = *value; - let change_cursor_position = |cursor_position: Point| -> Option { + let locate = |cursor_position: Point| -> Option { let bounds = layout.bounds(); let new_value = if cursor_position.y >= bounds.y + bounds.height { @@ -353,25 +353,11 @@ where T::from_f64(new_value) }; - enum Change { - Default, - CursorPosition(Point), - Increment, - Decrement, - } + let change = |new_value: T| { + if ((*value).into() - new_value.into()).abs() > f64::EPSILON { + shell.publish((on_change)(new_value)); - let mut change = |change: Change| { - if let Some(new_value) = match change { - Change::Default => default, - Change::CursorPosition(point) => change_cursor_position(point), - Change::Increment => increment(*value), - Change::Decrement => decrement(*value), - } { - if ((*value).into() - new_value.into()).abs() > f64::EPSILON { - shell.publish((on_change)(new_value)); - - *value = new_value; - } + *value = new_value; } }; @@ -380,32 +366,16 @@ where | Event::Touch(touch::Event::FingerPressed { .. }) => { if let Some(cursor_position) = cursor.position_over(layout.bounds()) { - let click = - mouse::Click::new(cursor_position, state.last_click); - - match click.kind() { - click::Kind::Single => { - if state.keyboard_modifiers.control() - || state.keyboard_modifiers.command() - { - change(Change::Default); - state.is_dragging = false; - } else { - change(Change::CursorPosition(cursor_position)); - state.is_dragging = true; - } - } - click::Kind::Double => { - change(Change::Default); - state.is_dragging = false; - } - mouse::click::Kind::Triple => { - state.is_dragging = false; - } + if state.keyboard_modifiers.control() + || state.keyboard_modifiers.command() + { + let _ = default.map(change); + state.is_dragging = false; + } else { + let _ = locate(cursor_position).map(change); + state.is_dragging = true; } - state.last_click = Some(click); - return event::Status::Captured; } } @@ -424,9 +394,7 @@ where Event::Mouse(mouse::Event::CursorMoved { .. }) | Event::Touch(touch::Event::FingerMoved { .. }) => { if is_dragging { - let _ = cursor - .position() - .map(|point| change(Change::CursorPosition(point))); + let _ = cursor.position().and_then(locate).map(change); return event::Status::Captured; } @@ -435,10 +403,10 @@ where if cursor.position_over(layout.bounds()).is_some() { match key { Key::Named(key::Named::ArrowUp) => { - change(Change::Increment); + let _ = increment(current_value).map(change); } Key::Named(key::Named::ArrowDown) => { - change(Change::Decrement); + let _ = decrement(current_value).map(change); } _ => (), } @@ -574,10 +542,9 @@ pub fn mouse_interaction( } /// The local state of a [`VerticalSlider`]. -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct State { is_dragging: bool, - last_click: Option, keyboard_modifiers: keyboard::Modifiers, } @@ -587,11 +554,3 @@ impl State { State::default() } } - -impl PartialEq for State { - fn eq(&self, other: &Self) -> bool { - self.is_dragging == other.is_dragging - } -} - -impl Eq for State {} From 66f8cf8215c1bcdf8bf7e5b698b901b49fd87c78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 31 Jan 2024 21:48:34 +0100 Subject: [PATCH 3/7] Simplify `slider` logic further --- widget/src/slider.rs | 41 ++++++++++++++++++----------------- widget/src/vertical_slider.rs | 41 ++++++++++++++++++----------------- 2 files changed, 42 insertions(+), 40 deletions(-) diff --git a/widget/src/slider.rs b/widget/src/slider.rs index 3d1d08ba33..ef451d438c 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -296,12 +296,13 @@ where } else if cursor_position.x >= bounds.x + bounds.width { Some(*range.end()) } else { - let step = match step_fine { - Some(step_fine) if state.keyboard_modifiers.shift() => { - step_fine.into() - } - _ => step.into(), - }; + let step = if state.keyboard_modifiers.shift() { + step_fine.unwrap_or(step) + } else { + step + } + .into(); + let start = (*range.start()).into(); let end = (*range.end()).into(); @@ -318,15 +319,15 @@ where }; let increment = |value: T| -> Option { - let step = match step_fine { - Some(step_fine) if state.keyboard_modifiers.shift() => { - step_fine.into() - } - _ => step.into(), - }; + let step = if state.keyboard_modifiers.shift() { + step_fine.unwrap_or(step) + } else { + step + } + .into(); let steps = (value.into() / step).round(); - let new_value = step * (steps + f64::from(1)); + let new_value = step * (steps + 1.0); if new_value > (*range.end()).into() { return Some(*range.end()); @@ -336,15 +337,15 @@ where }; let decrement = |value: T| -> Option { - let step = match step_fine { - Some(step_fine) if state.keyboard_modifiers.shift() => { - step_fine.into() - } - _ => step.into(), - }; + let step = if state.keyboard_modifiers.shift() { + step_fine.unwrap_or(step) + } else { + step + } + .into(); let steps = (value.into() / step).round(); - let new_value = step * (steps - f64::from(1)); + let new_value = step * (steps - 1.0); if new_value < (*range.start()).into() { return Some(*range.start()); diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index 168d784881..9c49d8f5d1 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -295,12 +295,13 @@ where } else if cursor_position.y <= bounds.y { Some(*range.end()) } else { - let step = match step_fine { - Some(step_fine) if state.keyboard_modifiers.shift() => { - step_fine.into() - } - _ => step.into(), - }; + let step = if state.keyboard_modifiers.shift() { + step_fine.unwrap_or(step) + } else { + step + } + .into(); + let start = (*range.start()).into(); let end = (*range.end()).into(); @@ -318,15 +319,15 @@ where }; let increment = |value: T| -> Option { - let step = match step_fine { - Some(step_fine) if state.keyboard_modifiers.shift() => { - step_fine.into() - } - _ => step.into(), - }; + let step = if state.keyboard_modifiers.shift() { + step_fine.unwrap_or(step) + } else { + step + } + .into(); let steps = (value.into() / step).round(); - let new_value = step * (steps + f64::from(1)); + let new_value = step * (steps + 1.0); if new_value > (*range.end()).into() { return Some(*range.end()); @@ -336,15 +337,15 @@ where }; let decrement = |value: T| -> Option { - let step = match step_fine { - Some(step_fine) if state.keyboard_modifiers.shift() => { - step_fine.into() - } - _ => step.into(), - }; + let step = if state.keyboard_modifiers.shift() { + step_fine.unwrap_or(step) + } else { + step + } + .into(); let steps = (value.into() / step).round(); - let new_value = step * (steps - f64::from(1)); + let new_value = step * (steps - 1.0); if new_value < (*range.start()).into() { return Some(*range.start()); From cd03a0dd24e300a8a39006e99ee647045e370591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 31 Jan 2024 21:53:29 +0100 Subject: [PATCH 4/7] Rename `step_fine` in `slider` to `shift_step` --- widget/src/slider.rs | 25 +++++++++++++------------ widget/src/vertical_slider.rs | 23 ++++++++++++----------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/widget/src/slider.rs b/widget/src/slider.rs index ef451d438c..a372f8e7f7 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -51,7 +51,7 @@ where { range: RangeInclusive, step: T, - step_fine: Option, + shift_step: Option, value: T, default: Option, on_change: Box Message + 'a>, @@ -63,7 +63,7 @@ where impl<'a, T, Message, Theme> Slider<'a, T, Message, Theme> where - T: Copy + From + std::cmp::PartialOrd, + T: Copy + From + PartialOrd, Message: Clone, Theme: StyleSheet, { @@ -99,7 +99,7 @@ where default: None, range, step: T::from(1), - step_fine: None, + shift_step: None, on_change: Box::new(on_change), on_release: None, width: Length::Fill, @@ -150,10 +150,11 @@ where self } - /// Sets the optional fine-grained step size for the [`Slider`]. - /// If set, this value is used as the step size while shift is pressed. - pub fn step_fine(mut self, step_fine: impl Into) -> Self { - self.step_fine = Some(step_fine.into()); + /// Sets the optional "shift" step for the [`Slider`]. + /// + /// If set, this value is used as the step while the shift key is pressed. + pub fn shift_step(mut self, shift_step: impl Into) -> Self { + self.shift_step = Some(shift_step.into()); self } } @@ -211,7 +212,7 @@ where self.default, &self.range, self.step, - self.step_fine, + self.shift_step, self.on_change.as_ref(), &self.on_release, ) @@ -278,7 +279,7 @@ pub fn update( default: Option, range: &RangeInclusive, step: T, - step_fine: Option, + shift_step: Option, on_change: &dyn Fn(T) -> Message, on_release: &Option, ) -> event::Status @@ -297,7 +298,7 @@ where Some(*range.end()) } else { let step = if state.keyboard_modifiers.shift() { - step_fine.unwrap_or(step) + shift_step.unwrap_or(step) } else { step } @@ -320,7 +321,7 @@ where let increment = |value: T| -> Option { let step = if state.keyboard_modifiers.shift() { - step_fine.unwrap_or(step) + shift_step.unwrap_or(step) } else { step } @@ -338,7 +339,7 @@ where let decrement = |value: T| -> Option { let step = if state.keyboard_modifiers.shift() { - step_fine.unwrap_or(step) + shift_step.unwrap_or(step) } else { step } diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index 9c49d8f5d1..06c2775298 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -48,7 +48,7 @@ where { range: RangeInclusive, step: T, - step_fine: Option, + shift_step: Option, value: T, default: Option, on_change: Box Message + 'a>, @@ -96,7 +96,7 @@ where default: None, range, step: T::from(1), - step_fine: None, + shift_step: None, on_change: Box::new(on_change), on_release: None, width: Self::DEFAULT_WIDTH, @@ -147,10 +147,11 @@ where self } - /// Sets the optional fine-grained step size for the [`VerticalSlider`]. - /// If set, this value is used as the step size while shift is pressed. - pub fn step_fine(mut self, step_fine: impl Into) -> Self { - self.step_fine = Some(step_fine.into()); + /// Sets the optional "shift" step for the [`VerticalSlider`]. + /// + /// If set, this value is used as the step while the shift key is pressed. + pub fn shift_step(mut self, shift_step: impl Into) -> Self { + self.shift_step = Some(shift_step.into()); self } } @@ -208,7 +209,7 @@ where self.default, &self.range, self.step, - self.step_fine, + self.shift_step, self.on_change.as_ref(), &self.on_release, ) @@ -276,7 +277,7 @@ pub fn update( default: Option, range: &RangeInclusive, step: T, - step_fine: Option, + shift_step: Option, on_change: &dyn Fn(T) -> Message, on_release: &Option, ) -> event::Status @@ -296,7 +297,7 @@ where Some(*range.end()) } else { let step = if state.keyboard_modifiers.shift() { - step_fine.unwrap_or(step) + shift_step.unwrap_or(step) } else { step } @@ -320,7 +321,7 @@ where let increment = |value: T| -> Option { let step = if state.keyboard_modifiers.shift() { - step_fine.unwrap_or(step) + shift_step.unwrap_or(step) } else { step } @@ -338,7 +339,7 @@ where let decrement = |value: T| -> Option { let step = if state.keyboard_modifiers.shift() { - step_fine.unwrap_or(step) + shift_step.unwrap_or(step) } else { step } From 468f6a461a6bed9b4aaa905005559f04f16755ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 31 Jan 2024 21:56:55 +0100 Subject: [PATCH 5/7] Simplify `slider` example --- examples/slider/src/main.rs | 41 ++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/examples/slider/src/main.rs b/examples/slider/src/main.rs index e9820aed43..f71dac01fa 100644 --- a/examples/slider/src/main.rs +++ b/examples/slider/src/main.rs @@ -11,10 +11,10 @@ pub enum Message { } pub struct Slider { - slider_value: u8, - slider_default: u8, - slider_step: u8, - slider_step_fine: u8, + value: u8, + default: u8, + step: u8, + shift_step: u8, } impl Sandbox for Slider { @@ -22,10 +22,10 @@ impl Sandbox for Slider { fn new() -> Slider { Slider { - slider_value: 50, - slider_default: 50, - slider_step: 5, - slider_step_fine: 1, + value: 50, + default: 50, + step: 5, + shift_step: 1, } } @@ -36,34 +36,29 @@ impl Sandbox for Slider { fn update(&mut self, message: Message) { match message { Message::SliderChanged(value) => { - self.slider_value = value; + self.value = value; } } } fn view(&self) -> Element { - let value = self.slider_value; - let default = self.slider_default; - let step = self.slider_step; - let step_fine = self.slider_step_fine; - let h_slider = container( - slider(0..=100, value, Message::SliderChanged) - .default(default) - .step(step) - .step_fine(step_fine), + slider(0..=100, self.value, Message::SliderChanged) + .default(self.default) + .step(self.step) + .shift_step(self.shift_step), ) .width(250); let v_slider = container( - vertical_slider(0..=100, value, Message::SliderChanged) - .default(default) - .step(step) - .step_fine(step_fine), + vertical_slider(0..=100, self.value, Message::SliderChanged) + .default(self.default) + .step(self.step) + .shift_step(self.shift_step), ) .height(200); - let text = text(format!("{value}")); + let text = text(self.value); container( column![ From 55791e45ebac908c59609641649e0f596b698303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 31 Jan 2024 21:58:21 +0100 Subject: [PATCH 6/7] Fix documentation of `default` method for `slider` --- widget/src/slider.rs | 3 ++- widget/src/vertical_slider.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/widget/src/slider.rs b/widget/src/slider.rs index a372f8e7f7..65bc1772eb 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -109,7 +109,8 @@ where } /// Sets the optional default value for the [`Slider`]. - /// If set, [`Slider`] will reset to this value when doubled-clicked, ctrl-clicked, or command-clicked. + /// + /// If set, the [`Slider`] will reset to this value when ctrl-clicked or command-clicked. pub fn default(mut self, default: impl Into) -> Self { self.default = Some(default.into()); self diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index 06c2775298..8f7c88da72 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -106,7 +106,8 @@ where } /// Sets the optional default value for the [`VerticalSlider`]. - /// If set, [`VerticalSlider`] will reset to this value when doubled-clicked, ctrl-clicked, or command-clicked. + /// + /// If set, the [`VerticalSlider`] will reset to this value when ctrl-clicked or command-clicked. pub fn default(mut self, default: impl Into) -> Self { self.default = Some(default.into()); self From 41bffa448b8a5901bdb3461d6d364b22e8a84a3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 31 Jan 2024 22:07:59 +0100 Subject: [PATCH 7/7] Update `CHANGELOG` --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2149e4fc8d..c836051443 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `theme::Custom::with_fn` to generate completely custom themes. [#2067](https://github.com/iced-rs/iced/pull/2067) - `style` attribute for `Font`. [#2041](https://github.com/iced-rs/iced/pull/2041) - Texture filtering options for `Image`. [#1894](https://github.com/iced-rs/iced/pull/1894) +- `default` and `shift_step` methods for `slider` widgets. [#2100](https://github.com/iced-rs/iced/pull/2100) ### Changed - Enable WebGPU backend in `wgpu` by default instead of WebGL. [#2068](https://github.com/iced-rs/iced/pull/2068) @@ -99,6 +100,7 @@ Many thanks to... - @jhff - @jim-ec - @joshuamegnauth54 +- @jpttrssn - @lufte - @matze - @MichalLebeda