From f4a4845ddbdced81ae4ff60bfa19f0e602d84709 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 4 Mar 2024 20:42:37 +0100 Subject: Simplify theming for `Button` widget --- core/src/background.rs | 13 ++ core/src/color.rs | 8 + core/src/gradient.rs | 4 +- examples/editor/src/main.rs | 2 +- examples/game_of_life/src/main.rs | 6 +- examples/lazy/src/main.rs | 3 +- examples/pane_grid/src/main.rs | 11 +- examples/screenshot/src/main.rs | 6 +- examples/stopwatch/src/main.rs | 4 +- examples/todos/src/main.rs | 14 +- examples/tour/src/main.rs | 30 ++- style/src/button.rs | 78 -------- style/src/checkbox.rs | 10 +- style/src/theme.rs | 123 +----------- widget/src/button.rs | 382 +++++++++++++++++++++++--------------- widget/src/helpers.rs | 2 +- 16 files changed, 295 insertions(+), 401 deletions(-) diff --git a/core/src/background.rs b/core/src/background.rs index 347c52c0..2e28e560 100644 --- a/core/src/background.rs +++ b/core/src/background.rs @@ -11,6 +11,19 @@ pub enum Background { // TODO: Add image variant } +impl Background { + /// Increases the translucency of the [`Background`] + /// by the given factor. + pub fn transparentize(self, factor: f32) -> Self { + match self { + Self::Color(color) => Self::Color(color.transparentize(factor)), + Self::Gradient(gradient) => { + Self::Gradient(gradient.transparentize(factor)) + } + } + } +} + impl From for Background { fn from(color: Color) -> Self { Background::Color(color) diff --git a/core/src/color.rs b/core/src/color.rs index b8db322f..6526e220 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -151,6 +151,14 @@ impl Color { pub fn inverse(self) -> Color { Color::new(1.0f32 - self.r, 1.0f32 - self.g, 1.0f32 - self.b, self.a) } + + /// Transparentizes the [`Color`] by the given factor. + pub fn transparentize(self, factor: f32) -> Color { + Self { + a: self.a * factor, + ..self + } + } } impl From<[f32; 3]> for Color { diff --git a/core/src/gradient.rs b/core/src/gradient.rs index 4711b044..ecf7830f 100644 --- a/core/src/gradient.rs +++ b/core/src/gradient.rs @@ -13,11 +13,11 @@ pub enum Gradient { impl Gradient { /// Adjust the opacity of the gradient by a multiplier applied to each color stop. - pub fn mul_alpha(mut self, alpha_multiplier: f32) -> Self { + pub fn transparentize(mut self, factor: f32) -> Self { match &mut self { Gradient::Linear(linear) => { for stop in linear.stops.iter_mut().flatten() { - stop.color.a *= alpha_multiplier; + stop.color.a *= factor; } } } diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index 53c9cf7c..b5870e9e 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -290,7 +290,7 @@ fn action<'a, Message: Clone + 'a>( .style(theme::Container::Box) .into() } else { - action.style(theme::Button::Secondary).into() + action.style(button::secondary).into() } } diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 9cbb7fff..b362381c 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -6,7 +6,6 @@ use grid::Grid; use preset::Preset; use iced::executor; -use iced::theme::{self, Theme}; use iced::time; use iced::widget::{ button, checkbox, column, container, pick_list, row, slider, text, @@ -14,6 +13,7 @@ use iced::widget::{ use iced::window; use iced::{ Alignment, Application, Command, Element, Length, Settings, Subscription, + Theme, }; use std::time::Duration; @@ -171,7 +171,7 @@ fn view_controls<'a>( .on_press(Message::TogglePlayback), button("Next") .on_press(Message::Next) - .style(theme::Button::Secondary), + .style(button::secondary), ] .spacing(10); @@ -195,7 +195,7 @@ fn view_controls<'a>( .text_size(16), button("Clear") .on_press(Message::Clear) - .style(theme::Button::Destructive), + .style(button::destructive), ] .padding(10) .spacing(20) diff --git a/examples/lazy/src/main.rs b/examples/lazy/src/main.rs index 37b5d52c..1c5f59d5 100644 --- a/examples/lazy/src/main.rs +++ b/examples/lazy/src/main.rs @@ -1,4 +1,3 @@ -use iced::theme; use iced::widget::{ button, column, horizontal_space, lazy, pick_list, row, scrollable, text, text_input, @@ -181,7 +180,7 @@ impl Sandbox for App { column(items.into_iter().map(|item| { let button = button("Delete") .on_press(Message::DeleteItem(item.clone())) - .style(theme::Button::Destructive); + .style(button::destructive); row![ text(&item.name).color(item.color), diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index c4bedccc..2bed5a03 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -1,13 +1,13 @@ use iced::alignment::{self, Alignment}; use iced::executor; use iced::keyboard; -use iced::theme::{self, Theme}; use iced::widget::pane_grid::{self, PaneGrid}; use iced::widget::{ button, column, container, responsive, row, scrollable, text, }; use iced::{ Application, Color, Command, Element, Length, Settings, Size, Subscription, + Theme, }; pub fn main() -> iced::Result { @@ -287,10 +287,7 @@ fn view_content<'a>( ) ] .push_maybe(if total_panes > 1 && !is_pinned { - Some( - button("Close", Message::Close(pane)) - .style(theme::Button::Destructive), - ) + Some(button("Close", Message::Close(pane)).style(button::destructive)) } else { None }) @@ -327,7 +324,7 @@ fn view_controls<'a>( Some( button(text(content).size(14)) - .style(theme::Button::Secondary) + .style(button::secondary) .padding(3) .on_press(message), ) @@ -336,7 +333,7 @@ fn view_controls<'a>( }); let close = button(text("Close").size(14)) - .style(theme::Button::Destructive) + .style(button::destructive) .padding(3) .on_press_maybe(if total_panes > 1 && !is_pinned { Some(Message::Close(pane)) diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index 79749956..dc4684d4 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -216,9 +216,9 @@ impl Application for Example { ) } else { button(centered_text("Saving...")) - .style(theme::Button::Secondary) + .style(button::secondary) } - .style(theme::Button::Secondary) + .style(button::secondary) .padding([10, 20, 10, 20]) .width(Length::Fill) ] @@ -227,7 +227,7 @@ impl Application for Example { crop_controls, button(centered_text("Crop")) .on_press(Message::Crop) - .style(theme::Button::Destructive) + .style(button::destructive) .padding([10, 20, 10, 20]) .width(Length::Fill), ] diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs index 8a0674c1..7a097e90 100644 --- a/examples/stopwatch/src/main.rs +++ b/examples/stopwatch/src/main.rs @@ -1,11 +1,11 @@ use iced::alignment; use iced::executor; use iced::keyboard; -use iced::theme::{self, Theme}; use iced::time; use iced::widget::{button, column, container, row, text}; use iced::{ Alignment, Application, Command, Element, Length, Settings, Subscription, + Theme, }; use std::time::{Duration, Instant}; @@ -136,7 +136,7 @@ impl Application for Stopwatch { }; let reset_button = button("Reset") - .style(theme::Button::Destructive) + .style(button::destructive) .on_press(Message::Reset); let controls = row![toggle_button, reset_button].spacing(20); diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index b1aeb4a7..b3b5d87a 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -1,14 +1,14 @@ use iced::alignment::{self, Alignment}; use iced::font::{self, Font}; use iced::keyboard; -use iced::theme::{self, Theme}; use iced::widget::{ self, button, checkbox, column, container, keyed_column, row, scrollable, text, text_input, Text, }; use iced::window; -use iced::{Application, Element}; -use iced::{Command, Length, Settings, Size, Subscription}; +use iced::{ + Application, Command, Element, Length, Settings, Size, Subscription, Theme, +}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; @@ -362,7 +362,7 @@ impl Task { button(edit_icon()) .on_press(TaskMessage::Edit) .padding(10) - .style(theme::Button::Text), + .style(button::text), ] .spacing(20) .align_items(Alignment::Center) @@ -385,7 +385,7 @@ impl Task { ) .on_press(TaskMessage::Delete) .padding(10) - .style(theme::Button::Destructive) + .style(button::destructive) ] .spacing(20) .align_items(Alignment::Center) @@ -402,9 +402,9 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element { let label = text(label); let button = button(label).style(if filter == current_filter { - theme::Button::Primary + button::primary } else { - theme::Button::Text + button::text }); button.on_press(Message::FilterChanged(filter)).padding(8) diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index 52e1bbb7..f5791ad7 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -1,7 +1,6 @@ use iced::alignment::{self, Alignment}; -use iced::theme; use iced::widget::{ - checkbox, column, container, horizontal_space, image, radio, row, + button, checkbox, column, container, horizontal_space, image, radio, row, scrollable, slider, text, text_input, toggler, vertical_space, }; use iced::widget::{Button, Column, Container, Slider}; @@ -56,18 +55,17 @@ impl Sandbox for Tour { fn view(&self) -> Element { let Tour { steps, .. } = self; - let controls = row![] - .push_maybe(steps.has_previous().then(|| { - button("Back") - .on_press(Message::BackPressed) - .style(theme::Button::Secondary) - })) - .push(horizontal_space()) - .push_maybe( - steps - .can_continue() - .then(|| button("Next").on_press(Message::NextPressed)), - ); + let controls = + row![] + .push_maybe(steps.has_previous().then(|| { + padded_button("Back") + .on_press(Message::BackPressed) + .style(button::secondary) + })) + .push(horizontal_space()) + .push_maybe(steps.can_continue().then(|| { + padded_button("Next").on_press(Message::NextPressed) + })); let content: Element<_> = column![ steps.view(self.debug).map(Message::StepMessage), @@ -676,8 +674,8 @@ fn ferris<'a>( .center_x() } -fn button<'a, Message: Clone>(label: &str) -> Button<'a, Message> { - iced::widget::button(text(label)).padding([12, 24]) +fn padded_button<'a, Message: Clone>(label: &str) -> Button<'a, Message> { + button(text(label)).padding([12, 24]) } fn color_slider<'a>( diff --git a/style/src/button.rs b/style/src/button.rs index 0d7a668a..8b137891 100644 --- a/style/src/button.rs +++ b/style/src/button.rs @@ -1,79 +1 @@ -//! Change the apperance of a button. -use iced_core::{Background, Border, Color, Shadow, Vector}; -/// The appearance of a button. -#[derive(Debug, Clone, Copy)] -pub struct Appearance { - /// The amount of offset to apply to the shadow of the button. - pub shadow_offset: Vector, - /// The [`Background`] of the button. - pub background: Option, - /// The text [`Color`] of the button. - pub text_color: Color, - /// The [`Border`] of the buton. - pub border: Border, - /// The [`Shadow`] of the butoon. - pub shadow: Shadow, -} - -impl std::default::Default for Appearance { - fn default() -> Self { - Self { - shadow_offset: Vector::default(), - background: None, - text_color: Color::BLACK, - border: Border::default(), - shadow: Shadow::default(), - } - } -} - -/// A set of rules that dictate the style of a button. -pub trait StyleSheet { - /// The supported style of the [`StyleSheet`]. - type Style: Default; - - /// Produces the active [`Appearance`] of a button. - fn active(&self, style: &Self::Style) -> Appearance; - - /// Produces the hovered [`Appearance`] of a button. - fn hovered(&self, style: &Self::Style) -> Appearance { - let active = self.active(style); - - Appearance { - shadow_offset: active.shadow_offset + Vector::new(0.0, 1.0), - ..active - } - } - - /// Produces the pressed [`Appearance`] of a button. - fn pressed(&self, style: &Self::Style) -> Appearance { - Appearance { - shadow_offset: Vector::default(), - ..self.active(style) - } - } - - /// Produces the disabled [`Appearance`] of a button. - fn disabled(&self, style: &Self::Style) -> Appearance { - let active = self.active(style); - - Appearance { - shadow_offset: Vector::default(), - background: active.background.map(|background| match background { - Background::Color(color) => Background::Color(Color { - a: color.a * 0.5, - ..color - }), - Background::Gradient(gradient) => { - Background::Gradient(gradient.mul_alpha(0.5)) - } - }), - text_color: Color { - a: active.text_color.a * 0.5, - ..active.text_color - }, - ..active - } - } -} diff --git a/style/src/checkbox.rs b/style/src/checkbox.rs index 77093f69..5e1c8374 100644 --- a/style/src/checkbox.rs +++ b/style/src/checkbox.rs @@ -30,15 +30,7 @@ pub trait StyleSheet { let active = self.active(style, is_checked); Appearance { - background: match active.background { - Background::Color(color) => Background::Color(Color { - a: color.a * 0.5, - ..color - }), - Background::Gradient(gradient) => { - Background::Gradient(gradient.mul_alpha(0.5)) - } - }, + background: active.background.transparentize(0.5), ..active } } diff --git a/style/src/theme.rs b/style/src/theme.rs index 43e7cafd..f967aebc 100644 --- a/style/src/theme.rs +++ b/style/src/theme.rs @@ -4,7 +4,6 @@ pub mod palette; pub use palette::Palette; use crate::application; -use crate::button; use crate::checkbox; use crate::container; use crate::core::widget::text; @@ -22,7 +21,7 @@ use crate::text_editor; use crate::text_input; use crate::toggler; -use crate::core::{Background, Border, Color, Shadow, Vector}; +use crate::core::{Background, Border, Color, Shadow}; use std::fmt; use std::rc::Rc; @@ -285,126 +284,6 @@ impl application::Appearance> application::StyleSheet for T { } } -/// The style of a button. -#[derive(Default)] -pub enum Button { - /// The primary style. - #[default] - Primary, - /// The secondary style. - Secondary, - /// The positive style. - Positive, - /// The destructive style. - Destructive, - /// The text style. - /// - /// Useful for links! - Text, - /// A custom style. - Custom(Box>), -} - -impl Button { - /// Creates a custom [`Button`] style variant. - pub fn custom( - style_sheet: impl button::StyleSheet