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/url_handler",
|
||||
"examples/websocket",
|
||||
"examples/slider",
|
||||
]
|
||||
|
||||
[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};
|
||||
|
||||
/// 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,16 +412,27 @@ 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 {
|
||||
bounds: match orientation {
|
||||
Orientation::Horizontal => Rectangle {
|
||||
x: bounds.x,
|
||||
y: rail_y - 1.0,
|
||||
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,
|
||||
border_color: Color::TRANSPARENT,
|
||||
|
|
@ -389,12 +442,20 @@ pub fn draw<T, R>(
|
|||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
bounds: match orientation {
|
||||
Orientation::Horizontal => Rectangle {
|
||||
x: bounds.x,
|
||||
y: rail_y + 1.0,
|
||||
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,
|
||||
border_color: Color::TRANSPARENT,
|
||||
|
|
@ -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,18 +491,34 @@ 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
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
bounds: match orientation {
|
||||
Orientation::Horizontal => Rectangle {
|
||||
x: bounds.x + handle_offset.round(),
|
||||
y: rail_y - handle_height / 2.0,
|
||||
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,
|
||||
border_color: style.handle.border_color,
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue