summaryrefslogtreecommitdiffstats
path: root/widget
diff options
context:
space:
mode:
authorLibravatar Jonatan Pettersson <jonatan.pettersson@proton.me>2023-09-20 20:56:50 +0200
committerLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-01-31 21:25:19 +0100
commit5e2b3d4a51be97c77124dcd60e5ee3bed1b19826 (patch)
treec742f2e87be4a9360c8b958a1730820ddb349107 /widget
parent66c8a804c6b665718a2cc80222ba8b906b543014 (diff)
downloadiced-5e2b3d4a51be97c77124dcd60e5ee3bed1b19826.tar.gz
iced-5e2b3d4a51be97c77124dcd60e5ee3bed1b19826.tar.bz2
iced-5e2b3d4a51be97c77124dcd60e5ee3bed1b19826.zip
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
Diffstat (limited to 'widget')
-rw-r--r--widget/src/slider.rs164
-rw-r--r--widget/src/vertical_slider.rs163
2 files changed, 296 insertions, 31 deletions
diff --git a/widget/src/slider.rs b/widget/src/slider.rs
index 5c3b6384..3b7de8b9 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<T>,
step: T,
+ step_fine: Option<T>,
value: T,
+ default: Option<T>,
on_change: Box<dyn Fn(T) -> Message + 'a>,
on_release: Option<Message>,
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<T>) -> 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<T>) -> Self {
+ self.step_fine = Some(step_fine.into());
+ self
+ }
}
impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
@@ -188,8 +208,10 @@ where
shell,
tree.state.downcast_mut::<State>(),
&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<Message, T>(
shell: &mut Shell<'_, Message>,
state: &mut State,
value: &mut T,
+ default: Option<T>,
range: &RangeInclusive<T>,
step: T,
+ step_fine: Option<T>,
on_change: &dyn Fn(T) -> Message,
on_release: &Option<Message>,
) -> 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<T> {
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<T> {
+ 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<T> {
+ 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<mouse::Click>,
+ 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 d3086a81..c16727b6 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<T>,
step: T,
+ step_fine: Option<T>,
value: T,
+ default: Option<T>,
on_change: Box<dyn Fn(T) -> Message + 'a>,
on_release: Option<Message>,
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<T>) -> 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<T>) -> Self {
+ self.step_fine = Some(step_fine.into());
+ self
+ }
}
impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
@@ -185,8 +206,10 @@ where
shell,
tree.state.downcast_mut::<State>(),
&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<Message, T>(
shell: &mut Shell<'_, Message>,
state: &mut State,
value: &mut T,
+ default: Option<T>,
range: &RangeInclusive<T>,
step: T,
+ step_fine: Option<T>,
on_change: &dyn Fn(T) -> Message,
on_release: &Option<Message>,
) -> 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<T> {
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<T> {
+ 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<T> {
+ 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<mouse::Click>,
+ 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 {}