summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--examples/slider/src/main.rs20
-rw-r--r--native/src/widget.rs3
-rw-r--r--native/src/widget/helpers.rs17
-rw-r--r--native/src/widget/slider.rs199
-rw-r--r--native/src/widget/vertical_slider.rs470
-rw-r--r--src/widget.rs2
6 files changed, 540 insertions, 171 deletions
diff --git a/examples/slider/src/main.rs b/examples/slider/src/main.rs
index 44770382..6286d625 100644
--- a/examples/slider/src/main.rs
+++ b/examples/slider/src/main.rs
@@ -1,4 +1,4 @@
-use iced::widget::{column, container, slider, text};
+use iced::widget::{column, container, slider, text, vertical_slider};
use iced::{Element, Length, Sandbox, Settings};
pub fn main() -> iced::Result {
@@ -34,21 +34,15 @@ impl Sandbox for Slider {
}
fn view(&self) -> Element<Message> {
- use slider::Orientation::{Horizontal, Vertical};
-
let value = self.slider_value;
- let h_slider = container(
- slider(0..=100, value, Message::SliderChanged)
- .orientation(Horizontal),
- )
- .width(Length::Units(250));
+ let h_slider =
+ container(slider(0..=100, value, Message::SliderChanged))
+ .width(Length::Units(250));
- let v_slider = container(
- slider(0..=100, value, Message::SliderChanged)
- .orientation(Vertical),
- )
- .height(Length::Units(200));
+ let v_slider =
+ container(vertical_slider(0..=100, value, Message::SliderChanged))
+ .height(Length::Units(200));
let text = text(format!("{value}"));
diff --git a/native/src/widget.rs b/native/src/widget.rs
index a4b46ed4..efe26fc7 100644
--- a/native/src/widget.rs
+++ b/native/src/widget.rs
@@ -33,6 +33,7 @@ pub mod text_input;
pub mod toggler;
pub mod tooltip;
pub mod tree;
+pub mod vertical_slider;
mod action;
mod id;
@@ -79,6 +80,8 @@ pub use toggler::Toggler;
pub use tooltip::Tooltip;
#[doc(no_inline)]
pub use tree::Tree;
+#[doc(no_inline)]
+pub use vertical_slider::VerticalSlider;
pub use action::Action;
pub use id::Id;
diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs
index 0bde288f..8cc1ae82 100644
--- a/native/src/widget/helpers.rs
+++ b/native/src/widget/helpers.rs
@@ -198,6 +198,23 @@ where
widget::Slider::new(range, value, on_change)
}
+/// Creates a new [`VerticalSlider`].
+///
+/// [`VerticalSlider`]: widget::VerticalSlider
+pub fn vertical_slider<'a, T, Message, Renderer>(
+ range: std::ops::RangeInclusive<T>,
+ value: T,
+ on_change: impl Fn(T) -> Message + 'a,
+) -> widget::VerticalSlider<'a, T, Message, Renderer>
+where
+ T: Copy + From<u8> + std::cmp::PartialOrd,
+ Message: Clone,
+ Renderer: crate::Renderer,
+ Renderer::Theme: widget::slider::StyleSheet,
+{
+ widget::VerticalSlider::new(range, value, on_change)
+}
+
/// Creates a new [`PickList`].
///
/// [`PickList`]: widget::PickList
diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs
index bf3383d9..87030a4d 100644
--- a/native/src/widget/slider.rs
+++ b/native/src/widget/slider.rs
@@ -16,9 +16,10 @@ use std::ops::RangeInclusive;
pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet};
-/// A bar and a handle that selects a single value from a range of values.
+/// An horizontal bar and a handle that selects a single value from a range of
+/// values.
///
-/// A [`Slider`] will try to fill the space of its container, based on its orientation.
+/// 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.
@@ -52,9 +53,8 @@ where
value: T,
on_change: Box<dyn Fn(T) -> Message + 'a>,
on_release: Option<Message>,
- width: Option<Length>,
- height: Option<Length>,
- orientation: Orientation,
+ width: Length,
+ height: u16,
style: <Renderer::Theme as StyleSheet>::Style,
}
@@ -65,6 +65,9 @@ where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
+ /// The default height of a [`Slider`].
+ pub const DEFAULT_HEIGHT: u16 = 22;
+
/// Creates a new [`Slider`].
///
/// It expects:
@@ -95,9 +98,8 @@ where
step: T::from(1),
on_change: Box::new(on_change),
on_release: None,
- width: None,
- height: None,
- orientation: Default::default(),
+ width: Length::Fill,
+ height: Self::DEFAULT_HEIGHT,
style: Default::default(),
}
}
@@ -115,13 +117,13 @@ where
/// Sets the width of the [`Slider`].
pub fn width(mut self, width: Length) -> Self {
- self.width = Some(width);
+ self.width = width;
self
}
/// Sets the height of the [`Slider`].
- pub fn height(mut self, height: Length) -> Self {
- self.height = Some(height);
+ pub fn height(mut self, height: u16) -> Self {
+ self.height = height;
self
}
@@ -139,12 +141,6 @@ where
self.step = step;
self
}
-
- /// Sets the orientation of the [`Slider`].
- pub fn orientation(mut self, orientation: Orientation) -> Self {
- self.orientation = orientation;
- self
- }
}
impl<'a, T, Message, Renderer> Widget<Message, Renderer>
@@ -164,17 +160,11 @@ where
}
fn width(&self) -> Length {
- match self.orientation {
- Orientation::Horizontal => self.width.unwrap_or(Length::Fill),
- Orientation::Vertical => Length::Shrink,
- }
+ self.width
}
fn height(&self) -> Length {
- match self.orientation {
- Orientation::Horizontal => Length::Shrink,
- Orientation::Vertical => self.height.unwrap_or(Length::Fill),
- }
+ Length::Shrink
}
fn layout(
@@ -182,14 +172,9 @@ where
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- let width = self
- .width
- .unwrap_or_else(|| self.orientation.default_width());
- let height = self
- .height
- .unwrap_or_else(|| self.orientation.default_height());
-
- let limits = limits.width(width).height(height);
+ let limits =
+ limits.width(self.width).height(Length::Units(self.height));
+
let size = limits.resolve(Size::ZERO);
layout::Node::new(size)
@@ -216,7 +201,6 @@ where
self.step,
self.on_change.as_ref(),
&self.on_release,
- self.orientation,
)
}
@@ -239,7 +223,6 @@ where
&self.range,
theme,
&self.style,
- self.orientation,
)
}
@@ -287,7 +270,6 @@ pub fn update<Message, T>(
step: T,
on_change: &dyn Fn(T) -> Message,
on_release: &Option<Message>,
- orientation: Orientation,
) -> event::Status
where
T: Copy + Into<f64> + num_traits::FromPrimitive,
@@ -297,40 +279,17 @@ where
let mut change = || {
let bounds = layout.bounds();
-
- let cursor_below_bounds = match orientation {
- Orientation::Horizontal => cursor_position.x <= bounds.x,
- Orientation::Vertical => {
- cursor_position.y >= bounds.y + bounds.height
- }
- };
-
- let cursor_above_bounds = match orientation {
- Orientation::Horizontal => {
- cursor_position.x >= bounds.x + bounds.width
- }
- Orientation::Vertical => cursor_position.y <= bounds.y,
- };
-
- let new_value = if cursor_below_bounds {
+ let new_value = if cursor_position.x <= bounds.x {
*range.start()
- } else if cursor_above_bounds {
+ } 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 = match orientation {
- Orientation::Horizontal => {
- f64::from(cursor_position.x - bounds.x)
- / f64::from(bounds.width)
- }
- Orientation::Vertical => {
- 1.00 - (f64::from(cursor_position.y - bounds.y)
- / f64::from(bounds.height))
- }
- };
+ 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;
@@ -395,7 +354,6 @@ pub fn draw<T, R>(
range: &RangeInclusive<T>,
style_sheet: &dyn StyleSheet<Style = <R::Theme as StyleSheet>::Style>,
style: &<R::Theme as StyleSheet>::Style,
- orientation: Orientation,
) where
T: Into<f64> + Copy,
R: crate::Renderer,
@@ -412,26 +370,15 @@ pub fn draw<T, R>(
style_sheet.active(style)
};
- let rail = match orientation {
- Orientation::Horizontal => bounds.y + (bounds.height / 2.0).round(),
- Orientation::Vertical => bounds.x + (bounds.width / 2.0).round(),
- };
+ let rail_y = bounds.y + (bounds.height / 2.0).round();
renderer.fill_quad(
renderer::Quad {
- bounds: match orientation {
- Orientation::Horizontal => Rectangle {
- x: bounds.x,
- y: rail - 1.0,
- width: bounds.width,
- height: 2.0,
- },
- Orientation::Vertical => Rectangle {
- x: rail - 1.0,
- y: bounds.y,
- width: 2.0,
- height: bounds.height,
- },
+ bounds: Rectangle {
+ x: bounds.x,
+ y: rail_y - 1.0,
+ width: bounds.width,
+ height: 2.0,
},
border_radius: 0.0.into(),
border_width: 0.0,
@@ -442,19 +389,11 @@ pub fn draw<T, R>(
renderer.fill_quad(
renderer::Quad {
- bounds: match orientation {
- Orientation::Horizontal => Rectangle {
- x: bounds.x,
- y: rail + 1.0,
- width: bounds.width,
- height: 2.0,
- },
- Orientation::Vertical => Rectangle {
- x: rail + 1.0,
- y: bounds.y,
- width: 2.0,
- height: bounds.height,
- },
+ bounds: Rectangle {
+ x: bounds.x,
+ y: rail_y + 1.0,
+ width: bounds.width,
+ height: 2.0,
},
border_radius: 0.0.into(),
border_width: 0.0,
@@ -471,14 +410,7 @@ pub fn draw<T, R>(
HandleShape::Rectangle {
width,
border_radius,
- } => {
- let handle_height = match orientation {
- Orientation::Horizontal => bounds.height,
- Orientation::Vertical => bounds.width,
- };
-
- (f32::from(width), handle_height, border_radius)
- }
+ } => (f32::from(width), bounds.height, border_radius),
};
let value = value.into() as f32;
@@ -491,33 +423,17 @@ pub fn draw<T, R>(
let handle_offset = if range_start >= range_end {
0.0
} else {
- match orientation {
- Orientation::Horizontal => {
- bounds.width * (value - range_start) / (range_end - range_start)
- - handle_width / 2.0
- }
- Orientation::Vertical => {
- bounds.height * (value - range_end) / (range_start - range_end)
- - handle_width / 2.0
- }
- }
+ bounds.width * (value - range_start) / (range_end - range_start)
+ - handle_width / 2.0
};
renderer.fill_quad(
renderer::Quad {
- bounds: match orientation {
- Orientation::Horizontal => Rectangle {
- x: bounds.x + handle_offset.round(),
- y: rail - handle_height / 2.0,
- width: handle_width,
- height: handle_height,
- },
- Orientation::Vertical => Rectangle {
- x: rail - (handle_height / 2.0),
- y: bounds.y + handle_offset.round(),
- width: handle_height,
- height: handle_width,
- },
+ bounds: Rectangle {
+ x: bounds.x + handle_offset.round(),
+ y: rail_y - handle_height / 2.0,
+ width: handle_width,
+ height: handle_height,
},
border_radius: handle_border_radius.into(),
border_width: style.handle.border_width,
@@ -557,36 +473,3 @@ impl State {
State::default()
}
}
-
-/// The orientation of a [`Slider`].
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
-pub enum Orientation {
- #[default]
- /// Default orientation.
- /// Will fill the horizontal space of its container.
- Horizontal,
- /// Vertical orientation.
- /// Will fill the vertical space of its container.
- Vertical,
-}
-
-impl Orientation {
- /// The default height of a [`Slider`] in horizontal orientation.
- pub const DEFAULT_HEIGHT: Length = Length::Units(22);
- /// The default width of a [`Slider`] in vertical orientation.
- pub const DEFAULT_WIDTH: Length = Length::Units(22);
-
- fn default_height(&self) -> Length {
- match self {
- Orientation::Horizontal => Self::DEFAULT_HEIGHT,
- Orientation::Vertical => Length::Fill,
- }
- }
-
- fn default_width(&self) -> Length {
- match self {
- Orientation::Horizontal => Length::Fill,
- Orientation::Vertical => Self::DEFAULT_WIDTH,
- }
- }
-}
diff --git a/native/src/widget/vertical_slider.rs b/native/src/widget/vertical_slider.rs
new file mode 100644
index 00000000..28e8405c
--- /dev/null
+++ b/native/src/widget/vertical_slider.rs
@@ -0,0 +1,470 @@
+//! Display an interactive selector of a single value from a range of values.
+//!
+//! A [`VerticalSlider`] has some local [`State`].
+use std::ops::RangeInclusive;
+
+pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet};
+
+use crate::event::{self, Event};
+use crate::widget::tree::{self, Tree};
+use crate::{
+ layout, mouse, renderer, touch, Background, Clipboard, Color, Element,
+ Layout, Length, Point, Rectangle, Shell, Size, Widget,
+};
+
+/// An vertical bar and a handle that selects a single value from a range of
+/// values.
+///
+/// A [`VerticalSlider`] will try to fill the vertical space of its container.
+///
+/// The [`VerticalSlider`] range of numeric values is generic and its step size defaults
+/// to 1 unit.
+///
+/// # Example
+/// ```
+/// # use iced_native::widget::vertical_slider;
+/// # use iced_native::renderer::Null;
+/// #
+/// # type VerticalSlider<'a, T, Message> = vertical_slider::VerticalSlider<'a, T, Message, Null>;
+/// #
+/// #[derive(Clone)]
+/// pub enum Message {
+/// SliderChanged(f32),
+/// }
+///
+/// let value = 50.0;
+///
+/// VerticalSlider::new(0.0..=100.0, value, Message::SliderChanged);
+/// ```
+#[allow(missing_debug_implementations)]
+pub struct VerticalSlider<'a, T, Message, Renderer>
+where
+ Renderer: crate::Renderer,
+ Renderer::Theme: StyleSheet,
+{
+ range: RangeInclusive<T>,
+ step: T,
+ value: T,
+ on_change: Box<dyn Fn(T) -> Message + 'a>,
+ on_release: Option<Message>,
+ width: u16,
+ height: Length,
+ style: <Renderer::Theme as StyleSheet>::Style,
+}
+
+impl<'a, T, Message, Renderer> VerticalSlider<'a, T, Message, Renderer>
+where
+ T: Copy + From<u8> + std::cmp::PartialOrd,
+ Message: Clone,
+ Renderer: crate::Renderer,
+ Renderer::Theme: StyleSheet,
+{
+ /// The default width of a [`VerticalSlider`].
+ pub const DEFAULT_WIDTH: u16 = 22;
+
+ /// Creates a new [`VerticalSlider`].
+ ///
+ /// It expects:
+ /// * an inclusive range of possible values
+ /// * the current value of the [`VerticalSlider`]
+ /// * a function that will be called when the [`VerticalSlider`] is dragged.
+ /// It receives the new value of the [`VerticalSlider`] and must produce a
+ /// `Message`.
+ pub fn new<F>(range: RangeInclusive<T>, value: T, on_change: F) -> Self
+ where
+ F: 'a + Fn(T) -> Message,
+ {
+ let value = if value >= *range.start() {
+ value
+ } else {
+ *range.start()
+ };
+
+ let value = if value <= *range.end() {
+ value
+ } else {
+ *range.end()
+ };
+
+ VerticalSlider {
+ value,
+ range,
+ step: T::from(1),
+ on_change: Box::new(on_change),
+ on_release: None,
+ width: Self::DEFAULT_WIDTH,
+ height: Length::Fill,
+ style: Default::default(),
+ }
+ }
+
+ /// Sets the release message of the [`VerticalSlider`].
+ /// 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 [`VerticalSlider`].
+ pub fn width(mut self, width: u16) -> Self {
+ self.width = width;
+ self
+ }
+
+ /// Sets the height of the [`VerticalSlider`].
+ pub fn height(mut self, height: Length) -> Self {
+ self.height = height;
+ self
+ }
+
+ /// Sets the style of the [`VerticalSlider`].
+ pub fn style(
+ mut self,
+ style: impl Into<<Renderer::Theme as StyleSheet>::Style>,
+ ) -> Self {
+ self.style = style.into();
+ self
+ }
+
+ /// Sets the step size of the [`VerticalSlider`].
+ pub fn step(mut self, step: T) -> Self {
+ self.step = step;
+ self
+ }
+}
+
+impl<'a, T, Message, Renderer> Widget<Message, Renderer>
+ for VerticalSlider<'a, T, Message, Renderer>
+where
+ T: Copy + Into<f64> + num_traits::FromPrimitive,
+ Message: Clone,
+ Renderer: crate::Renderer,
+ Renderer::Theme: StyleSheet,
+{
+ fn tag(&self) -> tree::Tag {
+ tree::Tag::of::<State>()
+ }
+
+ fn state(&self) -> tree::State {
+ tree::State::new(State::new())
+ }
+
+ fn width(&self) -> Length {
+ Length::Shrink
+ }
+
+ fn height(&self) -> Length {
+ self.height
+ }
+
+ fn layout(
+ &self,
+ _renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ let limits =
+ limits.width(Length::Units(self.width)).height(self.height);
+
+ let size = limits.resolve(Size::ZERO);
+
+ layout::Node::new(size)
+ }
+
+ fn on_event(
+ &mut self,
+ tree: &mut Tree,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _renderer: &Renderer,
+ _clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ update(
+ event,
+ layout,
+ cursor_position,
+ shell,
+ tree.state.downcast_mut::<State>(),
+ &mut self.value,
+ &self.range,
+ self.step,
+ self.on_change.as_ref(),
+ &self.on_release,
+ )
+ }
+
+ fn draw(
+ &self,
+ tree: &Tree,
+ renderer: &mut Renderer,
+ theme: &Renderer::Theme,
+ _style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) {
+ draw(
+ renderer,
+ layout,
+ cursor_position,
+ tree.state.downcast_ref::<State>(),
+ self.value,
+ &self.range,
+ theme,
+ &self.style,
+ )
+ }
+
+ fn mouse_interaction(
+ &self,
+ tree: &Tree,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ _renderer: &Renderer,
+ ) -> mouse::Interaction {
+ mouse_interaction(
+ layout,
+ cursor_position,
+ tree.state.downcast_ref::<State>(),
+ )
+ }
+}
+
+impl<'a, T, Message, Renderer> From<VerticalSlider<'a, T, Message, Renderer>>
+ for Element<'a, Message, Renderer>
+where
+ T: 'a + Copy + Into<f64> + num_traits::FromPrimitive,
+ Message: 'a + Clone,
+ Renderer: 'a + crate::Renderer,
+ Renderer::Theme: StyleSheet,
+{
+ fn from(
+ slider: VerticalSlider<'a, T, Message, Renderer>,
+ ) -> Element<'a, Message, Renderer> {
+ Element::new(slider)
+ }
+}
+
+/// Processes an [`Event`] and updates the [`State`] of a [`VerticalSlider`]
+/// accordingly.
+pub fn update<Message, T>(
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ shell: &mut Shell<'_, Message>,
+ state: &mut State,
+ value: &mut T,
+ range: &RangeInclusive<T>,
+ step: 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 mut change = || {
+ let bounds = layout.bounds();
+ let new_value = if cursor_position.y >= bounds.y + bounds.height {
+ *range.start()
+ } else if cursor_position.y <= bounds.y {
+ *range.end()
+ } else {
+ let step = step.into();
+ let start = (*range.start()).into();
+ let end = (*range.end()).into();
+
+ 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 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 [`VerticalSlider`].
+pub fn draw<T, R>(
+ renderer: &mut R,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ state: &State,
+ value: T,
+ range: &RangeInclusive<T>,
+ style_sheet: &dyn StyleSheet<Style = <R::Theme as StyleSheet>::Style>,
+ style: &<R::Theme as StyleSheet>::Style,
+) where
+ T: Into<f64> + Copy,
+ R: crate::Renderer,
+ R::Theme: StyleSheet,
+{
+ let bounds = layout.bounds();
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ 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 rail_x = bounds.x + (bounds.width / 2.0).round();
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: rail_x - 1.0,
+ y: bounds.y,
+ width: 2.0,
+ height: bounds.height,
+ },
+ border_radius: 0.0.into(),
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ style.rail_colors.0,
+ );
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: rail_x + 1.0,
+ y: bounds.y,
+ width: 2.0,
+ height: bounds.height,
+ },
+ border_radius: 0.0.into(),
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ Background::Color(style.rail_colors.1),
+ );
+
+ let (handle_width, handle_height, handle_border_radius) = match style
+ .handle
+ .shape
+ {
+ HandleShape::Circle { radius } => (radius * 2.0, radius * 2.0, radius),
+ HandleShape::Rectangle {
+ width,
+ border_radius,
+ } => (f32::from(width), bounds.width, border_radius),
+ };
+
+ 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 handle_offset = if range_start >= range_end {
+ 0.0
+ } else {
+ bounds.height * (value - range_end) / (range_start - range_end)
+ - handle_width / 2.0
+ };
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: rail_x - (handle_height / 2.0),
+ y: bounds.y + handle_offset.round(),
+ width: handle_height,
+ height: handle_width,
+ },
+ border_radius: handle_border_radius.into(),
+ border_width: style.handle.border_width,
+ border_color: style.handle.border_color,
+ },
+ style.handle.color,
+ );
+}
+
+/// Computes the current [`mouse::Interaction`] of a [`VerticalSlider`].
+pub fn mouse_interaction(
+ layout: Layout<'_>,
+ cursor_position: Point,
+ state: &State,
+) -> mouse::Interaction {
+ let bounds = layout.bounds();
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ if state.is_dragging {
+ mouse::Interaction::Grabbing
+ } else if is_mouse_over {
+ mouse::Interaction::Grab
+ } else {
+ mouse::Interaction::default()
+ }
+}
+
+/// The local state of a [`VerticalSlider`].
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub struct State {
+ is_dragging: bool,
+}
+
+impl State {
+ /// Creates a new [`State`].
+ pub fn new() -> State {
+ State::default()
+ }
+}
diff --git a/src/widget.rs b/src/widget.rs
index 526f2b53..76cea7be 100644
--- a/src/widget.rs
+++ b/src/widget.rs
@@ -141,6 +141,7 @@ pub mod tooltip {
pub use iced_native::widget::progress_bar;
pub use iced_native::widget::rule;
pub use iced_native::widget::slider;
+pub use iced_native::widget::vertical_slider;
pub use iced_native::widget::Space;
pub use button::Button;
@@ -157,6 +158,7 @@ pub use text::Text;
pub use text_input::TextInput;
pub use toggler::Toggler;
pub use tooltip::Tooltip;
+pub use vertical_slider::VerticalSlider;
#[cfg(feature = "canvas")]
#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))]