Skip to content

Commit 453bf38

Browse files
committed
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
1 parent bc9bb28 commit 453bf38

File tree

3 files changed

+315
-38
lines changed

3 files changed

+315
-38
lines changed

examples/slider/src/main.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,21 @@ pub enum Message {
1212

1313
pub struct Slider {
1414
slider_value: u8,
15+
slider_default: u8,
16+
slider_step: u8,
17+
slider_step_fine: u8,
1518
}
1619

1720
impl Sandbox for Slider {
1821
type Message = Message;
1922

2023
fn new() -> Slider {
21-
Slider { slider_value: 50 }
24+
Slider {
25+
slider_value: 50,
26+
slider_default: 50,
27+
slider_step: 5,
28+
slider_step_fine: 1,
29+
}
2230
}
2331

2432
fn title(&self) -> String {
@@ -35,14 +43,25 @@ impl Sandbox for Slider {
3543

3644
fn view(&self) -> Element<Message> {
3745
let value = self.slider_value;
46+
let default = self.slider_default;
47+
let step = self.slider_step;
48+
let step_fine = self.slider_step_fine;
3849

39-
let h_slider =
40-
container(slider(0..=100, value, Message::SliderChanged))
41-
.width(250);
50+
let h_slider = container(
51+
slider(0..=100, value, Message::SliderChanged)
52+
.default(default)
53+
.step(step)
54+
.step_fine(step_fine),
55+
)
56+
.width(250);
4257

43-
let v_slider =
44-
container(vertical_slider(0..=100, value, Message::SliderChanged))
45-
.height(200);
58+
let v_slider = container(
59+
vertical_slider(0..=100, value, Message::SliderChanged)
60+
.default(default)
61+
.step(step)
62+
.step_fine(step_fine),
63+
)
64+
.height(200);
4665

4766
let text = text(format!("{value}"));
4867

widget/src/slider.rs

Lines changed: 144 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//! A [`Slider`] has some local [`State`].
44
use crate::core::event::{self, Event};
55
use crate::core::layout;
6-
use crate::core::mouse;
6+
use crate::core::mouse::{self, click};
77
use crate::core::renderer;
88
use crate::core::touch;
99
use crate::core::widget::tree::{self, Tree};
@@ -14,6 +14,8 @@ use crate::core::{
1414

1515
use std::ops::RangeInclusive;
1616

17+
use iced_renderer::core::keyboard;
18+
use iced_runtime::keyboard::KeyCode;
1719
pub use iced_style::slider::{
1820
Appearance, Handle, HandleShape, Rail, StyleSheet,
1921
};
@@ -50,7 +52,9 @@ where
5052
{
5153
range: RangeInclusive<T>,
5254
step: T,
55+
step_fine: Option<T>,
5356
value: T,
57+
default: Option<T>,
5458
on_change: Box<dyn Fn(T) -> Message + 'a>,
5559
on_release: Option<Message>,
5660
width: Length,
@@ -94,8 +98,10 @@ where
9498

9599
Slider {
96100
value,
101+
default: None,
97102
range,
98103
step: T::from(1),
104+
step_fine: None,
99105
on_change: Box::new(on_change),
100106
on_release: None,
101107
width: Length::Fill,
@@ -104,6 +110,13 @@ where
104110
}
105111
}
106112

113+
/// Sets the optional default value for the [`Slider`].
114+
/// If set, [`Slider`] will reset to this value when doubled-clicked, ctrl-clicked, or command-clicked.
115+
pub fn default(mut self, default: impl Into<T>) -> Self {
116+
self.default = Some(default.into());
117+
self
118+
}
119+
107120
/// Sets the release message of the [`Slider`].
108121
/// This is called when the mouse is released from the slider.
109122
///
@@ -141,6 +154,13 @@ where
141154
self.step = step.into();
142155
self
143156
}
157+
158+
/// Sets the optional fine-grained step size for the [`Slider`].
159+
/// If set, this value is used as the step size while shift is pressed.
160+
pub fn step_fine(mut self, step_fine: impl Into<T>) -> Self {
161+
self.step_fine = Some(step_fine.into());
162+
self
163+
}
144164
}
145165

146166
impl<'a, T, Message, Renderer> Widget<Message, Renderer>
@@ -197,8 +217,10 @@ where
197217
shell,
198218
tree.state.downcast_mut::<State>(),
199219
&mut self.value,
220+
self.default,
200221
&self.range,
201222
self.step,
223+
self.step_fine,
202224
self.on_change.as_ref(),
203225
&self.on_release,
204226
)
@@ -262,8 +284,10 @@ pub fn update<Message, T>(
262284
shell: &mut Shell<'_, Message>,
263285
state: &mut State,
264286
value: &mut T,
287+
default: Option<T>,
265288
range: &RangeInclusive<T>,
266289
step: T,
290+
step_fine: Option<T>,
267291
on_change: &dyn Fn(T) -> Message,
268292
on_release: &Option<Message>,
269293
) -> event::Status
@@ -273,14 +297,19 @@ where
273297
{
274298
let is_dragging = state.is_dragging;
275299

276-
let mut change = |cursor_position: Point| {
300+
let change_cursor_position = |cursor_position: Point| -> Option<T> {
277301
let bounds = layout.bounds();
278302
let new_value = if cursor_position.x <= bounds.x {
279-
*range.start()
303+
Some(*range.start())
280304
} else if cursor_position.x >= bounds.x + bounds.width {
281-
*range.end()
305+
Some(*range.end())
282306
} else {
283-
let step = step.into();
307+
let step = match step_fine {
308+
Some(step_fine) if state.keyboard_modifiers.shift() => {
309+
step_fine.into()
310+
}
311+
_ => step.into(),
312+
};
284313
let start = (*range.start()).into();
285314
let end = (*range.end()).into();
286315

@@ -290,17 +319,67 @@ where
290319
let steps = (percent * (end - start) / step).round();
291320
let value = steps * step + start;
292321

293-
if let Some(value) = T::from_f64(value) {
294-
value
295-
} else {
296-
return;
322+
T::from_f64(value)
323+
};
324+
325+
new_value
326+
};
327+
328+
let increment = |value: T| -> Option<T> {
329+
let step = match step_fine {
330+
Some(step_fine) if state.keyboard_modifiers.shift() => {
331+
step_fine.into()
332+
}
333+
_ => step.into(),
334+
};
335+
336+
let steps = (value.into() / step).round();
337+
let new_value = step * (steps + f64::from(1));
338+
339+
if new_value > (*range.end()).into() {
340+
return Some(*range.end());
341+
}
342+
343+
T::from_f64(new_value)
344+
};
345+
346+
let decrement = |value: T| -> Option<T> {
347+
let step = match step_fine {
348+
Some(step_fine) if state.keyboard_modifiers.shift() => {
349+
step_fine.into()
297350
}
351+
_ => step.into(),
298352
};
299353

300-
if ((*value).into() - new_value.into()).abs() > f64::EPSILON {
301-
shell.publish((on_change)(new_value));
354+
let steps = (value.into() / step).round();
355+
let new_value = step * (steps - f64::from(1));
356+
357+
if new_value < (*range.start()).into() {
358+
return Some(*range.start());
359+
}
360+
361+
T::from_f64(new_value)
362+
};
363+
364+
enum Change {
365+
Default,
366+
CursorPosition(Point),
367+
Increment,
368+
Decrement,
369+
}
302370

303-
*value = new_value;
371+
let mut change = |change: Change| {
372+
if let Some(new_value) = match change {
373+
Change::Default => default,
374+
Change::CursorPosition(point) => change_cursor_position(point),
375+
Change::Increment => increment(*value),
376+
Change::Decrement => decrement(*value),
377+
} {
378+
if ((*value).into() - new_value.into()).abs() > f64::EPSILON {
379+
shell.publish((on_change)(new_value));
380+
381+
*value = new_value;
382+
}
304383
}
305384
};
306385

@@ -309,8 +388,31 @@ where
309388
| Event::Touch(touch::Event::FingerPressed { .. }) => {
310389
if let Some(cursor_position) = cursor.position_over(layout.bounds())
311390
{
312-
change(cursor_position);
313-
state.is_dragging = true;
391+
let click =
392+
mouse::Click::new(cursor_position, state.last_click);
393+
394+
match click.kind() {
395+
click::Kind::Single => {
396+
if state.keyboard_modifiers.control()
397+
|| state.keyboard_modifiers.command()
398+
{
399+
change(Change::Default);
400+
state.is_dragging = false;
401+
} else {
402+
change(Change::CursorPosition(cursor_position));
403+
state.is_dragging = true;
404+
}
405+
}
406+
click::Kind::Double => {
407+
change(Change::Default);
408+
state.is_dragging = false;
409+
}
410+
mouse::click::Kind::Triple => {
411+
state.is_dragging = false;
412+
}
413+
}
414+
415+
state.last_click = Some(click);
314416

315417
return event::Status::Captured;
316418
}
@@ -330,11 +432,27 @@ where
330432
Event::Mouse(mouse::Event::CursorMoved { .. })
331433
| Event::Touch(touch::Event::FingerMoved { .. }) => {
332434
if is_dragging {
333-
let _ = cursor.position().map(change);
435+
let _ = cursor
436+
.position()
437+
.map(|point| change(Change::CursorPosition(point)));
334438

335439
return event::Status::Captured;
336440
}
337441
}
442+
Event::Keyboard(keyboard::Event::KeyPressed { key_code, .. }) => {
443+
if cursor.position_over(layout.bounds()).is_some() {
444+
match key_code {
445+
KeyCode::Up => change(Change::Increment),
446+
KeyCode::Down => change(Change::Decrement),
447+
_ => (),
448+
}
449+
450+
return event::Status::Captured;
451+
}
452+
}
453+
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
454+
state.keyboard_modifiers = modifiers;
455+
}
338456
_ => {}
339457
}
340458

@@ -459,9 +577,11 @@ pub fn mouse_interaction(
459577
}
460578

461579
/// The local state of a [`Slider`].
462-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
580+
#[derive(Debug, Clone, Copy, Default)]
463581
pub struct State {
464582
is_dragging: bool,
583+
last_click: Option<mouse::Click>,
584+
keyboard_modifiers: keyboard::Modifiers,
465585
}
466586

467587
impl State {
@@ -470,3 +590,11 @@ impl State {
470590
State::default()
471591
}
472592
}
593+
594+
impl PartialEq for State {
595+
fn eq(&self, other: &Self) -> bool {
596+
self.is_dragging == other.is_dragging
597+
}
598+
}
599+
600+
impl Eq for State {}

0 commit comments

Comments
 (0)