//! Display an interactive selector of a single value from a range of values. //! //! A [`Slider`] has some local [`State`]. use crate::event::{self, Event}; use crate::layout; use crate::mouse; use crate::renderer; use crate::touch; use crate::{ Background, Clipboard, Color, Element, Layout, Length, Point, Rectangle, Shell, Size, Widget, }; use std::ops::RangeInclusive; pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet}; /// An horizontal bar and a handle that selects a single value from a range of /// values. /// /// A [`Slider`] will try to fill the horizontal space of its container. /// /// The [`Slider`] range of numeric values is generic and its step size defaults /// to 1 unit. /// /// # Example /// ``` /// # use iced_native::widget::slider; /// # use iced_native::renderer::Null; /// # /// # type Slider<'a, T, Message> = slider::Slider<'a, T, Message, Null>; /// #[derive(Clone)] /// pub enum Message { /// SliderChanged(f32), /// } /// /// let state = &mut slider::State::new(); /// let value = 50.0; /// /// Slider::new(state, 0.0..=100.0, value, Message::SliderChanged); /// ``` /// /// ![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, T, Message, Renderer> where Renderer: crate::Renderer, Renderer::Theme: StyleSheet, { state: &'a mut State, range: RangeInclusive, step: T, value: T, on_change: Box Message>, on_release: Option, width: Length, height: u16, style: ::Style, } impl<'a, T, Message, Renderer> Slider<'a, T, Message, Renderer> where T: Copy + From + std::cmp::PartialOrd, Message: Clone, Renderer: crate::Renderer, Renderer::Theme: StyleSheet, { /// The default height of a [`Slider`]. pub const DEFAULT_HEIGHT: u16 = 22; /// Creates a new [`Slider`]. /// /// It expects: /// * the local [`State`] of the [`Slider`] /// * an inclusive range of possible values /// * the current value of the [`Slider`] /// * a function that will be called when the [`Slider`] is dragged. /// It receives the new value of the [`Slider`] and must produce a /// `Message`. pub fn new( state: &'a mut State, range: RangeInclusive, value: T, on_change: F, ) -> Self where 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, range, step: T::from(1), on_change: Box::new(on_change), on_release: None, width: Length::Fill, height: Self::DEFAULT_HEIGHT, style: Default::default(), } } /// Sets the release message of the [`Slider`]. /// This is called when the mouse is released from the slider. /// /// 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`]. pub fn style( mut self, style: impl Into<::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 } } /// Processes an [`Event`] and updates the [`State`] of a [`Slider`] /// accordingly. pub fn update( event: Event, layout: Layout<'_>, cursor_position: Point, shell: &mut Shell<'_, Message>, state: &mut State, value: &mut T, range: &RangeInclusive, step: T, on_change: &dyn Fn(T) -> Message, on_release: &Option, ) -> event::Status where T: Copy + Into + num_traits::FromPrimitive, Message: Clone, { let is_dragging = state.is_dragging; let mut change = || { let bounds = layout.bounds(); let new_value = if cursor_position.x <= bounds.x { *range.start() } else if cursor_position.x >= bounds.x + bounds.width { *range.end() } else { let step = step.into(); let start = (*range.start()).into(); let end = (*range.end()).into(); let percent = f64::from(cursor_position.x - bounds.x) / f64::from(bounds.width); let steps = (percent * (end - start) / step).round(); let value = steps * step + start; if let Some(value) = T::from_f64(value) { value } else { return; } }; if ((*value).into() - new_value.into()).abs() > f64::EPSILON { shell.publish((on_change)(new_value)); *value = new_value; } }; match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { if layout.bounds().contains(cursor_position) { change(); state.is_dragging = true; return event::Status::Captured; } } Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) | Event::Touch(touch::Event::FingerLifted { .. }) | Event::Touch(touch::Event::FingerLost { .. }) => { if is_dragging { if let Some(on_release) = on_release.clone() { shell.publish(on_release); } state.is_dragging = false; return event::Status::Captured; } } Event::Mouse(mouse::Event::CursorMoved { .. }) | Event::Touch(touch::Event::FingerMoved { .. }) => { if is_dragging { change(); return event::Status::Captured; } } _ => {} } event::Status::Ignored } /// Draws a [`Slider`]. pub fn draw( renderer: &mut R, layout: Layout<'_>, cursor_position: Point, state: &State, value: T, range: &RangeInclusive, style_sheet: &dyn StyleSheet