summaryrefslogtreecommitdiffstats
path: root/native/src/widget/slider.rs
diff options
context:
space:
mode:
Diffstat (limited to 'native/src/widget/slider.rs')
-rw-r--r--native/src/widget/slider.rs182
1 files changed, 117 insertions, 65 deletions
diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs
index c98cebb6..755e6b2b 100644
--- a/native/src/widget/slider.rs
+++ b/native/src/widget/slider.rs
@@ -1,13 +1,12 @@
//! Display an interactive selector of a single value from a range of values.
//!
//! A [`Slider`] has some local [`State`].
-//!
-//! [`Slider`]: struct.Slider.html
-//! [`State`]: struct.State.html
+use crate::event::{self, Event};
+use crate::layout;
+use crate::mouse;
+use crate::touch::{self, Touch};
use crate::{
- input::{mouse, touch, ButtonState, Touch},
- layout, Clipboard, Element, Event, Hasher, Layout, Length, Point,
- Rectangle, Size, Widget,
+ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
};
use std::{hash::Hash, ops::RangeInclusive};
@@ -17,13 +16,15 @@ use std::{hash::Hash, ops::RangeInclusive};
///
/// A [`Slider`] will try to fill the horizontal space of its container.
///
-/// [`Slider`]: struct.Slider.html
+/// The [`Slider`] range of numeric values is generic and its step size defaults
+/// to 1 unit.
///
/// # Example
/// ```
/// # use iced_native::{slider, renderer::Null};
/// #
-/// # pub type Slider<'a, Message> = iced_native::Slider<'a, Message, Null>;
+/// # pub type Slider<'a, T, Message> = iced_native::Slider<'a, T, Message, Null>;
+/// #[derive(Clone)]
/// pub enum Message {
/// SliderChanged(f32),
/// }
@@ -36,16 +37,24 @@ use std::{hash::Hash, ops::RangeInclusive};
///
/// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true)
#[allow(missing_debug_implementations)]
-pub struct Slider<'a, Message, Renderer: self::Renderer> {
+pub struct Slider<'a, T, Message, Renderer: self::Renderer> {
state: &'a mut State,
- range: RangeInclusive<f32>,
- value: f32,
- on_change: Box<dyn Fn(f32) -> Message>,
+ range: RangeInclusive<T>,
+ step: T,
+ value: T,
+ on_change: Box<dyn Fn(T) -> Message>,
+ on_release: Option<Message>,
width: Length,
+ height: u16,
style: Renderer::Style,
}
-impl<'a, Message, Renderer: self::Renderer> Slider<'a, Message, Renderer> {
+impl<'a, T, Message, Renderer> Slider<'a, T, Message, Renderer>
+where
+ T: Copy + From<u8> + std::cmp::PartialOrd,
+ Message: Clone,
+ Renderer: self::Renderer,
+{
/// Creates a new [`Slider`].
///
/// It expects:
@@ -55,48 +64,77 @@ impl<'a, Message, Renderer: self::Renderer> Slider<'a, Message, Renderer> {
/// * a function that will be called when the [`Slider`] is dragged.
/// It receives the new value of the [`Slider`] and must produce a
/// `Message`.
- ///
- /// [`Slider`]: struct.Slider.html
- /// [`State`]: struct.State.html
pub fn new<F>(
state: &'a mut State,
- range: RangeInclusive<f32>,
- value: f32,
+ range: RangeInclusive<T>,
+ value: T,
on_change: F,
) -> Self
where
- F: 'static + Fn(f32) -> Message,
+ F: 'static + Fn(T) -> Message,
{
+ let value = if value >= *range.start() {
+ value
+ } else {
+ *range.start()
+ };
+
+ let value = if value <= *range.end() {
+ value
+ } else {
+ *range.end()
+ };
+
Slider {
state,
- value: value.max(*range.start()).min(*range.end()),
+ value,
range,
+ step: T::from(1),
on_change: Box::new(on_change),
+ on_release: None,
width: Length::Fill,
+ height: Renderer::DEFAULT_HEIGHT,
style: Renderer::Style::default(),
}
}
- /// Sets the width of the [`Slider`].
+ /// Sets the release message of the [`Slider`].
+ /// This is called when the mouse is released from the slider.
///
- /// [`Slider`]: struct.Slider.html
+ /// Typically, the user's interaction with the slider is finished when this message is produced.
+ /// This is useful if you need to spawn a long-running task from the slider's result, where
+ /// the default on_change message could create too many events.
+ pub fn on_release(mut self, on_release: Message) -> Self {
+ self.on_release = Some(on_release);
+ self
+ }
+
+ /// Sets the width of the [`Slider`].
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
+ /// Sets the height of the [`Slider`].
+ pub fn height(mut self, height: u16) -> Self {
+ self.height = height;
+ self
+ }
+
/// Sets the style of the [`Slider`].
- ///
- /// [`Slider`]: struct.Slider.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
}
+
+ /// Sets the step size of the [`Slider`].
+ pub fn step(mut self, step: T) -> Self {
+ self.step = step;
+ self
+ }
}
/// The local state of a [`Slider`].
-///
-/// [`Slider`]: struct.Slider.html
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct State {
is_dragging: bool,
@@ -104,16 +142,16 @@ pub struct State {
impl State {
/// Creates a new [`State`].
- ///
- /// [`State`]: struct.State.html
pub fn new() -> State {
State::default()
}
}
-impl<'a, Message, Renderer> Widget<Message, Renderer>
- for Slider<'a, Message, Renderer>
+impl<'a, T, Message, Renderer> Widget<Message, Renderer>
+ for Slider<'a, T, Message, Renderer>
where
+ T: Copy + Into<f64> + num_traits::FromPrimitive,
+ Message: Clone,
Renderer: self::Renderer,
{
fn width(&self) -> Length {
@@ -126,12 +164,11 @@ where
fn layout(
&self,
- renderer: &Renderer,
+ _renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- let limits = limits
- .width(self.width)
- .height(Length::Units(renderer.height() as u16));
+ let limits =
+ limits.width(self.width).height(Length::Units(self.height));
let size = limits.resolve(Size::ZERO);
@@ -146,28 +183,32 @@ where
messages: &mut Vec<Message>,
_renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
let mut change = || {
let bounds = layout.bounds();
-
if cursor_position.x <= bounds.x {
messages.push((self.on_change)(*self.range.start()));
} else if cursor_position.x >= bounds.x + bounds.width {
messages.push((self.on_change)(*self.range.end()));
} else {
- let percent = (cursor_position.x - bounds.x) / bounds.width;
- let value = (self.range.end() - self.range.start()) * percent
- + self.range.start();
+ let step = self.step.into();
+ let start = (*self.range.start()).into();
+ let end = (*self.range.end()).into();
+
+ let percent = f64::from(cursor_position.x - bounds.x)
+ / f64::from(bounds.width);
- messages.push((self.on_change)(value));
+ let steps = (percent * (end - start) / step).round();
+ let value = steps * step + start;
+
+ if let Some(value) = T::from_f64(value) {
+ messages.push((self.on_change)(value));
+ }
}
};
match event {
- Event::Mouse(mouse::Event::Input {
- button: mouse::Button::Left,
- state: ButtonState::Pressed,
- })
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(Touch {
phase: touch::Phase::Started,
..
@@ -175,17 +216,23 @@ where
if layout.bounds().contains(cursor_position) {
change();
self.state.is_dragging = true;
+
+ return event::Status::Captured;
}
}
- Event::Mouse(mouse::Event::Input {
- button: mouse::Button::Left,
- state: ButtonState::Released,
- })
+ Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
| Event::Touch(Touch {
phase: touch::Phase::Ended,
..
}) => {
- self.state.is_dragging = false;
+ if self.state.is_dragging {
+ if let Some(on_release) = self.on_release.clone() {
+ messages.push(on_release);
+ }
+ self.state.is_dragging = false;
+
+ return event::Status::Captured;
+ }
}
Event::Mouse(mouse::Event::CursorMoved { .. })
| Event::Touch(Touch {
@@ -194,10 +241,14 @@ where
}) => {
if self.state.is_dragging {
change();
+
+ return event::Status::Captured;
}
}
_ => {}
}
+
+ event::Status::Ignored
}
fn draw(
@@ -206,18 +257,25 @@ where
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
+ let start = *self.range.start();
+ let end = *self.range.end();
+
renderer.draw(
layout.bounds(),
cursor_position,
- self.range.clone(),
- self.value,
+ start.into() as f32..=end.into() as f32,
+ self.value.into() as f32,
self.state.is_dragging,
&self.style,
)
}
fn hash_layout(&self, state: &mut Hasher) {
+ struct Marker;
+ std::any::TypeId::of::<Marker>().hash(state);
+
self.width.hash(state);
}
}
@@ -227,16 +285,13 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Slider`] in your user interface.
///
-/// [`Slider`]: struct.Slider.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// The style supported by this renderer.
type Style: Default;
- /// Returns the height of the [`Slider`].
- ///
- /// [`Slider`]: struct.Slider.html
- fn height(&self) -> u32;
+ /// The default height of a [`Slider`].
+ const DEFAULT_HEIGHT: u16;
/// Draws a [`Slider`].
///
@@ -246,10 +301,6 @@ pub trait Renderer: crate::Renderer {
/// * the local state of the [`Slider`]
/// * the range of values of the [`Slider`]
/// * the current value of the [`Slider`]
- ///
- /// [`Slider`]: struct.Slider.html
- /// [`State`]: struct.State.html
- /// [`Class`]: enum.Class.html
fn draw(
&mut self,
bounds: Rectangle,
@@ -261,14 +312,15 @@ pub trait Renderer: crate::Renderer {
) -> Self::Output;
}
-impl<'a, Message, Renderer> From<Slider<'a, Message, Renderer>>
+impl<'a, T, Message, Renderer> From<Slider<'a, T, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'static + self::Renderer,
- Message: 'static,
+ T: 'a + Copy + Into<f64> + num_traits::FromPrimitive,
+ Message: 'a + Clone,
+ Renderer: 'a + self::Renderer,
{
fn from(
- slider: Slider<'a, Message, Renderer>,
+ slider: Slider<'a, T, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(slider)
}