Implement styling for Slider

This commit is contained in:
Héctor Ramón Jiménez 2020-01-07 00:28:08 +01:00
parent d0dc7cebf9
commit b329003c8f
9 changed files with 266 additions and 47 deletions

View File

@ -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
}
}
}
}
}

View File

@ -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 {
}
}

View File

@ -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)
}
}

View File

@ -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};

View File

@ -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
View 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)
}
}

View File

@ -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,
};
(

View File

@ -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
View 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>;