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/url_handler",
"examples/websocket",
"examples/slider",
]
[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};
/// An horizontal bar and a handle that selects a single value from a range of
/// values.
/// A 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.
/// 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
/// to 1 unit.
@ -53,8 +52,9 @@ where
value: T,
on_change: Box<dyn Fn(T) -> Message + 'a>,
on_release: Option<Message>,
width: Length,
height: u16,
width: Option<Length>,
height: Option<Length>,
orientation: Orientation,
style: <Renderer::Theme as StyleSheet>::Style,
}
@ -65,9 +65,6 @@ 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:
@ -98,8 +95,9 @@ where
step: T::from(1),
on_change: Box::new(on_change),
on_release: None,
width: Length::Fill,
height: Self::DEFAULT_HEIGHT,
width: None,
height: None,
orientation: Default::default(),
style: Default::default(),
}
}
@ -117,13 +115,13 @@ where
/// Sets the width of the [`Slider`].
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self.width = Some(width);
self
}
/// Sets the height of the [`Slider`].
pub fn height(mut self, height: u16) -> Self {
self.height = height;
pub fn height(mut self, height: Length) -> Self {
self.height = Some(height);
self
}
@ -141,6 +139,12 @@ 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>
@ -160,11 +164,17 @@ where
}
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 {
Length::Shrink
match self.orientation {
Orientation::Horizontal => Length::Shrink,
Orientation::Vertical => self.height.unwrap_or(Length::Fill),
}
}
fn layout(
@ -172,9 +182,14 @@ where
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits =
limits.width(self.width).height(Length::Units(self.height));
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 size = limits.resolve(Size::ZERO);
layout::Node::new(size)
@ -201,6 +216,7 @@ where
self.step,
self.on_change.as_ref(),
&self.on_release,
self.orientation,
)
}
@ -223,6 +239,7 @@ where
&self.range,
theme,
self.style,
self.orientation,
)
}
@ -270,6 +287,7 @@ 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,
@ -279,17 +297,40 @@ where
let mut change = || {
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()
} else if cursor_position.x >= bounds.x + bounds.width {
} else if cursor_above_bounds {
*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 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 steps = (percent * (end - start) / step).round();
let value = steps * step + start;
@ -354,6 +395,7 @@ 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,
@ -370,15 +412,26 @@ pub fn draw<T, R>(
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::Quad {
bounds: Rectangle {
x: bounds.x,
y: rail_y - 1.0,
width: bounds.width,
height: 2.0,
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,
},
},
border_radius: 0.0,
border_width: 0.0,
@ -389,11 +442,19 @@ pub fn draw<T, R>(
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
x: bounds.x,
y: rail_y + 1.0,
width: bounds.width,
height: 2.0,
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,
},
},
border_radius: 0.0,
border_width: 0.0,
@ -410,7 +471,14 @@ pub fn draw<T, R>(
HandleShape::Rectangle {
width,
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;
@ -423,17 +491,33 @@ pub fn draw<T, R>(
let handle_offset = if range_start >= range_end {
0.0
} else {
bounds.width * (value - range_start) / (range_end - range_start)
- handle_width / 2.0
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
}
}
};
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
x: bounds.x + handle_offset.round(),
y: rail_y - handle_height / 2.0,
width: handle_width,
height: handle_height,
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,
},
},
border_radius: handle_border_radius,
border_width: style.handle.border_width,
@ -473,3 +557,36 @@ 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,
}
}
}