diff options
Diffstat (limited to 'widget/src/slider.rs')
-rw-r--r-- | widget/src/slider.rs | 548 |
1 files changed, 238 insertions, 310 deletions
diff --git a/widget/src/slider.rs b/widget/src/slider.rs index 65bc1772..ce02a0a6 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -17,7 +17,7 @@ use crate::core::{ use std::ops::RangeInclusive; pub use iced_style::slider::{ - Appearance, Handle, HandleShape, Rail, StyleSheet, + Appearance, Handle, HandleShape, Rail, Status, StyleSheet, }; /// An horizontal bar and a handle that selects a single value from a range of @@ -58,7 +58,7 @@ where on_release: Option<Message>, width: Length, height: f32, - style: Theme::Style, + style: fn(&Theme, Status) -> Appearance, } impl<'a, T, Message, Theme> Slider<'a, T, Message, Theme> @@ -104,7 +104,7 @@ where on_release: None, width: Length::Fill, height: Self::DEFAULT_HEIGHT, - style: Default::default(), + style: Theme::default(), } } @@ -140,8 +140,8 @@ where } /// Sets the style of the [`Slider`]. - pub fn style(mut self, style: impl Into<Theme::Style>) -> Self { - self.style = style.into(); + pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { + self.style = style; self } @@ -173,7 +173,7 @@ where } fn state(&self) -> tree::State { - tree::State::new(State::new()) + tree::State::new(State::default()) } fn size(&self) -> Size<Length> { @@ -203,355 +203,283 @@ where shell: &mut Shell<'_, Message>, _viewport: &Rectangle, ) -> event::Status { - update( - event, - layout, - cursor, - shell, - tree.state.downcast_mut::<State>(), - &mut self.value, - self.default, - &self.range, - self.step, - self.shift_step, - self.on_change.as_ref(), - &self.on_release, - ) - } + let state = tree.state.downcast_mut::<State>(); - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Theme, - _style: &renderer::Style, - layout: Layout<'_>, - cursor: mouse::Cursor, - _viewport: &Rectangle, - ) { - draw( - renderer, - layout, - cursor, - tree.state.downcast_ref::<State>(), - self.value, - &self.range, - theme, - &self.style, - ); - } + let is_dragging = state.is_dragging; + let current_value = self.value; - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor: mouse::Cursor, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - mouse_interaction(layout, cursor, tree.state.downcast_ref::<State>()) - } -} + let locate = |cursor_position: Point| -> Option<T> { + let bounds = layout.bounds(); + let new_value = if cursor_position.x <= bounds.x { + Some(*self.range.start()) + } else if cursor_position.x >= bounds.x + bounds.width { + Some(*self.range.end()) + } else { + let step = if state.keyboard_modifiers.shift() { + self.shift_step.unwrap_or(self.step) + } else { + self.step + } + .into(); -impl<'a, T, Message, Theme, Renderer> From<Slider<'a, T, Message, Theme>> - for Element<'a, Message, Theme, Renderer> -where - T: Copy + Into<f64> + num_traits::FromPrimitive + 'a, - Message: Clone + 'a, - Theme: StyleSheet + 'a, - Renderer: crate::core::Renderer + 'a, -{ - fn from( - slider: Slider<'a, T, Message, Theme>, - ) -> Element<'a, Message, Theme, Renderer> { - Element::new(slider) - } -} + let start = (*self.range.start()).into(); + let end = (*self.range.end()).into(); -/// Processes an [`Event`] and updates the [`State`] of a [`Slider`] -/// accordingly. -pub fn update<Message, T>( - event: Event, - layout: Layout<'_>, - cursor: mouse::Cursor, - shell: &mut Shell<'_, Message>, - state: &mut State, - value: &mut T, - default: Option<T>, - range: &RangeInclusive<T>, - step: T, - shift_step: Option<T>, - on_change: &dyn Fn(T) -> Message, - on_release: &Option<Message>, -) -> event::Status -where - T: Copy + Into<f64> + num_traits::FromPrimitive, - Message: Clone, -{ - let is_dragging = state.is_dragging; - let current_value = *value; + let percent = f64::from(cursor_position.x - bounds.x) + / f64::from(bounds.width); - let locate = |cursor_position: Point| -> Option<T> { - let bounds = layout.bounds(); - let new_value = if cursor_position.x <= bounds.x { - Some(*range.start()) - } else if cursor_position.x >= bounds.x + bounds.width { - Some(*range.end()) - } else { + let steps = (percent * (end - start) / step).round(); + let value = steps * step + start; + + T::from_f64(value) + }; + + new_value + }; + + let increment = |value: T| -> Option<T> { let step = if state.keyboard_modifiers.shift() { - shift_step.unwrap_or(step) + self.shift_step.unwrap_or(self.step) } else { - step + self.step } .into(); - let start = (*range.start()).into(); - let end = (*range.end()).into(); + let steps = (value.into() / step).round(); + let new_value = step * (steps + 1.0); - 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 new_value > (*self.range.end()).into() { + return Some(*self.range.end()); + } - T::from_f64(value) + T::from_f64(new_value) }; - new_value - }; - - let increment = |value: T| -> Option<T> { - let step = if state.keyboard_modifiers.shift() { - shift_step.unwrap_or(step) - } else { - step - } - .into(); - - let steps = (value.into() / step).round(); - let new_value = step * (steps + 1.0); - - if new_value > (*range.end()).into() { - return Some(*range.end()); - } + let decrement = |value: T| -> Option<T> { + let step = if state.keyboard_modifiers.shift() { + self.shift_step.unwrap_or(self.step) + } else { + self.step + } + .into(); - T::from_f64(new_value) - }; + let steps = (value.into() / step).round(); + let new_value = step * (steps - 1.0); - let decrement = |value: T| -> Option<T> { - let step = if state.keyboard_modifiers.shift() { - shift_step.unwrap_or(step) - } else { - step - } - .into(); + if new_value < (*self.range.start()).into() { + return Some(*self.range.start()); + } - let steps = (value.into() / step).round(); - let new_value = step * (steps - 1.0); + T::from_f64(new_value) + }; - if new_value < (*range.start()).into() { - return Some(*range.start()); - } + let change = |new_value: T| { + if (self.value.into() - new_value.into()).abs() > f64::EPSILON { + shell.publish((self.on_change)(new_value)); - T::from_f64(new_value) - }; + self.value = new_value; + } + }; - let change = |new_value: T| { - if ((*value).into() - new_value.into()).abs() > f64::EPSILON { - shell.publish((on_change)(new_value)); + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + if let Some(cursor_position) = + cursor.position_over(layout.bounds()) + { + if state.keyboard_modifiers.command() { + let _ = self.default.map(change); + state.is_dragging = false; + } else { + let _ = locate(cursor_position).map(change); + state.is_dragging = true; + } - *value = new_value; - } - }; - - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - if let Some(cursor_position) = cursor.position_over(layout.bounds()) - { - if state.keyboard_modifiers.command() { - let _ = default.map(change); - state.is_dragging = false; - } else { - let _ = locate(cursor_position).map(change); - state.is_dragging = true; + return event::Status::Captured; } - - 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; + 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) = self.on_release.clone() { + shell.publish(on_release); + } + state.is_dragging = false; - return event::Status::Captured; + return event::Status::Captured; + } } - } - Event::Mouse(mouse::Event::CursorMoved { .. }) - | Event::Touch(touch::Event::FingerMoved { .. }) => { - if is_dragging { - let _ = cursor.position().and_then(locate).map(change); + Event::Mouse(mouse::Event::CursorMoved { .. }) + | Event::Touch(touch::Event::FingerMoved { .. }) => { + if is_dragging { + let _ = cursor.position().and_then(locate).map(change); - return event::Status::Captured; + 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) => { - let _ = increment(current_value).map(change); + Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => { + if cursor.position_over(layout.bounds()).is_some() { + match key { + Key::Named(key::Named::ArrowUp) => { + let _ = increment(current_value).map(change); + } + Key::Named(key::Named::ArrowDown) => { + let _ = decrement(current_value).map(change); + } + _ => (), } - Key::Named(key::Named::ArrowDown) => { - let _ = decrement(current_value).map(change); - } - _ => (), - } - return event::Status::Captured; + return event::Status::Captured; + } } + Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { + state.keyboard_modifiers = modifiers; + } + _ => {} } - Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { - state.keyboard_modifiers = modifiers; - } - _ => {} + + event::Status::Ignored } - event::Status::Ignored -} + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Theme, + _style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + _viewport: &Rectangle, + ) { + let state = tree.state.downcast_ref::<State>(); + let bounds = layout.bounds(); + let is_mouse_over = cursor.is_over(bounds); -/// Draws a [`Slider`]. -pub fn draw<T, Theme, Renderer>( - renderer: &mut Renderer, - layout: Layout<'_>, - cursor: mouse::Cursor, - state: &State, - value: T, - range: &RangeInclusive<T>, - theme: &Theme, - style: &Theme::Style, -) where - T: Into<f64> + Copy, - Theme: StyleSheet, - Renderer: crate::core::Renderer, -{ - let bounds = layout.bounds(); - let is_mouse_over = cursor.is_over(bounds); - - let style = if state.is_dragging { - theme.dragging(style) - } else if is_mouse_over { - theme.hovered(style) - } else { - theme.active(style) - }; - - let (handle_width, handle_height, handle_border_radius) = - match style.handle.shape { - HandleShape::Circle { radius } => { - (radius * 2.0, radius * 2.0, radius.into()) - } - HandleShape::Rectangle { - width, - border_radius, - } => (f32::from(width), bounds.height, border_radius), + let style = (self.style)( + theme, + if state.is_dragging { + Status::Dragging + } else if is_mouse_over { + Status::Hovered + } else { + Status::Active + }, + ); + + let (handle_width, handle_height, handle_border_radius) = + match style.handle.shape { + HandleShape::Circle { radius } => { + (radius * 2.0, radius * 2.0, radius.into()) + } + HandleShape::Rectangle { + width, + border_radius, + } => (f32::from(width), bounds.height, border_radius), + }; + + let value = self.value.into() as f32; + let (range_start, range_end) = { + let (start, end) = self.range.clone().into_inner(); + + (start.into() as f32, end.into() as f32) }; - let value = value.into() as f32; - let (range_start, range_end) = { - let (start, end) = range.clone().into_inner(); - - (start.into() as f32, end.into() as f32) - }; - - let offset = if range_start >= range_end { - 0.0 - } else { - (bounds.width - handle_width) * (value - range_start) - / (range_end - range_start) - }; - - let rail_y = bounds.y + bounds.height / 2.0; - - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: bounds.x, - y: rail_y - style.rail.width / 2.0, - width: offset + handle_width / 2.0, - height: style.rail.width, - }, - border: Border::with_radius(style.rail.border_radius), - ..renderer::Quad::default() - }, - style.rail.colors.0, - ); - - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: bounds.x + offset + handle_width / 2.0, - y: rail_y - style.rail.width / 2.0, - width: bounds.width - offset - handle_width / 2.0, - height: style.rail.width, + let offset = if range_start >= range_end { + 0.0 + } else { + (bounds.width - handle_width) * (value - range_start) + / (range_end - range_start) + }; + + let rail_y = bounds.y + bounds.height / 2.0; + + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: bounds.x, + y: rail_y - style.rail.width / 2.0, + width: offset + handle_width / 2.0, + height: style.rail.width, + }, + border: Border::with_radius(style.rail.border_radius), + ..renderer::Quad::default() }, - border: Border::with_radius(style.rail.border_radius), - ..renderer::Quad::default() - }, - style.rail.colors.1, - ); - - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: bounds.x + offset, - y: rail_y - handle_height / 2.0, - width: handle_width, - height: handle_height, + style.rail.colors.0, + ); + + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: bounds.x + offset + handle_width / 2.0, + y: rail_y - style.rail.width / 2.0, + width: bounds.width - offset - handle_width / 2.0, + height: style.rail.width, + }, + border: Border::with_radius(style.rail.border_radius), + ..renderer::Quad::default() }, - border: Border { - radius: handle_border_radius, - width: style.handle.border_width, - color: style.handle.border_color, + style.rail.colors.1, + ); + + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: bounds.x + offset, + y: rail_y - handle_height / 2.0, + width: handle_width, + height: handle_height, + }, + border: Border { + radius: handle_border_radius, + width: style.handle.border_width, + color: style.handle.border_color, + }, + ..renderer::Quad::default() }, - ..renderer::Quad::default() - }, - style.handle.color, - ); + style.handle.color, + ); + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + _viewport: &Rectangle, + _renderer: &Renderer, + ) -> mouse::Interaction { + let state = tree.state.downcast_ref::<State>(); + let bounds = layout.bounds(); + let is_mouse_over = cursor.is_over(bounds); + + if state.is_dragging { + mouse::Interaction::Grabbing + } else if is_mouse_over { + mouse::Interaction::Grab + } else { + mouse::Interaction::default() + } + } } -/// Computes the current [`mouse::Interaction`] of a [`Slider`]. -pub fn mouse_interaction( - layout: Layout<'_>, - cursor: mouse::Cursor, - state: &State, -) -> mouse::Interaction { - let bounds = layout.bounds(); - let is_mouse_over = cursor.is_over(bounds); - - if state.is_dragging { - mouse::Interaction::Grabbing - } else if is_mouse_over { - mouse::Interaction::Grab - } else { - mouse::Interaction::default() +impl<'a, T, Message, Theme, Renderer> From<Slider<'a, T, Message, Theme>> + for Element<'a, Message, Theme, Renderer> +where + T: Copy + Into<f64> + num_traits::FromPrimitive + 'a, + Message: Clone + 'a, + Theme: StyleSheet + 'a, + Renderer: crate::core::Renderer + 'a, +{ + fn from( + slider: Slider<'a, T, Message, Theme>, + ) -> Element<'a, Message, Theme, Renderer> { + Element::new(slider) } } -/// The local state of a [`Slider`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct State { +struct State { is_dragging: bool, keyboard_modifiers: keyboard::Modifiers, } - -impl State { - /// Creates a new [`State`]. - pub fn new() -> State { - State::default() - } -} |