Draft Style and StyleSheet for Button

This commit is contained in:
Héctor Ramón Jiménez 2019-12-29 10:57:01 +01:00
parent 4b86c2ff98
commit c7b170da6d
15 changed files with 275 additions and 114 deletions

View File

@ -220,7 +220,26 @@ impl From<surf::Exception> for Error {
fn button<'a>(state: &'a mut button::State, text: &str) -> Button<'a, Message> { fn button<'a>(state: &'a mut button::State, text: &str) -> Button<'a, Message> {
Button::new(state, Text::new(text).color(Color::WHITE)) Button::new(state, Text::new(text).color(Color::WHITE))
.background(Color::from_rgb(0.11, 0.42, 0.87))
.border_radius(10)
.padding(10) .padding(10)
.style(style::Button::Primary)
}
mod style {
use iced::{button, Background, Color};
pub enum Button {
Primary,
}
impl button::StyleSheet for Button {
fn active(&self) -> button::Style {
button::Style {
background: Some(Background::Color(match self {
Button::Primary => Color::from_rgb(0.11, 0.42, 0.87),
})),
border_radius: 12,
shadow_offset: 1.0,
}
}
}
} }

View File

@ -1,7 +1,6 @@
use iced::{ use iced::{
button, Align, Application, Background, Button, Color, Column, Command, button, Align, Application, Button, Color, Column, Command, Container,
Container, Element, HorizontalAlignment, Length, Row, Settings, Element, HorizontalAlignment, Length, Row, Settings, Subscription, Text,
Subscription, Text,
}; };
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@ -99,7 +98,7 @@ impl Application for Stopwatch {
.width(Length::Shrink) .width(Length::Shrink)
.size(40); .size(40);
let button = |state, label, color: [f32; 3]| { let button = |state, label, style| {
Button::new( Button::new(
state, state,
Text::new(label) Text::new(label)
@ -107,22 +106,22 @@ impl Application for Stopwatch {
.horizontal_alignment(HorizontalAlignment::Center), .horizontal_alignment(HorizontalAlignment::Center),
) )
.min_width(80) .min_width(80)
.background(Background::Color(color.into()))
.border_radius(10)
.padding(10) .padding(10)
.style(style)
}; };
let toggle_button = { let toggle_button = {
let (label, color) = match self.state { let (label, color) = match self.state {
State::Idle => ("Start", [0.11, 0.42, 0.87]), State::Idle => ("Start", style::Button::Primary),
State::Ticking { .. } => ("Stop", [0.9, 0.4, 0.4]), State::Ticking { .. } => ("Stop", style::Button::Destructive),
}; };
button(&mut self.toggle, label, color).on_press(Message::Toggle) button(&mut self.toggle, label, color).on_press(Message::Toggle)
}; };
let reset_button = button(&mut self.reset, "Reset", [0.7, 0.7, 0.7]) let reset_button =
.on_press(Message::Reset); button(&mut self.reset, "Reset", style::Button::Secondary)
.on_press(Message::Reset);
let controls = Row::new() let controls = Row::new()
.width(Length::Shrink) .width(Length::Shrink)
@ -180,3 +179,27 @@ mod time {
} }
} }
} }
mod style {
use iced::{button, Background, Color};
pub enum Button {
Primary,
Secondary,
Destructive,
}
impl button::StyleSheet for Button {
fn active(&self) -> button::Style {
button::Style {
background: Some(Background::Color(match self {
Button::Primary => Color::from_rgb(0.11, 0.42, 0.87),
Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5),
Button::Destructive => Color::from_rgb(0.8, 0.2, 0.2),
})),
border_radius: 12,
shadow_offset: 1.0,
}
}
}
}

View File

@ -296,7 +296,8 @@ impl Task {
edit_icon().color([0.5, 0.5, 0.5]), edit_icon().color([0.5, 0.5, 0.5]),
) )
.on_press(TaskMessage::Edit) .on_press(TaskMessage::Edit)
.padding(10), .padding(10)
.style(style::Button::NoBackground),
) )
.into() .into()
} }
@ -331,8 +332,7 @@ impl Task {
) )
.on_press(TaskMessage::Delete) .on_press(TaskMessage::Delete)
.padding(10) .padding(10)
.border_radius(5) .style(style::Button::Destructive),
.background(Color::from_rgb(0.8, 0.2, 0.2)),
) )
.into() .into()
} }
@ -361,15 +361,12 @@ impl Controls {
let label = Text::new(label).size(16).width(Length::Shrink); let label = Text::new(label).size(16).width(Length::Shrink);
let button = if filter == current_filter { let button = if filter == current_filter {
Button::new(state, label.color(Color::WHITE)) Button::new(state, label.color(Color::WHITE))
.background(Color::from_rgb(0.2, 0.2, 0.7)) .style(style::Button::FilterSelected)
} else { } else {
Button::new(state, label) Button::new(state, label).style(style::Button::NoBackground)
}; };
button button.on_press(Message::FilterChanged(filter)).padding(8)
.on_press(Message::FilterChanged(filter))
.padding(8)
.border_radius(10)
}; };
Row::new() Row::new()
@ -562,3 +559,39 @@ impl SavedState {
Ok(()) Ok(())
} }
} }
mod style {
use iced::{button, Background, Color};
pub enum Button {
FilterSelected,
NoBackground,
Destructive,
}
impl button::StyleSheet for Button {
fn active(&self) -> button::Style {
match self {
Button::FilterSelected => button::Style {
background: Some(Background::Color(Color::from_rgb(
0.2, 0.2, 0.7,
))),
border_radius: 10,
shadow_offset: 0.0,
},
Button::NoBackground => button::Style {
background: None,
border_radius: 0,
shadow_offset: 0.0,
},
Button::Destructive => button::Style {
background: Some(Background::Color(Color::from_rgb(
0.8, 0.2, 0.2,
))),
border_radius: 5,
shadow_offset: 1.0,
},
}
}
}
}

View File

@ -62,8 +62,9 @@ impl Sandbox for Tour {
if steps.has_previous() { if steps.has_previous() {
controls = controls.push( controls = controls.push(
secondary_button(back_button, "Back") button(back_button, "Back")
.on_press(Message::BackPressed), .on_press(Message::BackPressed)
.style(style::Button::Secondary),
); );
} }
@ -71,8 +72,9 @@ impl Sandbox for Tour {
if steps.can_continue() { if steps.can_continue() {
controls = controls.push( controls = controls.push(
primary_button(next_button, "Next") button(next_button, "Next")
.on_press(Message::NextPressed), .on_press(Message::NextPressed)
.style(style::Button::Primary),
); );
} }
@ -697,24 +699,9 @@ fn button<'a, Message>(
.horizontal_alignment(HorizontalAlignment::Center), .horizontal_alignment(HorizontalAlignment::Center),
) )
.padding(12) .padding(12)
.border_radius(12)
.min_width(100) .min_width(100)
} }
fn primary_button<'a, Message>(
state: &'a mut button::State,
label: &str,
) -> Button<'a, Message> {
button(state, label).background(Color::from_rgb(0.11, 0.42, 0.87))
}
fn secondary_button<'a, Message>(
state: &'a mut button::State,
label: &str,
) -> Button<'a, Message> {
button(state, label).background(Color::from_rgb(0.4, 0.4, 0.4))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Language { pub enum Language {
Rust, Rust,
@ -757,6 +744,28 @@ pub enum Layout {
Column, Column,
} }
mod style {
use iced::{button, Background, Color};
pub enum Button {
Primary,
Secondary,
}
impl button::StyleSheet for Button {
fn active(&self) -> button::Style {
button::Style {
background: Some(Background::Color(match self {
Button::Primary => Color::from_rgb(0.11, 0.42, 0.87),
Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5),
})),
border_radius: 12,
shadow_offset: 1.0,
}
}
}
}
// This should be gracefully handled by Iced in the future. Probably using our // This should be gracefully handled by Iced in the future. Probably using our
// own proc macro, or maybe the whole process is streamlined by `wasm-pack` at // own proc macro, or maybe the whole process is streamlined by `wasm-pack` at
// some point. // some point.

View File

@ -34,7 +34,7 @@
//! [`Windowed`]: renderer/trait.Windowed.html //! [`Windowed`]: renderer/trait.Windowed.html
//! [`UserInterface`]: struct.UserInterface.html //! [`UserInterface`]: struct.UserInterface.html
//! [renderer]: renderer/index.html //! [renderer]: renderer/index.html
#![deny(missing_docs)] //#![deny(missing_docs)]
#![deny(missing_debug_implementations)] #![deny(missing_debug_implementations)]
#![deny(unused_results)] #![deny(unused_results)]
#![deny(unsafe_code)] #![deny(unsafe_code)]

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
button, checkbox, column, radio, row, scrollable, text, text_input, button, checkbox, column, radio, row, scrollable, text, text_input, Color,
Background, Color, Element, Font, HorizontalAlignment, Layout, Point, Element, Font, HorizontalAlignment, Layout, Point, Rectangle, Renderer,
Rectangle, Renderer, Size, VerticalAlignment, Size, VerticalAlignment,
}; };
/// A renderer that does nothing. /// A renderer that does nothing.
@ -117,13 +117,14 @@ impl text_input::Renderer for Null {
} }
impl button::Renderer for Null { impl button::Renderer for Null {
type Style = ();
fn draw( fn draw(
&mut self, &mut self,
_bounds: Rectangle, _bounds: Rectangle,
_cursor_position: Point, _cursor_position: Point,
_is_pressed: bool, _is_pressed: bool,
_background: Option<Background>, _style: &Self::Style,
_border_radius: u16,
_content: Self::Output, _content: Self::Output,
) -> Self::Output { ) -> Self::Output {
} }

View File

@ -6,8 +6,8 @@
//! [`State`]: struct.State.html //! [`State`]: struct.State.html
use crate::{ use crate::{
input::{mouse, ButtonState}, input::{mouse, ButtonState},
layout, Background, Clipboard, Element, Event, Hasher, Layout, Length, layout, Clipboard, Element, Event, Hasher, Layout, Length, Point,
Point, Rectangle, Widget, Rectangle, Widget,
}; };
use std::hash::Hash; use std::hash::Hash;
@ -28,7 +28,7 @@ use std::hash::Hash;
/// .on_press(Message::ButtonPressed); /// .on_press(Message::ButtonPressed);
/// ``` /// ```
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Button<'a, Message, Renderer> { pub struct Button<'a, Message, Renderer: self::Renderer> {
state: &'a mut State, state: &'a mut State,
content: Element<'a, Message, Renderer>, content: Element<'a, Message, Renderer>,
on_press: Option<Message>, on_press: Option<Message>,
@ -37,11 +37,13 @@ pub struct Button<'a, Message, Renderer> {
min_width: u32, min_width: u32,
min_height: u32, min_height: u32,
padding: u16, padding: u16,
background: Option<Background>, style: Renderer::Style,
border_radius: u16,
} }
impl<'a, Message, Renderer> Button<'a, Message, Renderer> { impl<'a, Message, Renderer> Button<'a, Message, Renderer>
where
Renderer: self::Renderer,
{
/// Creates a new [`Button`] with some local [`State`] and the given /// Creates a new [`Button`] with some local [`State`] and the given
/// content. /// content.
/// ///
@ -60,8 +62,7 @@ impl<'a, Message, Renderer> Button<'a, Message, Renderer> {
min_width: 0, min_width: 0,
min_height: 0, min_height: 0,
padding: 0, padding: 0,
background: None, style: Renderer::Style::default(),
border_radius: 0,
} }
} }
@ -105,23 +106,6 @@ impl<'a, Message, Renderer> Button<'a, Message, Renderer> {
self self
} }
/// Sets the [`Background`] of the [`Button`].
///
/// [`Button`]: struct.Button.html
/// [`Background`]: ../../struct.Background.html
pub fn background<T: Into<Background>>(mut self, background: T) -> Self {
self.background = Some(background.into());
self
}
/// Sets the border radius of the [`Button`].
///
/// [`Button`]: struct.Button.html
pub fn border_radius(mut self, border_radius: u16) -> Self {
self.border_radius = border_radius;
self
}
/// Sets the message that will be produced when the [`Button`] is pressed. /// Sets the message that will be produced when the [`Button`] is pressed.
/// ///
/// [`Button`]: struct.Button.html /// [`Button`]: struct.Button.html
@ -129,6 +113,11 @@ impl<'a, Message, Renderer> Button<'a, Message, Renderer> {
self.on_press = Some(msg); self.on_press = Some(msg);
self self
} }
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
}
} }
/// The local state of a [`Button`]. /// The local state of a [`Button`].
@ -240,8 +229,7 @@ where
layout.bounds(), layout.bounds(),
cursor_position, cursor_position,
self.state.is_pressed, self.state.is_pressed,
self.background, &self.style,
self.border_radius,
content, content,
) )
} }
@ -260,6 +248,8 @@ where
/// [`Button`]: struct.Button.html /// [`Button`]: struct.Button.html
/// [renderer]: ../../renderer/index.html /// [renderer]: ../../renderer/index.html
pub trait Renderer: crate::Renderer + Sized { pub trait Renderer: crate::Renderer + Sized {
type Style: Default;
/// Draws a [`Button`]. /// Draws a [`Button`].
/// ///
/// [`Button`]: struct.Button.html /// [`Button`]: struct.Button.html
@ -268,8 +258,7 @@ pub trait Renderer: crate::Renderer + Sized {
bounds: Rectangle, bounds: Rectangle,
cursor_position: Point, cursor_position: Point,
is_pressed: bool, is_pressed: bool,
background: Option<Background>, style: &Self::Style,
border_radius: u16,
content: Self::Output, content: Self::Output,
) -> Self::Output; ) -> Self::Output;
} }

View File

@ -174,7 +174,7 @@
//! [documentation]: https://docs.rs/iced //! [documentation]: https://docs.rs/iced
//! [examples]: https://github.com/hecrj/iced/tree/master/examples //! [examples]: https://github.com/hecrj/iced/tree/master/examples
//! [`Application`]: trait.Application.html //! [`Application`]: trait.Application.html
#![deny(missing_docs)] //#![deny(missing_docs)]
#![deny(missing_debug_implementations)] #![deny(missing_debug_implementations)]
#![deny(unused_results)] #![deny(unused_results)]
#![deny(unsafe_code)] #![deny(unsafe_code)]

View File

@ -22,23 +22,7 @@ pub mod widget {
//! //!
//! [`TextInput`]: text_input/struct.TextInput.html //! [`TextInput`]: text_input/struct.TextInput.html
//! [`text_input::State`]: text_input/struct.State.html //! [`text_input::State`]: text_input/struct.State.html
pub mod button { pub use iced_wgpu::button;
//! Allow your users to perform actions by pressing a button.
//!
//! A [`Button`] has some local [`State`].
//!
//! [`Button`]: type.Button.html
//! [`State`]: struct.State.html
/// A widget that produces a message when clicked.
///
/// This is an alias of an `iced_native` button with a default
/// `Renderer`.
pub type Button<'a, Message> =
iced_winit::Button<'a, Message, iced_wgpu::Renderer>;
pub use iced_winit::button::State;
}
pub mod scrollable { pub mod scrollable {
//! Navigate an endless amount of content with a scrollbar. //! Navigate an endless amount of content with a scrollbar.

View File

@ -19,11 +19,13 @@
//! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs //! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
//! [WebGPU API]: https://gpuweb.github.io/gpuweb/ //! [WebGPU API]: https://gpuweb.github.io/gpuweb/
//! [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph //! [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph
#![deny(missing_docs)] //#![deny(missing_docs)]
#![deny(missing_debug_implementations)] #![deny(missing_debug_implementations)]
#![deny(unused_results)] #![deny(unused_results)]
#![deny(unsafe_code)] #![deny(unsafe_code)]
#![deny(rust_2018_idioms)] #![deny(rust_2018_idioms)]
pub mod widget;
mod image; mod image;
mod primitive; mod primitive;
mod quad; mod quad;
@ -31,9 +33,11 @@ mod renderer;
mod text; mod text;
mod transformation; mod transformation;
pub(crate) use crate::image::Image;
pub(crate) use quad::Quad;
pub(crate) use transformation::Transformation;
pub use primitive::Primitive; pub use primitive::Primitive;
pub use renderer::{Renderer, Target}; pub use renderer::{Renderer, Target};
#[doc(no_inline)]
pub use widget::*;
pub(crate) use self::image::Image;
pub(crate) use quad::Quad;
pub(crate) use transformation::Transformation;

View File

@ -22,7 +22,7 @@ pub struct Renderer {
device: Device, device: Device,
queue: Queue, queue: Queue,
quad_pipeline: quad::Pipeline, quad_pipeline: quad::Pipeline,
image_pipeline: crate::image::Pipeline, image_pipeline: image::Pipeline,
text_pipeline: text::Pipeline, text_pipeline: text::Pipeline,
} }
@ -63,7 +63,7 @@ impl Renderer {
let text_pipeline = text::Pipeline::new(&mut device); let text_pipeline = text::Pipeline::new(&mut device);
let quad_pipeline = quad::Pipeline::new(&mut device); let quad_pipeline = quad::Pipeline::new(&mut device);
let image_pipeline = crate::image::Pipeline::new(&mut device); let image_pipeline = image::Pipeline::new(&mut device);
Self { Self {
device, device,

View File

@ -1,50 +1,50 @@
use crate::{Primitive, Renderer}; use crate::{button::StyleSheet, Primitive, Renderer};
use iced_native::{button, Background, MouseCursor, Point, Rectangle}; use iced_native::{Background, MouseCursor, Point, Rectangle};
impl iced_native::button::Renderer for Renderer {
type Style = Box<dyn StyleSheet>;
impl button::Renderer for Renderer {
fn draw( fn draw(
&mut self, &mut self,
bounds: Rectangle, bounds: Rectangle,
cursor_position: Point, cursor_position: Point,
is_pressed: bool, is_pressed: bool,
background: Option<Background>, style: &Box<dyn StyleSheet>,
border_radius: u16,
(content, _): Self::Output, (content, _): Self::Output,
) -> Self::Output { ) -> Self::Output {
let is_mouse_over = bounds.contains(cursor_position); let is_mouse_over = bounds.contains(cursor_position);
// TODO: Render proper shadows // TODO: Render proper shadows
// TODO: Make hovering and pressed styles configurable let styling = if is_mouse_over {
let shadow_offset = if is_mouse_over {
if is_pressed { if is_pressed {
0.0 style.pressed()
} else { } else {
2.0 style.hovered()
} }
} else { } else {
1.0 style.active()
}; };
( (
match background { match styling.background {
None => content, None => content,
Some(background) => Primitive::Group { Some(background) => Primitive::Group {
primitives: vec![ primitives: vec![
Primitive::Quad { Primitive::Quad {
bounds: Rectangle { bounds: Rectangle {
x: bounds.x + 1.0, x: bounds.x + 1.0,
y: bounds.y + shadow_offset, y: bounds.y + styling.shadow_offset,
..bounds ..bounds
}, },
background: Background::Color( background: Background::Color(
[0.0, 0.0, 0.0, 0.5].into(), [0.0, 0.0, 0.0, 0.5].into(),
), ),
border_radius, border_radius: styling.border_radius,
}, },
Primitive::Quad { Primitive::Quad {
bounds, bounds,
background, background,
border_radius, border_radius: styling.border_radius,
}, },
content, content,
], ],

View File

@ -0,0 +1 @@

1
wgpu/src/widget.rs Normal file
View File

@ -0,0 +1 @@
pub mod button;

97
wgpu/src/widget/button.rs Normal file
View File

@ -0,0 +1,97 @@
//! Allow your users to perform actions by pressing a button.
//!
//! A [`Button`] has some local [`State`].
//!
//! [`Button`]: type.Button.html
//! [`State`]: struct.State.html
use crate::Renderer;
use iced_native::Background;
pub use iced_native::button::State;
/// A widget that produces a message when clicked.
///
/// This is an alias of an `iced_native` button with an `iced_wgpu::Renderer`.
pub type Button<'a, Message> = iced_native::Button<'a, Message, Renderer>;
#[derive(Debug)]
pub struct Style {
pub shadow_offset: f32,
pub background: Option<Background>,
pub border_radius: u16,
}
pub trait StyleSheet {
fn active(&self) -> Style;
fn hovered(&self) -> Style {
let active = self.active();
Style {
shadow_offset: active.shadow_offset + 1.0,
..active
}
}
fn pressed(&self) -> Style {
Style {
shadow_offset: 0.0,
..self.active()
}
}
fn disabled(&self) -> Style {
self.active()
}
}
struct Default;
impl StyleSheet for Default {
fn active(&self) -> Style {
Style {
shadow_offset: 1.0,
background: Some(Background::Color([0.5, 0.5, 0.5].into())),
border_radius: 5,
}
}
fn hovered(&self) -> Style {
Style {
shadow_offset: 2.0,
background: Some(Background::Color([0.5, 0.5, 0.5].into())),
border_radius: 5,
}
}
fn pressed(&self) -> Style {
Style {
shadow_offset: 0.0,
background: Some(Background::Color([0.5, 0.5, 0.5].into())),
border_radius: 5,
}
}
fn disabled(&self) -> Style {
Style {
shadow_offset: 0.0,
background: Some(Background::Color([0.7, 0.7, 0.7].into())),
border_radius: 5,
}
}
}
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)
}
}