Implement styling for Slider
This commit is contained in:
parent
d0dc7cebf9
commit
b329003c8f
@ -1,6 +1,6 @@
|
||||
use iced::{
|
||||
button, scrollable, text_input, Button, Column, Container, Element, Length,
|
||||
Radio, Row, Sandbox, Scrollable, Settings, Text, TextInput,
|
||||
button, scrollable, slider, text_input, Button, Column, Container, Element,
|
||||
Length, Radio, Row, Sandbox, Scrollable, Settings, Slider, Text, TextInput,
|
||||
};
|
||||
|
||||
pub fn main() {
|
||||
@ -14,6 +14,8 @@ struct Styling {
|
||||
input: text_input::State,
|
||||
input_value: String,
|
||||
button: button::State,
|
||||
slider: slider::State,
|
||||
slider_value: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -21,6 +23,7 @@ enum Message {
|
||||
ThemeChanged(style::Theme),
|
||||
InputChanged(String),
|
||||
ButtonPressed,
|
||||
SliderChanged(f32),
|
||||
}
|
||||
|
||||
impl Sandbox for Styling {
|
||||
@ -39,6 +42,7 @@ impl Sandbox for Styling {
|
||||
Message::ThemeChanged(theme) => self.theme = theme,
|
||||
Message::InputChanged(value) => self.input_value = value,
|
||||
Message::ButtonPressed => (),
|
||||
Message::SliderChanged(value) => self.slider_value = value,
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,12 +74,21 @@ impl Sandbox for Styling {
|
||||
.on_press(Message::ButtonPressed)
|
||||
.style(self.theme);
|
||||
|
||||
let slider = Slider::new(
|
||||
&mut self.slider,
|
||||
0.0..=100.0,
|
||||
self.slider_value,
|
||||
Message::SliderChanged,
|
||||
)
|
||||
.style(self.theme);
|
||||
|
||||
let content = Column::new()
|
||||
.spacing(20)
|
||||
.padding(20)
|
||||
.max_width(600)
|
||||
.push(choose_theme)
|
||||
.push(Row::new().spacing(10).push(text_input).push(button));
|
||||
.push(Row::new().spacing(10).push(text_input).push(button))
|
||||
.push(slider);
|
||||
|
||||
let scrollable = Scrollable::new(&mut self.scroll)
|
||||
.style(self.theme)
|
||||
@ -91,7 +104,7 @@ impl Sandbox for Styling {
|
||||
}
|
||||
|
||||
mod style {
|
||||
use iced::{button, container, scrollable, text_input};
|
||||
use iced::{button, container, scrollable, slider, text_input};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Theme {
|
||||
@ -145,6 +158,15 @@ mod style {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Theme> for Box<dyn slider::StyleSheet> {
|
||||
fn from(theme: Theme) -> Self {
|
||||
match theme {
|
||||
Theme::Light => Default::default(),
|
||||
Theme::Dark => dark::Slider.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod light {
|
||||
use iced::{button, Background, Color, Vector};
|
||||
|
||||
@ -175,7 +197,8 @@ mod style {
|
||||
|
||||
mod dark {
|
||||
use iced::{
|
||||
button, container, scrollable, text_input, Background, Color,
|
||||
button, container, scrollable, slider, text_input, Background,
|
||||
Color,
|
||||
};
|
||||
|
||||
pub struct Container;
|
||||
@ -291,5 +314,47 @@ mod style {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Slider;
|
||||
|
||||
impl slider::StyleSheet for Slider {
|
||||
fn active(&self) -> slider::Style {
|
||||
let blue = Color::from_rgb8(0x72, 0x89, 0xDA);
|
||||
|
||||
slider::Style {
|
||||
rail_colors: (blue, Color { a: 0.1, ..blue }),
|
||||
handle: slider::Handle {
|
||||
shape: slider::HandleShape::Circle { radius: 9 },
|
||||
color: blue,
|
||||
border_width: 0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn hovered(&self) -> slider::Style {
|
||||
let active = self.active();
|
||||
|
||||
slider::Style {
|
||||
handle: slider::Handle {
|
||||
color: Color::from_rgb(0.90, 0.90, 0.90),
|
||||
..active.handle
|
||||
},
|
||||
..active
|
||||
}
|
||||
}
|
||||
|
||||
fn dragging(&self) -> slider::Style {
|
||||
let active = self.active();
|
||||
|
||||
slider::Style {
|
||||
handle: slider::Handle {
|
||||
color: Color::from_rgb(0.85, 0.85, 0.85),
|
||||
..active.handle
|
||||
},
|
||||
..active
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
button, checkbox, column, radio, row, scrollable, text, text_input, Color,
|
||||
Element, Font, HorizontalAlignment, Layout, Point, Rectangle, Renderer,
|
||||
Size, VerticalAlignment,
|
||||
button, checkbox, column, radio, row, scrollable, slider, text, text_input,
|
||||
Color, Element, Font, HorizontalAlignment, Layout, Point, Rectangle,
|
||||
Renderer, Size, VerticalAlignment,
|
||||
};
|
||||
|
||||
/// A renderer that does nothing.
|
||||
@ -180,3 +180,22 @@ impl checkbox::Renderer for Null {
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl slider::Renderer for Null {
|
||||
type Style = ();
|
||||
|
||||
fn height(&self) -> u32 {
|
||||
30
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
_bounds: Rectangle,
|
||||
_cursor_position: Point,
|
||||
_range: std::ops::RangeInclusive<f32>,
|
||||
_value: f32,
|
||||
_is_dragging: bool,
|
||||
_style_sheet: &Self::Style,
|
||||
) -> Self::Output {
|
||||
}
|
||||
}
|
||||
|
@ -21,8 +21,9 @@ use std::{hash::Hash, ops::RangeInclusive};
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use iced_native::{slider, Slider};
|
||||
/// # use iced_native::{slider, renderer::Null};
|
||||
/// #
|
||||
/// # pub type Slider<'a, Message> = iced_native::Slider<'a, Message, Null>;
|
||||
/// pub enum Message {
|
||||
/// SliderChanged(f32),
|
||||
/// }
|
||||
@ -35,15 +36,16 @@ use std::{hash::Hash, ops::RangeInclusive};
|
||||
///
|
||||
/// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true)
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Slider<'a, Message> {
|
||||
pub struct Slider<'a, Message, Renderer: self::Renderer> {
|
||||
state: &'a mut State,
|
||||
range: RangeInclusive<f32>,
|
||||
value: f32,
|
||||
on_change: Box<dyn Fn(f32) -> Message>,
|
||||
width: Length,
|
||||
style: Renderer::Style,
|
||||
}
|
||||
|
||||
impl<'a, Message> Slider<'a, Message> {
|
||||
impl<'a, Message, Renderer: self::Renderer> Slider<'a, Message, Renderer> {
|
||||
/// Creates a new [`Slider`].
|
||||
///
|
||||
/// It expects:
|
||||
@ -71,6 +73,7 @@ impl<'a, Message> Slider<'a, Message> {
|
||||
range,
|
||||
on_change: Box::new(on_change),
|
||||
width: Length::Fill,
|
||||
style: Renderer::Style::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,6 +84,14 @@ impl<'a, Message> Slider<'a, Message> {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the style of the [`Slider`].
|
||||
///
|
||||
/// [`Slider`]: struct.Slider.html
|
||||
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
|
||||
self.style = style.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// The local state of a [`Slider`].
|
||||
@ -100,7 +111,8 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer> for Slider<'a, Message>
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Slider<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
{
|
||||
@ -188,6 +200,7 @@ where
|
||||
self.range.clone(),
|
||||
self.value,
|
||||
self.state.is_dragging,
|
||||
&self.style,
|
||||
)
|
||||
}
|
||||
|
||||
@ -204,6 +217,8 @@ where
|
||||
/// [`Slider`]: struct.Slider.html
|
||||
/// [renderer]: ../../renderer/index.html
|
||||
pub trait Renderer: crate::Renderer {
|
||||
type Style: Default;
|
||||
|
||||
/// Returns the height of the [`Slider`].
|
||||
///
|
||||
/// [`Slider`]: struct.Slider.html
|
||||
@ -228,16 +243,19 @@ pub trait Renderer: crate::Renderer {
|
||||
range: RangeInclusive<f32>,
|
||||
value: f32,
|
||||
is_dragging: bool,
|
||||
style: &Self::Style,
|
||||
) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Slider<'a, Message>>
|
||||
impl<'a, Message, Renderer> From<Slider<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
Renderer: 'static + self::Renderer,
|
||||
Message: 'static,
|
||||
{
|
||||
fn from(slider: Slider<'a, Message>) -> Element<'a, Message, Renderer> {
|
||||
fn from(
|
||||
slider: Slider<'a, Message, Renderer>,
|
||||
) -> Element<'a, Message, Renderer> {
|
||||
Element::new(slider)
|
||||
}
|
||||
}
|
||||
|
@ -24,17 +24,6 @@ pub mod widget {
|
||||
//! [`text_input::State`]: text_input/struct.State.html
|
||||
pub use iced_wgpu::widget::*;
|
||||
|
||||
pub mod slider {
|
||||
//! Display an interactive selector of a single value from a range of
|
||||
//! values.
|
||||
//!
|
||||
//! A [`Slider`] has some local [`State`].
|
||||
//!
|
||||
//! [`Slider`]: struct.Slider.html
|
||||
//! [`State`]: struct.State.html
|
||||
pub use iced_winit::slider::{Slider, State};
|
||||
}
|
||||
|
||||
pub mod image {
|
||||
//! Display images in your user interface.
|
||||
pub use iced_winit::image::{Handle, Image};
|
||||
|
@ -1,4 +1,5 @@
|
||||
pub mod button;
|
||||
pub mod container;
|
||||
pub mod scrollable;
|
||||
pub mod slider;
|
||||
pub mod text_input;
|
||||
|
95
style/src/slider.rs
Normal file
95
style/src/slider.rs
Normal file
@ -0,0 +1,95 @@
|
||||
//! Display an interactive selector of a single value from a range of values.
|
||||
use iced_core::Color;
|
||||
|
||||
/// The appearance of a slider.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Style {
|
||||
pub rail_colors: (Color, Color),
|
||||
pub handle: Handle,
|
||||
}
|
||||
|
||||
/// The appearance of the handle of a slider.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Handle {
|
||||
pub shape: HandleShape,
|
||||
pub color: Color,
|
||||
pub border_width: u16,
|
||||
pub border_color: Color,
|
||||
}
|
||||
|
||||
/// The shape of the handle of a slider.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum HandleShape {
|
||||
Circle { radius: u16 },
|
||||
Rectangle { width: u16, border_radius: u16 },
|
||||
}
|
||||
|
||||
/// A set of rules that dictate the style of a slider.
|
||||
pub trait StyleSheet {
|
||||
/// Produces the style of an active slider.
|
||||
fn active(&self) -> Style;
|
||||
|
||||
/// Produces the style of an hovered slider.
|
||||
fn hovered(&self) -> Style;
|
||||
|
||||
/// Produces the style of a slider that is being dragged.
|
||||
fn dragging(&self) -> Style;
|
||||
}
|
||||
|
||||
struct Default;
|
||||
|
||||
impl StyleSheet for Default {
|
||||
fn active(&self) -> Style {
|
||||
Style {
|
||||
rail_colors: ([0.6, 0.6, 0.6, 0.5].into(), Color::WHITE),
|
||||
handle: Handle {
|
||||
shape: HandleShape::Rectangle {
|
||||
width: 8,
|
||||
border_radius: 4,
|
||||
},
|
||||
color: Color::from_rgb(0.95, 0.95, 0.95),
|
||||
border_color: Color::from_rgb(0.6, 0.6, 0.6),
|
||||
border_width: 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn hovered(&self) -> Style {
|
||||
let active = self.active();
|
||||
|
||||
Style {
|
||||
handle: Handle {
|
||||
color: Color::from_rgb(0.90, 0.90, 0.90),
|
||||
..active.handle
|
||||
},
|
||||
..active
|
||||
}
|
||||
}
|
||||
|
||||
fn dragging(&self) -> Style {
|
||||
let active = self.active();
|
||||
|
||||
Style {
|
||||
handle: Handle {
|
||||
color: Color::from_rgb(0.85, 0.85, 0.85),
|
||||
..active.handle
|
||||
},
|
||||
..active
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::default::Default for Box<dyn StyleSheet> {
|
||||
fn default() -> Self {
|
||||
Box::new(Default)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Box<dyn StyleSheet>
|
||||
where
|
||||
T: 'static + StyleSheet,
|
||||
{
|
||||
fn from(style: T) -> Self {
|
||||
Box::new(style)
|
||||
}
|
||||
}
|
@ -1,10 +1,14 @@
|
||||
use crate::{Primitive, Renderer};
|
||||
use crate::{
|
||||
slider::{HandleShape, StyleSheet},
|
||||
Primitive, Renderer,
|
||||
};
|
||||
use iced_native::{slider, Background, Color, MouseCursor, Point, Rectangle};
|
||||
|
||||
const HANDLE_WIDTH: f32 = 8.0;
|
||||
const HANDLE_HEIGHT: f32 = 22.0;
|
||||
|
||||
impl slider::Renderer for Renderer {
|
||||
type Style = Box<dyn StyleSheet>;
|
||||
|
||||
fn height(&self) -> u32 {
|
||||
30
|
||||
}
|
||||
@ -16,9 +20,18 @@ impl slider::Renderer for Renderer {
|
||||
range: std::ops::RangeInclusive<f32>,
|
||||
value: f32,
|
||||
is_dragging: bool,
|
||||
style_sheet: &Self::Style,
|
||||
) -> Self::Output {
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
|
||||
let style = if is_dragging {
|
||||
style_sheet.dragging()
|
||||
} else if is_mouse_over {
|
||||
style_sheet.hovered()
|
||||
} else {
|
||||
style_sheet.active()
|
||||
};
|
||||
|
||||
let rail_y = bounds.y + (bounds.height / 2.0).round();
|
||||
|
||||
let (rail_top, rail_bottom) = (
|
||||
@ -29,7 +42,7 @@ impl slider::Renderer for Renderer {
|
||||
width: bounds.width,
|
||||
height: 2.0,
|
||||
},
|
||||
background: Background::Color([0.6, 0.6, 0.6, 0.5].into()),
|
||||
background: Background::Color(style.rail_colors.0),
|
||||
border_radius: 0,
|
||||
border_width: 0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
@ -41,7 +54,7 @@ impl slider::Renderer for Renderer {
|
||||
width: bounds.width,
|
||||
height: 2.0,
|
||||
},
|
||||
background: Background::Color(Color::WHITE),
|
||||
background: Background::Color(style.rail_colors.1),
|
||||
border_radius: 0,
|
||||
border_width: 0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
@ -50,29 +63,31 @@ impl slider::Renderer for Renderer {
|
||||
|
||||
let (range_start, range_end) = range.into_inner();
|
||||
|
||||
let handle_offset = (bounds.width - HANDLE_WIDTH)
|
||||
let (handle_width, handle_height, handle_border_radius) =
|
||||
match style.handle.shape {
|
||||
HandleShape::Circle { radius } => {
|
||||
(f32::from(radius * 2), f32::from(radius * 2), radius)
|
||||
}
|
||||
HandleShape::Rectangle {
|
||||
width,
|
||||
border_radius,
|
||||
} => (f32::from(width), HANDLE_HEIGHT, border_radius),
|
||||
};
|
||||
|
||||
let handle_offset = (bounds.width - handle_width)
|
||||
* ((value - range_start) / (range_end - range_start).max(1.0));
|
||||
|
||||
let handle = Primitive::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + handle_offset.round(),
|
||||
y: rail_y - HANDLE_HEIGHT / 2.0,
|
||||
width: HANDLE_WIDTH,
|
||||
height: HANDLE_HEIGHT,
|
||||
y: rail_y - handle_height / 2.0,
|
||||
width: handle_width,
|
||||
height: handle_height,
|
||||
},
|
||||
background: Background::Color(
|
||||
if is_dragging {
|
||||
[0.85, 0.85, 0.85]
|
||||
} else if is_mouse_over {
|
||||
[0.90, 0.90, 0.90]
|
||||
} else {
|
||||
[0.95, 0.95, 0.95]
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
border_radius: 4,
|
||||
border_width: 1,
|
||||
border_color: Color::from_rgb(0.6, 0.6, 0.6),
|
||||
background: Background::Color(style.handle.color),
|
||||
border_radius: handle_border_radius,
|
||||
border_width: style.handle.border_width,
|
||||
border_color: style.handle.border_color,
|
||||
};
|
||||
|
||||
(
|
||||
|
@ -1,4 +1,5 @@
|
||||
pub mod button;
|
||||
pub mod container;
|
||||
pub mod scrollable;
|
||||
pub mod slider;
|
||||
pub mod text_input;
|
||||
|
16
wgpu/src/widget/slider.rs
Normal file
16
wgpu/src/widget/slider.rs
Normal file
@ -0,0 +1,16 @@
|
||||
//! Display an interactive selector of a single value from a range of values.
|
||||
//!
|
||||
//! A [`Slider`] has some local [`State`].
|
||||
//!
|
||||
//! [`Slider`]: struct.Slider.html
|
||||
//! [`State`]: struct.State.html
|
||||
use crate::Renderer;
|
||||
|
||||
pub use iced_native::slider::State;
|
||||
pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet};
|
||||
|
||||
/// An horizontal bar and a handle that selects a single value from a range of
|
||||
/// values.
|
||||
///
|
||||
/// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`.
|
||||
pub type Slider<'a, Message> = iced_native::Slider<'a, Message, Renderer>;
|
Loading…
Reference in New Issue
Block a user