Vertical orientation added to Slider.

This commit is contained in:
Casper Storm 2022-12-12 15:27:00 +01:00
parent 1c00adad61
commit ba95042fff
No known key found for this signature in database
GPG key ID: BABF49AA70C405C2
6 changed files with 250 additions and 40 deletions

View file

@ -90,6 +90,7 @@ members = [
"examples/tour", "examples/tour",
"examples/url_handler", "examples/url_handler",
"examples/websocket", "examples/websocket",
"examples/slider",
] ]
[dependencies] [dependencies]

View file

@ -0,0 +1,9 @@
[package]
name = "slider"
version = "0.1.0"
authors = ["Casper Rogild Storm<casper@rogildstorm.com>"]
edition = "2021"
publish = false
[dependencies]
iced = { path = "../.." }

14
examples/slider/README.md Normal file
View file

@ -0,0 +1,14 @@
## Slider
A bar and a handle that selects a single value from a range of values.
Can be oriented both vertical and horizontal.
<div align="center">
<img src="sliders.gif">
</div>
You can run it with `cargo run`:
```
cargo run --package slider
```

BIN
examples/slider/sliders.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

View file

@ -0,0 +1,69 @@
use iced::widget::{column, container, slider, text};
use iced::{Element, Length, Sandbox, Settings};
pub fn main() -> iced::Result {
Slider::run(Settings::default())
}
#[derive(Debug, Clone)]
pub enum Message {
SliderChanged(u8),
}
pub struct Slider {
slider_value: u8,
}
impl Sandbox for Slider {
type Message = Message;
fn new() -> Slider {
Slider { slider_value: 50 }
}
fn title(&self) -> String {
String::from("Slider - Iced")
}
fn update(&mut self, message: Message) {
match message {
Message::SliderChanged(value) => {
self.slider_value = value;
}
}
}
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 v_slider = container(
slider(0..=100, value, Message::SliderChanged)
.orientation(Vertical),
)
.height(Length::Units(200));
let text = text(format!("{value}"));
container(
column![
container(v_slider).width(Length::Fill).center_x(),
container(h_slider).width(Length::Fill).center_x(),
container(text).width(Length::Fill).center_x(),
]
.spacing(25),
)
.height(Length::Fill)
.width(Length::Fill)
.center_x()
.center_y()
.into()
}
}

View file

@ -16,10 +16,9 @@ use std::ops::RangeInclusive;
pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet}; pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet};
/// An horizontal bar and a handle that selects a single value from a range of /// A bar and a handle that selects a single value from a range of values.
/// values.
/// ///
/// A [`Slider`] will try to fill the horizontal space of its container. /// A [`Slider`] will try to fill the space of its container, based on its orientation.
/// ///
/// The [`Slider`] range of numeric values is generic and its step size defaults /// The [`Slider`] range of numeric values is generic and its step size defaults
/// to 1 unit. /// to 1 unit.
@ -53,8 +52,9 @@ where
value: T, value: T,
on_change: Box<dyn Fn(T) -> Message + 'a>, on_change: Box<dyn Fn(T) -> Message + 'a>,
on_release: Option<Message>, on_release: Option<Message>,
width: Length, width: Option<Length>,
height: u16, height: Option<Length>,
orientation: Orientation,
style: <Renderer::Theme as StyleSheet>::Style, style: <Renderer::Theme as StyleSheet>::Style,
} }
@ -65,9 +65,6 @@ where
Renderer: crate::Renderer, Renderer: crate::Renderer,
Renderer::Theme: StyleSheet, Renderer::Theme: StyleSheet,
{ {
/// The default height of a [`Slider`].
pub const DEFAULT_HEIGHT: u16 = 22;
/// Creates a new [`Slider`]. /// Creates a new [`Slider`].
/// ///
/// It expects: /// It expects:
@ -98,8 +95,9 @@ where
step: T::from(1), step: T::from(1),
on_change: Box::new(on_change), on_change: Box::new(on_change),
on_release: None, on_release: None,
width: Length::Fill, width: None,
height: Self::DEFAULT_HEIGHT, height: None,
orientation: Default::default(),
style: Default::default(), style: Default::default(),
} }
} }
@ -117,13 +115,13 @@ where
/// Sets the width of the [`Slider`]. /// Sets the width of the [`Slider`].
pub fn width(mut self, width: Length) -> Self { pub fn width(mut self, width: Length) -> Self {
self.width = width; self.width = Some(width);
self self
} }
/// Sets the height of the [`Slider`]. /// Sets the height of the [`Slider`].
pub fn height(mut self, height: u16) -> Self { pub fn height(mut self, height: Length) -> Self {
self.height = height; self.height = Some(height);
self self
} }
@ -141,6 +139,12 @@ where
self.step = step; self.step = step;
self 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> impl<'a, T, Message, Renderer> Widget<Message, Renderer>
@ -160,11 +164,17 @@ where
} }
fn width(&self) -> Length { fn width(&self) -> Length {
self.width match self.orientation {
Orientation::Horizontal => self.width.unwrap_or(Length::Fill),
Orientation::Vertical => Length::Shrink,
}
} }
fn height(&self) -> Length { fn height(&self) -> Length {
Length::Shrink match self.orientation {
Orientation::Horizontal => Length::Shrink,
Orientation::Vertical => self.height.unwrap_or(Length::Fill),
}
} }
fn layout( fn layout(
@ -172,9 +182,14 @@ where
_renderer: &Renderer, _renderer: &Renderer,
limits: &layout::Limits, limits: &layout::Limits,
) -> layout::Node { ) -> layout::Node {
let limits = let width = self
limits.width(self.width).height(Length::Units(self.height)); .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 size = limits.resolve(Size::ZERO); let size = limits.resolve(Size::ZERO);
layout::Node::new(size) layout::Node::new(size)
@ -201,6 +216,7 @@ where
self.step, self.step,
self.on_change.as_ref(), self.on_change.as_ref(),
&self.on_release, &self.on_release,
self.orientation,
) )
} }
@ -223,6 +239,7 @@ where
&self.range, &self.range,
theme, theme,
self.style, self.style,
self.orientation,
) )
} }
@ -270,6 +287,7 @@ pub fn update<Message, T>(
step: T, step: T,
on_change: &dyn Fn(T) -> Message, on_change: &dyn Fn(T) -> Message,
on_release: &Option<Message>, on_release: &Option<Message>,
orientation: Orientation,
) -> event::Status ) -> event::Status
where where
T: Copy + Into<f64> + num_traits::FromPrimitive, T: Copy + Into<f64> + num_traits::FromPrimitive,
@ -279,17 +297,40 @@ where
let mut change = || { let mut change = || {
let bounds = layout.bounds(); let bounds = layout.bounds();
let new_value = if cursor_position.x <= bounds.x {
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 {
*range.start() *range.start()
} else if cursor_position.x >= bounds.x + bounds.width { } else if cursor_above_bounds {
*range.end() *range.end()
} else { } else {
let step = step.into(); let step = step.into();
let start = (*range.start()).into(); let start = (*range.start()).into();
let end = (*range.end()).into(); let end = (*range.end()).into();
let percent = f64::from(cursor_position.x - bounds.x) let percent = match orientation {
/ f64::from(bounds.width); 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 steps = (percent * (end - start) / step).round(); let steps = (percent * (end - start) / step).round();
let value = steps * step + start; let value = steps * step + start;
@ -354,6 +395,7 @@ pub fn draw<T, R>(
range: &RangeInclusive<T>, range: &RangeInclusive<T>,
style_sheet: &dyn StyleSheet<Style = <R::Theme as StyleSheet>::Style>, style_sheet: &dyn StyleSheet<Style = <R::Theme as StyleSheet>::Style>,
style: <R::Theme as StyleSheet>::Style, style: <R::Theme as StyleSheet>::Style,
orientation: Orientation,
) where ) where
T: Into<f64> + Copy, T: Into<f64> + Copy,
R: crate::Renderer, R: crate::Renderer,
@ -370,15 +412,26 @@ pub fn draw<T, R>(
style_sheet.active(style) style_sheet.active(style)
}; };
let rail_y = bounds.y + (bounds.height / 2.0).round(); let rail = match orientation {
Orientation::Horizontal => bounds.y + (bounds.height / 2.0).round(),
Orientation::Vertical => bounds.x + (bounds.width / 2.0).round(),
};
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
bounds: Rectangle { bounds: match orientation {
x: bounds.x, Orientation::Horizontal => Rectangle {
y: rail_y - 1.0, x: bounds.x,
width: bounds.width, y: rail - 1.0,
height: 2.0, width: bounds.width,
height: 2.0,
},
Orientation::Vertical => Rectangle {
x: rail - 1.0,
y: bounds.y,
width: 2.0,
height: bounds.height,
},
}, },
border_radius: 0.0, border_radius: 0.0,
border_width: 0.0, border_width: 0.0,
@ -389,11 +442,19 @@ pub fn draw<T, R>(
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
bounds: Rectangle { bounds: match orientation {
x: bounds.x, Orientation::Horizontal => Rectangle {
y: rail_y + 1.0, x: bounds.x,
width: bounds.width, y: rail + 1.0,
height: 2.0, width: bounds.width,
height: 2.0,
},
Orientation::Vertical => Rectangle {
x: rail + 1.0,
y: bounds.y,
width: 2.0,
height: bounds.height,
},
}, },
border_radius: 0.0, border_radius: 0.0,
border_width: 0.0, border_width: 0.0,
@ -410,7 +471,14 @@ pub fn draw<T, R>(
HandleShape::Rectangle { HandleShape::Rectangle {
width, width,
border_radius, border_radius,
} => (f32::from(width), bounds.height, border_radius), } => {
let handle_height = match orientation {
Orientation::Horizontal => bounds.height,
Orientation::Vertical => bounds.width,
};
(f32::from(width), handle_height, border_radius)
}
}; };
let value = value.into() as f32; let value = value.into() as f32;
@ -423,17 +491,33 @@ pub fn draw<T, R>(
let handle_offset = if range_start >= range_end { let handle_offset = if range_start >= range_end {
0.0 0.0
} else { } else {
bounds.width * (value - range_start) / (range_end - range_start) match orientation {
- handle_width / 2.0 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
}
}
}; };
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
bounds: Rectangle { bounds: match orientation {
x: bounds.x + handle_offset.round(), Orientation::Horizontal => Rectangle {
y: rail_y - handle_height / 2.0, x: bounds.x + handle_offset.round(),
width: handle_width, y: rail - handle_height / 2.0,
height: handle_height, 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,
},
}, },
border_radius: handle_border_radius, border_radius: handle_border_radius,
border_width: style.handle.border_width, border_width: style.handle.border_width,
@ -473,3 +557,36 @@ impl State {
State::default() 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,
}
}
}