Vertical orientation added to Slider.
This commit is contained in:
parent
1c00adad61
commit
ba95042fff
6 changed files with 250 additions and 40 deletions
|
|
@ -90,6 +90,7 @@ members = [
|
||||||
"examples/tour",
|
"examples/tour",
|
||||||
"examples/url_handler",
|
"examples/url_handler",
|
||||||
"examples/websocket",
|
"examples/websocket",
|
||||||
|
"examples/slider",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
||||||
9
examples/slider/Cargo.toml
Normal file
9
examples/slider/Cargo.toml
Normal 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
14
examples/slider/README.md
Normal 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
BIN
examples/slider/sliders.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 85 KiB |
69
examples/slider/src/main.rs
Normal file
69
examples/slider/src/main.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue