summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-03-04 03:57:03 +0100
committerLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-03-04 03:57:03 +0100
commitce309db37b8eb9860ae1f1be1710fb39e7a9edea (patch)
treea28ef6659f6235ec7bc2a7403cfef467a71fb2bf
parent1bb5a1b9a23e1c4739430ac87ca33b06c2f4d9df (diff)
downloadiced-ce309db37b8eb9860ae1f1be1710fb39e7a9edea.tar.gz
iced-ce309db37b8eb9860ae1f1be1710fb39e7a9edea.tar.bz2
iced-ce309db37b8eb9860ae1f1be1710fb39e7a9edea.zip
Try new approach to theming for `Slider`
-rw-r--r--style/src/lib.rs2
-rw-r--r--style/src/slider.rs29
-rw-r--r--style/src/theme.rs108
-rw-r--r--widget/src/slider.rs548
-rw-r--r--widget/src/vertical_slider.rs558
5 files changed, 532 insertions, 713 deletions
diff --git a/style/src/lib.rs b/style/src/lib.rs
index 3c2865eb..17ba09c4 100644
--- a/style/src/lib.rs
+++ b/style/src/lib.rs
@@ -10,7 +10,7 @@
#![forbid(unsafe_code, rust_2018_idioms)]
#![deny(
unused_results,
- missing_docs,
+ // missing_docs,
unused_results,
rustdoc::broken_intra_doc_links
)]
diff --git a/style/src/slider.rs b/style/src/slider.rs
index bf1c7329..0c19e47d 100644
--- a/style/src/slider.rs
+++ b/style/src/slider.rs
@@ -1,6 +1,6 @@
//! Change the apperance of a slider.
use crate::core::border;
-use crate::core::Color;
+use crate::core::{Color, Pixels};
/// The appearance of a slider.
#[derive(Debug, Clone, Copy)]
@@ -11,6 +11,17 @@ pub struct Appearance {
pub handle: Handle,
}
+impl Appearance {
+ /// Changes the [`HandleShape`] of the [`Appearance`] to a circle
+ /// with the given radius.
+ pub fn with_circular_handle(mut self, radius: impl Into<Pixels>) -> Self {
+ self.handle.shape = HandleShape::Circle {
+ radius: radius.into().0,
+ };
+ self
+ }
+}
+
/// The appearance of a slider rail
#[derive(Debug, Clone, Copy)]
pub struct Rail {
@@ -54,15 +65,11 @@ pub enum HandleShape {
/// A set of rules that dictate the style of a slider.
pub trait StyleSheet {
- /// The supported style of the [`StyleSheet`].
- type Style: Default;
-
- /// Produces the style of an active slider.
- fn active(&self, style: &Self::Style) -> Appearance;
-
- /// Produces the style of an hovered slider.
- fn hovered(&self, style: &Self::Style) -> Appearance;
+ fn default() -> fn(&Self, Status) -> Appearance;
+}
- /// Produces the style of a slider that is being dragged.
- fn dragging(&self, style: &Self::Style) -> Appearance;
+pub enum Status {
+ Active,
+ Hovered,
+ Dragging,
}
diff --git a/style/src/theme.rs b/style/src/theme.rs
index 0b56e101..656d6bf9 100644
--- a/style/src/theme.rs
+++ b/style/src/theme.rs
@@ -608,88 +608,40 @@ impl<T: Fn(&Theme) -> container::Appearance> container::StyleSheet for T {
}
}
-/// The style of a slider.
-#[derive(Default)]
-pub enum Slider {
- /// The default style.
- #[default]
- Default,
- /// A custom style.
- Custom(Box<dyn slider::StyleSheet<Style = Theme>>),
-}
-
impl slider::StyleSheet for Theme {
- type Style = Slider;
-
- fn active(&self, style: &Self::Style) -> slider::Appearance {
- match style {
- Slider::Default => {
- let palette = self.extended_palette();
-
- let handle = slider::Handle {
- shape: slider::HandleShape::Rectangle {
- width: 8,
- border_radius: 4.0.into(),
- },
- color: Color::WHITE,
- border_color: Color::WHITE,
- border_width: 1.0,
- };
-
- slider::Appearance {
- rail: slider::Rail {
- colors: (
- palette.primary.base.color,
- palette.secondary.base.color,
- ),
- width: 4.0,
- border_radius: 2.0.into(),
- },
- handle: slider::Handle {
- color: palette.background.base.color,
- border_color: palette.primary.base.color,
- ..handle
- },
- }
- }
- Slider::Custom(custom) => custom.active(self),
- }
- }
-
- fn hovered(&self, style: &Self::Style) -> slider::Appearance {
- match style {
- Slider::Default => {
- let active = self.active(style);
- let palette = self.extended_palette();
-
- slider::Appearance {
- handle: slider::Handle {
- color: palette.primary.weak.color,
- ..active.handle
- },
- ..active
- }
- }
- Slider::Custom(custom) => custom.hovered(self),
- }
+ fn default() -> fn(&Self, slider::Status) -> slider::Appearance {
+ slider
}
+}
- fn dragging(&self, style: &Self::Style) -> slider::Appearance {
- match style {
- Slider::Default => {
- let active = self.active(style);
- let palette = self.extended_palette();
+pub fn slider(theme: &Theme, status: slider::Status) -> slider::Appearance {
+ let palette = theme.extended_palette();
- slider::Appearance {
- handle: slider::Handle {
- color: palette.primary.base.color,
- ..active.handle
- },
- ..active
- }
- }
- Slider::Custom(custom) => custom.dragging(self),
- }
+ let handle = slider::Handle {
+ shape: slider::HandleShape::Rectangle {
+ width: 8,
+ border_radius: 4.0.into(),
+ },
+ color: Color::WHITE,
+ border_color: Color::WHITE,
+ border_width: 1.0,
+ };
+
+ slider::Appearance {
+ rail: slider::Rail {
+ colors: (palette.primary.base.color, palette.secondary.base.color),
+ width: 4.0,
+ border_radius: 2.0.into(),
+ },
+ handle: slider::Handle {
+ color: match status {
+ slider::Status::Active => palette.background.base.color,
+ slider::Status::Hovered => palette.primary.weak.color,
+ slider::Status::Dragging => palette.primary.base.color,
+ },
+ border_color: palette.primary.base.color,
+ ..handle
+ },
}
}
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()
- }
-}
diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs
index 8f7c88da..b6903001 100644
--- a/widget/src/vertical_slider.rs
+++ b/widget/src/vertical_slider.rs
@@ -3,7 +3,9 @@
//! A [`VerticalSlider`] has some local [`State`].
use std::ops::RangeInclusive;
-pub use crate::style::slider::{Appearance, Handle, HandleShape, StyleSheet};
+pub use crate::style::slider::{
+ Appearance, Handle, HandleShape, Status, StyleSheet,
+};
use crate::core;
use crate::core::event::{self, Event};
@@ -55,7 +57,7 @@ where
on_release: Option<Message>,
width: f32,
height: Length,
- style: Theme::Style,
+ style: fn(&Theme, Status) -> Appearance,
}
impl<'a, T, Message, Theme> VerticalSlider<'a, T, Message, Theme>
@@ -101,7 +103,7 @@ where
on_release: None,
width: Self::DEFAULT_WIDTH,
height: Length::Fill,
- style: Default::default(),
+ style: Theme::default(),
}
}
@@ -137,7 +139,10 @@ where
}
/// Sets the style of the [`VerticalSlider`].
- pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
+ pub fn style(
+ mut self,
+ style: impl Into<fn(&Theme, Status) -> Appearance>,
+ ) -> Self {
self.style = style.into();
self
}
@@ -170,7 +175,7 @@ where
}
fn state(&self) -> tree::State {
- tree::State::new(State::new())
+ tree::State::new(State::default())
}
fn size(&self) -> Size<Length> {
@@ -200,360 +205,287 @@ 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>();
+ let is_dragging = state.is_dragging;
+ let current_value = self.value;
- 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 locate = |cursor_position: Point| -> Option<T> {
+ let bounds = layout.bounds();
- 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 new_value = if cursor_position.y >= bounds.y + bounds.height {
+ Some(*self.range.start())
+ } else if cursor_position.y <= bounds.y {
+ 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<VerticalSlider<'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: core::Renderer + 'a,
-{
- fn from(
- slider: VerticalSlider<'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 [`VerticalSlider`]
-/// 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 = 1.0
+ - f64::from(cursor_position.y - bounds.y)
+ / f64::from(bounds.height);
- let locate = |cursor_position: Point| -> Option<T> {
- let bounds = layout.bounds();
+ let steps = (percent * (end - start) / step).round();
+ let value = steps * step + start;
- let new_value = if cursor_position.y >= bounds.y + bounds.height {
- Some(*range.start())
- } else if cursor_position.y <= bounds.y {
- Some(*range.end())
- } else {
+ 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 = 1.0
- - f64::from(cursor_position.y - bounds.y)
- / f64::from(bounds.height);
-
- 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());
- }
-
- T::from_f64(new_value)
- };
+ let decrement = |value: T| -> Option<T> {
+ let step = if state.keyboard_modifiers.shift() {
+ self.shift_step.unwrap_or(self.step)
+ } else {
+ self.step
+ }
+ .into();
- let decrement = |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);
- let steps = (value.into() / step).round();
- let new_value = step * (steps - 1.0);
+ if new_value < (*self.range.start()).into() {
+ return Some(*self.range.start());
+ }
- if new_value < (*range.start()).into() {
- return Some(*range.start());
- }
+ T::from_f64(new_value)
+ };
- T::from_f64(new_value)
- };
+ let change = |new_value: T| {
+ if (self.value.into() - new_value.into()).abs() > f64::EPSILON {
+ shell.publish((self.on_change)(new_value));
- let change = |new_value: T| {
- if ((*value).into() - new_value.into()).abs() > f64::EPSILON {
- shell.publish((on_change)(new_value));
+ self.value = new_value;
+ }
+ };
- *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.control()
- || state.keyboard_modifiers.command()
+ 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())
{
- let _ = default.map(change);
- state.is_dragging = false;
- } else {
- let _ = locate(cursor_position).map(change);
- state.is_dragging = true;
- }
+ if state.keyboard_modifiers.control()
+ || state.keyboard_modifiers.command()
+ {
+ let _ = self.default.map(change);
+ state.is_dragging = false;
+ } else {
+ let _ = locate(cursor_position).map(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);
+ return event::Status::Captured;
}
- 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);
- }
- Key::Named(key::Named::ArrowDown) => {
- let _ = decrement(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);
+ }
+ _ => (),
}
- _ => (),
- }
- 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 [`VerticalSlider`].
-pub fn draw<T, Theme, Renderer>(
- renderer: &mut Renderer,
- layout: Layout<'_>,
- cursor: mouse::Cursor,
- state: &State,
- value: T,
- range: &RangeInclusive<T>,
- style_sheet: &Theme,
- style: &Theme::Style,
-) where
- T: Into<f64> + Copy,
- Theme: StyleSheet,
- Renderer: core::Renderer,
-{
- let bounds = layout.bounds();
- let is_mouse_over = cursor.is_over(bounds);
-
- let style = if state.is_dragging {
- style_sheet.dragging(style)
- } else if is_mouse_over {
- style_sheet.hovered(style)
- } else {
- style_sheet.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.width, 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.width, 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.height - handle_width) * (value - range_end)
- / (range_start - range_end)
- };
-
- let rail_x = bounds.x + bounds.width / 2.0;
-
- renderer.fill_quad(
- renderer::Quad {
- bounds: Rectangle {
- x: rail_x - style.rail.width / 2.0,
- y: bounds.y,
- width: style.rail.width,
- height: offset + handle_width / 2.0,
- },
- border: Border::with_radius(style.rail.border_radius),
- ..renderer::Quad::default()
- },
- style.rail.colors.1,
- );
-
- renderer.fill_quad(
- renderer::Quad {
- bounds: Rectangle {
- x: rail_x - style.rail.width / 2.0,
- y: bounds.y + offset + handle_width / 2.0,
- width: style.rail.width,
- height: bounds.height - offset - handle_width / 2.0,
+ let offset = if range_start >= range_end {
+ 0.0
+ } else {
+ (bounds.height - handle_width) * (value - range_end)
+ / (range_start - range_end)
+ };
+
+ let rail_x = bounds.x + bounds.width / 2.0;
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: rail_x - style.rail.width / 2.0,
+ y: bounds.y,
+ width: style.rail.width,
+ height: offset + handle_width / 2.0,
+ },
+ 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.0,
- );
-
- renderer.fill_quad(
- renderer::Quad {
- bounds: Rectangle {
- x: rail_x - handle_height / 2.0,
- y: bounds.y + offset,
- width: handle_height,
- height: handle_width,
+ style.rail.colors.1,
+ );
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: rail_x - style.rail.width / 2.0,
+ y: bounds.y + offset + handle_width / 2.0,
+ width: style.rail.width,
+ height: bounds.height - offset - handle_width / 2.0,
+ },
+ 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.0,
+ );
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: rail_x - handle_height / 2.0,
+ y: bounds.y + offset,
+ width: handle_height,
+ height: handle_width,
+ },
+ 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 [`VerticalSlider`].
-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<VerticalSlider<'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: core::Renderer + 'a,
+{
+ fn from(
+ slider: VerticalSlider<'a, T, Message, Theme>,
+ ) -> Element<'a, Message, Theme, Renderer> {
+ Element::new(slider)
}
}
-/// The local state of a [`VerticalSlider`].
#[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()
- }
-}