diff options
Diffstat (limited to 'style')
| -rw-r--r-- | style/Cargo.toml | 11 | ||||
| -rw-r--r-- | style/src/application.rs | 13 | ||||
| -rw-r--r-- | style/src/button.rs | 56 | ||||
| -rw-r--r-- | style/src/checkbox.rs | 43 | ||||
| -rw-r--r-- | style/src/container.rs | 39 | ||||
| -rw-r--r-- | style/src/lib.rs | 15 | ||||
| -rw-r--r-- | style/src/menu.rs | 18 | ||||
| -rw-r--r-- | style/src/pane_grid.rs | 33 | ||||
| -rw-r--r-- | style/src/pick_list.rs | 65 | ||||
| -rw-r--r-- | style/src/progress_bar.rs | 33 | ||||
| -rw-r--r-- | style/src/radio.rs | 42 | ||||
| -rw-r--r-- | style/src/rule.rs | 74 | ||||
| -rw-r--r-- | style/src/scrollable.rs | 51 | ||||
| -rw-r--r-- | style/src/slider.rs | 68 | ||||
| -rw-r--r-- | style/src/text.rs | 12 | ||||
| -rw-r--r-- | style/src/text_input.rs | 76 | ||||
| -rw-r--r-- | style/src/theme.rs | 719 | ||||
| -rw-r--r-- | style/src/theme/palette.rs | 277 | ||||
| -rw-r--r-- | style/src/toggler.rs | 45 | 
19 files changed, 1145 insertions, 545 deletions
| diff --git a/style/Cargo.toml b/style/Cargo.toml index 047c905d..cf9d328b 100644 --- a/style/Cargo.toml +++ b/style/Cargo.toml @@ -1,6 +1,6 @@  [package]  name = "iced_style" -version = "0.3.0" +version = "0.4.0"  authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]  edition = "2021"  description = "The default set of styles of Iced" @@ -11,5 +11,12 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"]  categories = ["gui"]  [dependencies.iced_core] -version = "0.4" +version = "0.5"  path = "../core" +features = ["palette"] + +[dependencies.palette] +version = "0.6" + +[dependencies.lazy_static] +version = "1.4" diff --git a/style/src/application.rs b/style/src/application.rs new file mode 100644 index 00000000..d48c6a34 --- /dev/null +++ b/style/src/application.rs @@ -0,0 +1,13 @@ +use iced_core::Color; + +pub trait StyleSheet { +    type Style: Default + Copy; + +    fn appearance(&self, style: Self::Style) -> Appearance; +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Appearance { +    pub background_color: Color, +    pub text_color: Color, +} diff --git a/style/src/button.rs b/style/src/button.rs index de2de4f4..c63a6b71 100644 --- a/style/src/button.rs +++ b/style/src/button.rs @@ -3,7 +3,7 @@ use iced_core::{Background, Color, Vector};  /// The appearance of a button.  #[derive(Debug, Clone, Copy)] -pub struct Style { +pub struct Appearance {      pub shadow_offset: Vector,      pub background: Option<Background>,      pub border_radius: f32, @@ -12,7 +12,7 @@ pub struct Style {      pub text_color: Color,  } -impl std::default::Default for Style { +impl std::default::Default for Appearance {      fn default() -> Self {          Self {              shadow_offset: Vector::default(), @@ -27,28 +27,30 @@ impl std::default::Default for Style {  /// A set of rules that dictate the style of a button.  pub trait StyleSheet { -    fn active(&self) -> Style; +    type Style: Default + Copy; -    fn hovered(&self) -> Style { -        let active = self.active(); +    fn active(&self, style: Self::Style) -> Appearance; -        Style { +    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          }      } -    fn pressed(&self) -> Style { -        Style { +    fn pressed(&self, style: Self::Style) -> Appearance { +        Appearance {              shadow_offset: Vector::default(), -            ..self.active() +            ..self.active(style)          }      } -    fn disabled(&self) -> Style { -        let active = self.active(); +    fn disabled(&self, style: Self::Style) -> Appearance { +        let active = self.active(style); -        Style { +        Appearance {              shadow_offset: Vector::default(),              background: active.background.map(|background| match background {                  Background::Color(color) => Background::Color(Color { @@ -64,33 +66,3 @@ pub trait StyleSheet {          }      }  } - -struct Default; - -impl StyleSheet for Default { -    fn active(&self) -> Style { -        Style { -            shadow_offset: Vector::new(0.0, 0.0), -            background: Some(Background::Color([0.87, 0.87, 0.87].into())), -            border_radius: 2.0, -            border_width: 1.0, -            border_color: [0.7, 0.7, 0.7].into(), -            text_color: Color::BLACK, -        } -    } -} - -impl<'a> std::default::Default for Box<dyn StyleSheet + 'a> { -    fn default() -> Self { -        Box::new(Default) -    } -} - -impl<'a, T> From<T> for Box<dyn StyleSheet + 'a> -where -    T: StyleSheet + 'a, -{ -    fn from(style_sheet: T) -> Self { -        Box::new(style_sheet) -    } -} diff --git a/style/src/checkbox.rs b/style/src/checkbox.rs index de52e548..ba54b0a2 100644 --- a/style/src/checkbox.rs +++ b/style/src/checkbox.rs @@ -3,7 +3,7 @@ use iced_core::{Background, Color};  /// The appearance of a checkbox.  #[derive(Debug, Clone, Copy)] -pub struct Style { +pub struct Appearance {      pub background: Background,      pub checkmark_color: Color,      pub border_radius: f32, @@ -14,44 +14,9 @@ pub struct Style {  /// A set of rules that dictate the style of a checkbox.  pub trait StyleSheet { -    fn active(&self, is_checked: bool) -> Style; +    type Style: Default + Copy; -    fn hovered(&self, is_checked: bool) -> Style; -} - -struct Default; - -impl StyleSheet for Default { -    fn active(&self, _is_checked: bool) -> Style { -        Style { -            background: Background::Color(Color::from_rgb(0.95, 0.95, 0.95)), -            checkmark_color: Color::from_rgb(0.3, 0.3, 0.3), -            border_radius: 5.0, -            border_width: 1.0, -            border_color: Color::from_rgb(0.6, 0.6, 0.6), -            text_color: None, -        } -    } - -    fn hovered(&self, is_checked: bool) -> Style { -        Style { -            background: Background::Color(Color::from_rgb(0.90, 0.90, 0.90)), -            ..self.active(is_checked) -        } -    } -} - -impl<'a> std::default::Default for Box<dyn StyleSheet + 'a> { -    fn default() -> Self { -        Box::new(Default) -    } -} +    fn active(&self, style: Self::Style, is_checked: bool) -> Appearance; -impl<'a, T> From<T> for Box<dyn StyleSheet + 'a> -where -    T: StyleSheet + 'a, -{ -    fn from(style_sheet: T) -> Self { -        Box::new(style_sheet) -    } +    fn hovered(&self, style: Self::Style, is_checked: bool) -> Appearance;  } diff --git a/style/src/container.rs b/style/src/container.rs index 2f411611..184310fa 100644 --- a/style/src/container.rs +++ b/style/src/container.rs @@ -3,7 +3,7 @@ use iced_core::{Background, Color};  /// The appearance of a container.  #[derive(Debug, Clone, Copy)] -pub struct Style { +pub struct Appearance {      pub text_color: Option<Color>,      pub background: Option<Background>,      pub border_radius: f32, @@ -11,7 +11,7 @@ pub struct Style {      pub border_color: Color,  } -impl std::default::Default for Style { +impl std::default::Default for Appearance {      fn default() -> Self {          Self {              text_color: None, @@ -23,37 +23,10 @@ impl std::default::Default for Style {      }  } -/// A set of rules that dictate the style of a container. +/// A set of rules that dictate the [`Appearance`] of a container.  pub trait StyleSheet { -    /// Produces the style of a container. -    fn style(&self) -> Style; -} - -struct Default; - -impl StyleSheet for Default { -    fn style(&self) -> Style { -        Style { -            text_color: None, -            background: None, -            border_radius: 0.0, -            border_width: 0.0, -            border_color: Color::TRANSPARENT, -        } -    } -} - -impl<'a> std::default::Default for Box<dyn StyleSheet + 'a> { -    fn default() -> Self { -        Box::new(Default) -    } -} +    type Style: Default + Copy; -impl<'a, T> From<T> for Box<dyn StyleSheet + 'a> -where -    T: StyleSheet + 'a, -{ -    fn from(style_sheet: T) -> Self { -        Box::new(style_sheet) -    } +    /// Produces the [`Appearance`] of a container. +    fn appearance(&self, style: Self::Style) -> Appearance;  } diff --git a/style/src/lib.rs b/style/src/lib.rs index e4556f67..0dde9582 100644 --- a/style/src/lib.rs +++ b/style/src/lib.rs @@ -7,8 +7,19 @@  #![doc(      html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"  )] +#![deny( +    unused_results, +    clippy::extra_unused_lifetimes, +    clippy::from_over_into, +    clippy::needless_borrow, +    clippy::new_without_default, +    clippy::useless_conversion +)] +#![forbid(unsafe_code, rust_2018_idioms)] +#![allow(clippy::inherent_to_string, clippy::type_complexity)]  pub use iced_core::{Background, Color}; +pub mod application;  pub mod button;  pub mod checkbox;  pub mod container; @@ -20,5 +31,9 @@ pub mod radio;  pub mod rule;  pub mod scrollable;  pub mod slider; +pub mod text;  pub mod text_input; +pub mod theme;  pub mod toggler; + +pub use theme::Theme; diff --git a/style/src/menu.rs b/style/src/menu.rs index 90985b8f..6ef3e2a2 100644 --- a/style/src/menu.rs +++ b/style/src/menu.rs @@ -2,24 +2,18 @@ use iced_core::{Background, Color};  /// The appearance of a menu.  #[derive(Debug, Clone, Copy)] -pub struct Style { +pub struct Appearance {      pub text_color: Color,      pub background: Background,      pub border_width: f32, +    pub border_radius: f32,      pub border_color: Color,      pub selected_text_color: Color,      pub selected_background: Background,  } -impl std::default::Default for Style { -    fn default() -> Self { -        Self { -            text_color: Color::BLACK, -            background: Background::Color([0.87, 0.87, 0.87].into()), -            border_width: 1.0, -            border_color: [0.7, 0.7, 0.7].into(), -            selected_text_color: Color::WHITE, -            selected_background: Background::Color([0.4, 0.4, 1.0].into()), -        } -    } +pub trait StyleSheet { +    type Style: Default + Copy; + +    fn appearance(&self, style: Self::Style) -> Appearance;  } diff --git a/style/src/pane_grid.rs b/style/src/pane_grid.rs index a12ac3f5..5bae353f 100644 --- a/style/src/pane_grid.rs +++ b/style/src/pane_grid.rs @@ -4,11 +4,13 @@ use iced_core::Color;  /// A set of rules that dictate the style of a container.  pub trait StyleSheet { +    type Style: Default + Copy; +      /// The [`Line`] to draw when a split is picked. -    fn picked_split(&self) -> Option<Line>; +    fn picked_split(&self, style: Self::Style) -> Option<Line>;      /// The [`Line`] to draw when a split is hovered. -    fn hovered_split(&self) -> Option<Line>; +    fn hovered_split(&self, style: Self::Style) -> Option<Line>;  }  /// A line. @@ -22,30 +24,3 @@ pub struct Line {      /// The width of the [`Line`].      pub width: f32,  } - -struct Default; - -impl StyleSheet for Default { -    fn picked_split(&self) -> Option<Line> { -        None -    } - -    fn hovered_split(&self) -> Option<Line> { -        None -    } -} - -impl<'a> std::default::Default for Box<dyn StyleSheet + 'a> { -    fn default() -> Self { -        Box::new(Default) -    } -} - -impl<'a, T> From<T> for Box<dyn StyleSheet + 'a> -where -    T: StyleSheet + 'a, -{ -    fn from(style_sheet: T) -> Self { -        Box::new(style_sheet) -    } -} diff --git a/style/src/pick_list.rs b/style/src/pick_list.rs index ad96b201..2bafe932 100644 --- a/style/src/pick_list.rs +++ b/style/src/pick_list.rs @@ -1,9 +1,12 @@ -use crate::menu;  use iced_core::{Background, Color}; +use crate::container; +use crate::menu; +use crate::scrollable; +  /// The appearance of a pick list.  #[derive(Debug, Clone, Copy)] -pub struct Style { +pub struct Appearance {      pub text_color: Color,      pub placeholder_color: Color,      pub background: Background, @@ -13,60 +16,14 @@ pub struct Style {      pub icon_size: f32,  } -impl std::default::Default for Style { -    fn default() -> Self { -        Self { -            text_color: Color::BLACK, -            placeholder_color: [0.4, 0.4, 0.4].into(), -            background: Background::Color([0.87, 0.87, 0.87].into()), -            border_radius: 0.0, -            border_width: 1.0, -            border_color: [0.7, 0.7, 0.7].into(), -            icon_size: 0.7, -        } -    } -} -  /// A set of rules that dictate the style of a container. -pub trait StyleSheet { -    fn menu(&self) -> menu::Style; +pub trait StyleSheet: +    container::StyleSheet + menu::StyleSheet + scrollable::StyleSheet +{ +    type Style: Default + Copy + Into<<Self as menu::StyleSheet>::Style>; -    fn active(&self) -> Style; +    fn active(&self, style: <Self as StyleSheet>::Style) -> Appearance;      /// Produces the style of a container. -    fn hovered(&self) -> Style; -} - -struct Default; - -impl StyleSheet for Default { -    fn menu(&self) -> menu::Style { -        menu::Style::default() -    } - -    fn active(&self) -> Style { -        Style::default() -    } - -    fn hovered(&self) -> Style { -        Style { -            border_color: Color::BLACK, -            ..self.active() -        } -    } -} - -impl<'a> std::default::Default for Box<dyn StyleSheet + 'a> { -    fn default() -> Self { -        Box::new(Default) -    } -} - -impl<'a, T> From<T> for Box<dyn StyleSheet + 'a> -where -    T: 'a + StyleSheet, -{ -    fn from(style: T) -> Self { -        Box::new(style) -    } +    fn hovered(&self, style: <Self as StyleSheet>::Style) -> Appearance;  } diff --git a/style/src/progress_bar.rs b/style/src/progress_bar.rs index a0195c7a..768e7c9c 100644 --- a/style/src/progress_bar.rs +++ b/style/src/progress_bar.rs @@ -1,9 +1,9 @@  //! Provide progress feedback to your users. -use iced_core::{Background, Color}; +use iced_core::Background;  /// The appearance of a progress bar.  #[derive(Debug, Clone, Copy)] -pub struct Style { +pub struct Appearance {      pub background: Background,      pub bar: Background,      pub border_radius: f32, @@ -11,32 +11,7 @@ pub struct Style {  /// A set of rules that dictate the style of a progress bar.  pub trait StyleSheet { -    fn style(&self) -> Style; -} - -struct Default; - -impl StyleSheet for Default { -    fn style(&self) -> Style { -        Style { -            background: Background::Color(Color::from_rgb(0.6, 0.6, 0.6)), -            bar: Background::Color(Color::from_rgb(0.3, 0.9, 0.3)), -            border_radius: 5.0, -        } -    } -} - -impl<'a> std::default::Default for Box<dyn StyleSheet + 'a> { -    fn default() -> Self { -        Box::new(Default) -    } -} +    type Style: Default + Copy; -impl<'a, T> From<T> for Box<dyn StyleSheet + 'a> -where -    T: 'a + StyleSheet, -{ -    fn from(style: T) -> Self { -        Box::new(style) -    } +    fn appearance(&self, style: Self::Style) -> Appearance;  } diff --git a/style/src/radio.rs b/style/src/radio.rs index dab76ad8..a4d4a83b 100644 --- a/style/src/radio.rs +++ b/style/src/radio.rs @@ -3,7 +3,7 @@ use iced_core::{Background, Color};  /// The appearance of a radio button.  #[derive(Debug, Clone, Copy)] -pub struct Style { +pub struct Appearance {      pub background: Background,      pub dot_color: Color,      pub border_width: f32, @@ -13,43 +13,9 @@ pub struct Style {  /// A set of rules that dictate the style of a radio button.  pub trait StyleSheet { -    fn active(&self) -> Style; +    type Style: Default + Copy; -    fn hovered(&self) -> Style; -} - -struct Default; - -impl StyleSheet for Default { -    fn active(&self) -> Style { -        Style { -            background: Background::Color(Color::from_rgb(0.95, 0.95, 0.95)), -            dot_color: Color::from_rgb(0.3, 0.3, 0.3), -            border_width: 1.0, -            border_color: Color::from_rgb(0.6, 0.6, 0.6), -            text_color: None, -        } -    } - -    fn hovered(&self) -> Style { -        Style { -            background: Background::Color(Color::from_rgb(0.90, 0.90, 0.90)), -            ..self.active() -        } -    } -} - -impl<'a> std::default::Default for Box<dyn StyleSheet + 'a> { -    fn default() -> Self { -        Box::new(Default) -    } -} +    fn active(&self, style: Self::Style) -> Appearance; -impl<'a, T> From<T> for Box<dyn StyleSheet + 'a> -where -    T: StyleSheet + 'a, -{ -    fn from(style_sheet: T) -> Self { -        Box::new(style_sheet) -    } +    fn hovered(&self, style: Self::Style) -> Appearance;  } diff --git a/style/src/rule.rs b/style/src/rule.rs index 12a40f7d..af334912 100644 --- a/style/src/rule.rs +++ b/style/src/rule.rs @@ -1,6 +1,27 @@  //! Display a horizontal or vertical rule for dividing content.  use iced_core::Color; +/// The appearance of a rule. +#[derive(Debug, Clone, Copy)] +pub struct Appearance { +    /// The color of the rule. +    pub color: Color, +    /// The width (thickness) of the rule line. +    pub width: u16, +    /// The radius of the line corners. +    pub radius: f32, +    /// The [`FillMode`] of the rule. +    pub fill_mode: FillMode, +} + +/// A set of rules that dictate the style of a rule. +pub trait StyleSheet { +    type Style: Default + Copy; + +    /// Produces the style of a rule. +    fn style(&self, style: Self::Style) -> Appearance; +} +  /// The fill mode of a rule.  #[derive(Debug, Clone, Copy)]  pub enum FillMode { @@ -64,56 +85,3 @@ impl FillMode {          }      }  } - -/// The appearance of a rule. -#[derive(Debug, Clone, Copy)] -pub struct Style { -    /// The color of the rule. -    pub color: Color, -    /// The width (thickness) of the rule line. -    pub width: u16, -    /// The radius of the line corners. -    pub radius: f32, -    /// The [`FillMode`] of the rule. -    pub fill_mode: FillMode, -} - -impl std::default::Default for Style { -    fn default() -> Self { -        Style { -            color: [0.6, 0.6, 0.6, 0.6].into(), -            width: 1, -            radius: 0.0, -            fill_mode: FillMode::Full, -        } -    } -} - -/// A set of rules that dictate the style of a rule. -pub trait StyleSheet { -    /// Produces the style of a rule. -    fn style(&self) -> Style; -} - -struct Default; - -impl StyleSheet for Default { -    fn style(&self) -> Style { -        Style::default() -    } -} - -impl<'a> std::default::Default for Box<dyn StyleSheet + 'a> { -    fn default() -> Self { -        Box::new(Default) -    } -} - -impl<'a, T> From<T> for Box<dyn StyleSheet + 'a> -where -    T: 'a + StyleSheet, -{ -    fn from(style: T) -> Self { -        Box::new(style) -    } -} diff --git a/style/src/scrollable.rs b/style/src/scrollable.rs index 748ba888..8da7409c 100644 --- a/style/src/scrollable.rs +++ b/style/src/scrollable.rs @@ -22,55 +22,16 @@ pub struct Scroller {  /// A set of rules that dictate the style of a scrollable.  pub trait StyleSheet { +    type Style: Default + Copy; +      /// Produces the style of an active scrollbar. -    fn active(&self) -> Scrollbar; +    fn active(&self, style: Self::Style) -> Scrollbar;      /// Produces the style of an hovered scrollbar. -    fn hovered(&self) -> Scrollbar; +    fn hovered(&self, style: Self::Style) -> Scrollbar;      /// Produces the style of a scrollbar that is being dragged. -    fn dragging(&self) -> Scrollbar { -        self.hovered() -    } -} - -struct Default; - -impl StyleSheet for Default { -    fn active(&self) -> Scrollbar { -        Scrollbar { -            background: None, -            border_radius: 5.0, -            border_width: 0.0, -            border_color: Color::TRANSPARENT, -            scroller: Scroller { -                color: [0.0, 0.0, 0.0, 0.7].into(), -                border_radius: 5.0, -                border_width: 0.0, -                border_color: Color::TRANSPARENT, -            }, -        } -    } - -    fn hovered(&self) -> Scrollbar { -        Scrollbar { -            background: Some(Background::Color([0.0, 0.0, 0.0, 0.3].into())), -            ..self.active() -        } -    } -} - -impl<'a> std::default::Default for Box<dyn StyleSheet + 'a> { -    fn default() -> Self { -        Box::new(Default) -    } -} - -impl<'a, T> From<T> for Box<dyn StyleSheet + 'a> -where -    T: StyleSheet + 'a, -{ -    fn from(style_sheet: T) -> Self { -        Box::new(style_sheet) +    fn dragging(&self, style: Self::Style) -> Scrollbar { +        self.hovered(style)      }  } diff --git a/style/src/slider.rs b/style/src/slider.rs index 1bb28b09..0ff0449b 100644 --- a/style/src/slider.rs +++ b/style/src/slider.rs @@ -3,7 +3,7 @@ use iced_core::Color;  /// The appearance of a slider.  #[derive(Debug, Clone, Copy)] -pub struct Style { +pub struct Appearance {      pub rail_colors: (Color, Color),      pub handle: Handle,  } @@ -26,70 +26,14 @@ pub enum HandleShape {  /// A set of rules that dictate the style of a slider.  pub trait StyleSheet { +    type Style: Default + Copy; +      /// Produces the style of an active slider. -    fn active(&self) -> Style; +    fn active(&self, style: Self::Style) -> Appearance;      /// Produces the style of an hovered slider. -    fn hovered(&self) -> Style; +    fn hovered(&self, style: Self::Style) -> Appearance;      /// 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.0, -                }, -                color: Color::from_rgb(0.95, 0.95, 0.95), -                border_color: Color::from_rgb(0.6, 0.6, 0.6), -                border_width: 1.0, -            }, -        } -    } - -    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<'a> std::default::Default for Box<dyn StyleSheet + 'a> { -    fn default() -> Self { -        Box::new(Default) -    } -} - -impl<'a, T> From<T> for Box<dyn StyleSheet + 'a> -where -    T: StyleSheet + 'a, -{ -    fn from(style_sheet: T) -> Self { -        Box::new(style_sheet) -    } +    fn dragging(&self, style: Self::Style) -> Appearance;  } diff --git a/style/src/text.rs b/style/src/text.rs new file mode 100644 index 00000000..6e3aeef8 --- /dev/null +++ b/style/src/text.rs @@ -0,0 +1,12 @@ +use iced_core::Color; + +pub trait StyleSheet { +    type Style: Default + Copy; + +    fn appearance(&self, style: Self::Style) -> Appearance; +} + +#[derive(Debug, Clone, Copy, Default)] +pub struct Appearance { +    pub color: Option<Color>, +} diff --git a/style/src/text_input.rs b/style/src/text_input.rs index 3d5817cc..af86617b 100644 --- a/style/src/text_input.rs +++ b/style/src/text_input.rs @@ -3,87 +3,31 @@ use iced_core::{Background, Color};  /// The appearance of a text input.  #[derive(Debug, Clone, Copy)] -pub struct Style { +pub struct Appearance {      pub background: Background,      pub border_radius: f32,      pub border_width: f32,      pub border_color: Color,  } -impl std::default::Default for Style { -    fn default() -> Self { -        Self { -            background: Background::Color(Color::WHITE), -            border_radius: 0.0, -            border_width: 0.0, -            border_color: Color::TRANSPARENT, -        } -    } -} -  /// A set of rules that dictate the style of a text input.  pub trait StyleSheet { +    type Style: Default + Copy; +      /// Produces the style of an active text input. -    fn active(&self) -> Style; +    fn active(&self, style: Self::Style) -> Appearance;      /// Produces the style of a focused text input. -    fn focused(&self) -> Style; +    fn focused(&self, style: Self::Style) -> Appearance; -    fn placeholder_color(&self) -> Color; +    fn placeholder_color(&self, style: Self::Style) -> Color; -    fn value_color(&self) -> Color; +    fn value_color(&self, style: Self::Style) -> Color; -    fn selection_color(&self) -> Color; +    fn selection_color(&self, style: Self::Style) -> Color;      /// Produces the style of an hovered text input. -    fn hovered(&self) -> Style { -        self.focused() -    } -} - -struct Default; - -impl StyleSheet for Default { -    fn active(&self) -> Style { -        Style { -            background: Background::Color(Color::WHITE), -            border_radius: 5.0, -            border_width: 1.0, -            border_color: Color::from_rgb(0.7, 0.7, 0.7), -        } -    } - -    fn focused(&self) -> Style { -        Style { -            border_color: Color::from_rgb(0.5, 0.5, 0.5), -            ..self.active() -        } -    } - -    fn placeholder_color(&self) -> Color { -        Color::from_rgb(0.7, 0.7, 0.7) -    } - -    fn value_color(&self) -> Color { -        Color::from_rgb(0.3, 0.3, 0.3) -    } - -    fn selection_color(&self) -> Color { -        Color::from_rgb(0.8, 0.8, 1.0) -    } -} - -impl<'a> std::default::Default for Box<dyn StyleSheet + 'a> { -    fn default() -> Self { -        Box::new(Default) -    } -} - -impl<'a, T> From<T> for Box<dyn StyleSheet + 'a> -where -    T: StyleSheet + 'a, -{ -    fn from(style_sheet: T) -> Self { -        Box::new(style_sheet) +    fn hovered(&self, style: Self::Style) -> Appearance { +        self.focused(style)      }  } diff --git a/style/src/theme.rs b/style/src/theme.rs new file mode 100644 index 00000000..9e9abfa0 --- /dev/null +++ b/style/src/theme.rs @@ -0,0 +1,719 @@ +pub mod palette; + +pub use self::palette::Palette; + +use crate::application; +use crate::button; +use crate::checkbox; +use crate::container; +use crate::menu; +use crate::pane_grid; +use crate::pick_list; +use crate::progress_bar; +use crate::radio; +use crate::rule; +use crate::scrollable; +use crate::slider; +use crate::text; +use crate::text_input; +use crate::toggler; + +use iced_core::{Background, Color}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Theme { +    Light, +    Dark, +} + +impl Theme { +    pub fn palette(self) -> Palette { +        match self { +            Self::Light => Palette::LIGHT, +            Self::Dark => Palette::DARK, +        } +    } + +    pub fn extended_palette(&self) -> &palette::Extended { +        match self { +            Self::Light => &palette::EXTENDED_LIGHT, +            Self::Dark => &palette::EXTENDED_DARK, +        } +    } +} + +impl Default for Theme { +    fn default() -> Self { +        Self::Light +    } +} + +#[derive(Debug, Clone, Copy)] +pub enum Application { +    Default, +    Custom(fn(Theme) -> application::Appearance), +} + +impl Default for Application { +    fn default() -> Self { +        Self::Default +    } +} + +impl application::StyleSheet for Theme { +    type Style = Application; + +    fn appearance(&self, style: Self::Style) -> application::Appearance { +        let palette = self.extended_palette(); + +        match style { +            Application::Default => application::Appearance { +                background_color: palette.background.base.color, +                text_color: palette.background.base.text, +            }, +            Application::Custom(f) => f(*self), +        } +    } +} + +/* + * Button + */ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Button { +    Primary, +    Secondary, +    Positive, +    Destructive, +    Text, +} + +impl Default for Button { +    fn default() -> Self { +        Self::Primary +    } +} + +impl button::StyleSheet for Theme { +    type Style = Button; + +    fn active(&self, style: Self::Style) -> button::Appearance { +        let palette = self.extended_palette(); + +        let appearance = button::Appearance { +            border_radius: 2.0, +            ..button::Appearance::default() +        }; + +        let from_pair = |pair: palette::Pair| button::Appearance { +            background: Some(pair.color.into()), +            text_color: pair.text, +            ..appearance +        }; + +        match style { +            Button::Primary => from_pair(palette.primary.strong), +            Button::Secondary => from_pair(palette.secondary.base), +            Button::Positive => from_pair(palette.success.base), +            Button::Destructive => from_pair(palette.danger.base), +            Button::Text => button::Appearance { +                text_color: palette.background.base.text, +                ..appearance +            }, +        } +    } + +    fn hovered(&self, style: Self::Style) -> button::Appearance { +        let active = self.active(style); +        let palette = self.extended_palette(); + +        let background = match style { +            Button::Primary => Some(palette.primary.base.color), +            Button::Secondary => Some(palette.background.strong.color), +            Button::Positive => Some(palette.success.strong.color), +            Button::Destructive => Some(palette.danger.strong.color), +            Button::Text => None, +        }; + +        button::Appearance { +            background: background.map(Background::from), +            ..active +        } +    } +} + +/* + * Checkbox + */ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Checkbox { +    Primary, +    Secondary, +    Success, +    Danger, +} + +impl Default for Checkbox { +    fn default() -> Self { +        Self::Primary +    } +} + +impl checkbox::StyleSheet for Theme { +    type Style = Checkbox; + +    fn active( +        &self, +        style: Self::Style, +        is_checked: bool, +    ) -> checkbox::Appearance { +        let palette = self.extended_palette(); + +        match style { +            Checkbox::Primary => checkbox_appearance( +                palette.primary.strong.text, +                palette.background.base, +                palette.primary.strong, +                is_checked, +            ), +            Checkbox::Secondary => checkbox_appearance( +                palette.background.base.text, +                palette.background.base, +                palette.background.base, +                is_checked, +            ), +            Checkbox::Success => checkbox_appearance( +                palette.success.base.text, +                palette.background.base, +                palette.success.base, +                is_checked, +            ), +            Checkbox::Danger => checkbox_appearance( +                palette.danger.base.text, +                palette.background.base, +                palette.danger.base, +                is_checked, +            ), +        } +    } + +    fn hovered( +        &self, +        style: Self::Style, +        is_checked: bool, +    ) -> checkbox::Appearance { +        let palette = self.extended_palette(); + +        match style { +            Checkbox::Primary => checkbox_appearance( +                palette.primary.strong.text, +                palette.background.weak, +                palette.primary.base, +                is_checked, +            ), +            Checkbox::Secondary => checkbox_appearance( +                palette.background.base.text, +                palette.background.weak, +                palette.background.base, +                is_checked, +            ), +            Checkbox::Success => checkbox_appearance( +                palette.success.base.text, +                palette.background.weak, +                palette.success.base, +                is_checked, +            ), +            Checkbox::Danger => checkbox_appearance( +                palette.danger.base.text, +                palette.background.weak, +                palette.danger.base, +                is_checked, +            ), +        } +    } +} + +fn checkbox_appearance( +    checkmark_color: Color, +    base: palette::Pair, +    accent: palette::Pair, +    is_checked: bool, +) -> checkbox::Appearance { +    checkbox::Appearance { +        background: Background::Color(if is_checked { +            accent.color +        } else { +            base.color +        }), +        checkmark_color, +        border_radius: 2.0, +        border_width: 1.0, +        border_color: accent.color, +        text_color: None, +    } +} + +/* + * Container + */ +#[derive(Clone, Copy)] +pub enum Container { +    Transparent, +    Box, +    Custom(fn(&Theme) -> container::Appearance), +} + +impl Default for Container { +    fn default() -> Self { +        Self::Transparent +    } +} + +impl From<fn(&Theme) -> container::Appearance> for Container { +    fn from(f: fn(&Theme) -> container::Appearance) -> Self { +        Self::Custom(f) +    } +} + +impl container::StyleSheet for Theme { +    type Style = Container; + +    fn appearance(&self, style: Self::Style) -> container::Appearance { +        match style { +            Container::Transparent => Default::default(), +            Container::Box => { +                let palette = self.extended_palette(); + +                container::Appearance { +                    text_color: None, +                    background: palette.background.weak.color.into(), +                    border_radius: 2.0, +                    border_width: 0.0, +                    border_color: Color::TRANSPARENT, +                } +            } +            Container::Custom(f) => f(self), +        } +    } +} + +/* + * Slider + */ +impl slider::StyleSheet for Theme { +    type Style = (); + +    fn active(&self, _style: Self::Style) -> slider::Appearance { +        let palette = self.extended_palette(); + +        let handle = slider::Handle { +            shape: slider::HandleShape::Rectangle { +                width: 8, +                border_radius: 4.0, +            }, +            color: Color::WHITE, +            border_color: Color::WHITE, +            border_width: 1.0, +        }; + +        slider::Appearance { +            rail_colors: (palette.primary.base.color, Color::TRANSPARENT), +            handle: slider::Handle { +                color: palette.background.base.color, +                border_color: palette.primary.base.color, +                ..handle +            }, +        } +    } + +    fn hovered(&self, style: Self::Style) -> slider::Appearance { +        let active = self.active(style); +        let palette = self.extended_palette(); + +        slider::Appearance { +            handle: slider::Handle { +                color: palette.primary.weak.color, +                ..active.handle +            }, +            ..active +        } +    } + +    fn dragging(&self, style: Self::Style) -> slider::Appearance { +        let active = self.active(style); +        let palette = self.extended_palette(); + +        slider::Appearance { +            handle: slider::Handle { +                color: palette.primary.base.color, +                ..active.handle +            }, +            ..active +        } +    } +} + +/* + * Menu + */ +impl menu::StyleSheet for Theme { +    type Style = (); + +    fn appearance(&self, _style: Self::Style) -> menu::Appearance { +        let palette = self.extended_palette(); + +        menu::Appearance { +            text_color: palette.background.weak.text, +            background: palette.background.weak.color.into(), +            border_width: 1.0, +            border_radius: 0.0, +            border_color: palette.background.strong.color, +            selected_text_color: palette.primary.strong.text, +            selected_background: palette.primary.strong.color.into(), +        } +    } +} + +/* + * Pick List + */ +impl pick_list::StyleSheet for Theme { +    type Style = (); + +    fn active(&self, _style: ()) -> pick_list::Appearance { +        let palette = self.extended_palette(); + +        pick_list::Appearance { +            text_color: palette.background.weak.text, +            background: palette.background.weak.color.into(), +            placeholder_color: palette.background.strong.color, +            border_radius: 2.0, +            border_width: 1.0, +            border_color: palette.background.strong.color, +            icon_size: 0.7, +        } +    } + +    fn hovered(&self, _style: ()) -> pick_list::Appearance { +        let palette = self.extended_palette(); + +        pick_list::Appearance { +            text_color: palette.background.weak.text, +            background: palette.background.weak.color.into(), +            placeholder_color: palette.background.strong.color, +            border_radius: 2.0, +            border_width: 1.0, +            border_color: palette.primary.strong.color, +            icon_size: 0.7, +        } +    } +} + +/* + * Radio + */ +impl radio::StyleSheet for Theme { +    type Style = (); + +    fn active(&self, _style: Self::Style) -> radio::Appearance { +        let palette = self.extended_palette(); + +        radio::Appearance { +            background: Color::TRANSPARENT.into(), +            dot_color: palette.primary.strong.color, +            border_width: 1.0, +            border_color: palette.primary.strong.color, +            text_color: None, +        } +    } + +    fn hovered(&self, style: Self::Style) -> radio::Appearance { +        let active = self.active(style); +        let palette = self.extended_palette(); + +        radio::Appearance { +            dot_color: palette.primary.strong.color, +            background: palette.primary.weak.color.into(), +            ..active +        } +    } +} + +/* + * Toggler + */ +impl toggler::StyleSheet for Theme { +    type Style = (); + +    fn active( +        &self, +        _style: Self::Style, +        is_active: bool, +    ) -> toggler::Appearance { +        let palette = self.extended_palette(); + +        toggler::Appearance { +            background: if is_active { +                palette.primary.strong.color +            } else { +                palette.background.strong.color +            }, +            background_border: None, +            foreground: if is_active { +                palette.primary.strong.text +            } else { +                palette.background.base.color +            }, +            foreground_border: None, +        } +    } + +    fn hovered( +        &self, +        style: Self::Style, +        is_active: bool, +    ) -> toggler::Appearance { +        let palette = self.extended_palette(); + +        toggler::Appearance { +            foreground: if is_active { +                Color { +                    a: 0.5, +                    ..palette.primary.strong.text +                } +            } else { +                palette.background.weak.color +            }, +            ..self.active(style, is_active) +        } +    } +} + +/* + * Pane Grid + */ +impl pane_grid::StyleSheet for Theme { +    type Style = (); + +    fn picked_split(&self, _style: Self::Style) -> Option<pane_grid::Line> { +        let palette = self.extended_palette(); + +        Some(pane_grid::Line { +            color: palette.primary.strong.color, +            width: 2.0, +        }) +    } + +    fn hovered_split(&self, _style: Self::Style) -> Option<pane_grid::Line> { +        let palette = self.extended_palette(); + +        Some(pane_grid::Line { +            color: palette.primary.base.color, +            width: 2.0, +        }) +    } +} + +/* + * Progress Bar + */ +#[derive(Clone, Copy)] +pub enum ProgressBar { +    Primary, +    Success, +    Danger, +    Custom(fn(&Theme) -> progress_bar::Appearance), +} + +impl Default for ProgressBar { +    fn default() -> Self { +        Self::Primary +    } +} + +impl progress_bar::StyleSheet for Theme { +    type Style = ProgressBar; + +    fn appearance(&self, style: Self::Style) -> progress_bar::Appearance { +        let palette = self.extended_palette(); + +        let from_palette = |bar: Color| progress_bar::Appearance { +            background: palette.background.strong.color.into(), +            bar: bar.into(), +            border_radius: 2.0, +        }; + +        match style { +            ProgressBar::Primary => from_palette(palette.primary.base.color), +            ProgressBar::Success => from_palette(palette.success.base.color), +            ProgressBar::Danger => from_palette(palette.danger.base.color), +            ProgressBar::Custom(f) => f(self), +        } +    } +} + +/* + * Rule + */ +#[derive(Clone, Copy)] +pub enum Rule { +    Default, +    Custom(fn(&Theme) -> rule::Appearance), +} + +impl Default for Rule { +    fn default() -> Self { +        Self::Default +    } +} + +impl rule::StyleSheet for Theme { +    type Style = Rule; + +    fn style(&self, style: Self::Style) -> rule::Appearance { +        let palette = self.extended_palette(); + +        match style { +            Rule::Default => rule::Appearance { +                color: palette.background.strong.color, +                width: 1, +                radius: 0.0, +                fill_mode: rule::FillMode::Full, +            }, +            Rule::Custom(f) => f(self), +        } +    } +} + +/* + * Scrollable + */ +impl scrollable::StyleSheet for Theme { +    type Style = (); + +    fn active(&self, _style: Self::Style) -> scrollable::Scrollbar { +        let palette = self.extended_palette(); + +        scrollable::Scrollbar { +            background: palette.background.weak.color.into(), +            border_radius: 2.0, +            border_width: 0.0, +            border_color: Color::TRANSPARENT, +            scroller: scrollable::Scroller { +                color: palette.background.strong.color, +                border_radius: 2.0, +                border_width: 0.0, +                border_color: Color::TRANSPARENT, +            }, +        } +    } + +    fn hovered(&self, _style: Self::Style) -> scrollable::Scrollbar { +        let palette = self.extended_palette(); + +        scrollable::Scrollbar { +            background: palette.background.weak.color.into(), +            border_radius: 2.0, +            border_width: 0.0, +            border_color: Color::TRANSPARENT, +            scroller: scrollable::Scroller { +                color: palette.primary.strong.color, +                border_radius: 2.0, +                border_width: 0.0, +                border_color: Color::TRANSPARENT, +            }, +        } +    } +} + +/* + * Text + */ +#[derive(Clone, Copy)] +pub enum Text { +    Default, +    Color(Color), +    Custom(fn(&Theme) -> text::Appearance), +} + +impl Default for Text { +    fn default() -> Self { +        Self::Default +    } +} + +impl From<Color> for Text { +    fn from(color: Color) -> Self { +        Text::Color(color) +    } +} + +impl text::StyleSheet for Theme { +    type Style = Text; + +    fn appearance(&self, style: Self::Style) -> text::Appearance { +        match style { +            Text::Default => Default::default(), +            Text::Color(c) => text::Appearance { color: Some(c) }, +            Text::Custom(f) => f(self), +        } +    } +} + +/* + * Text Input + */ +impl text_input::StyleSheet for Theme { +    type Style = (); + +    fn active(&self, _style: Self::Style) -> text_input::Appearance { +        let palette = self.extended_palette(); + +        text_input::Appearance { +            background: palette.background.base.color.into(), +            border_radius: 2.0, +            border_width: 1.0, +            border_color: palette.background.strong.color, +        } +    } + +    fn hovered(&self, _style: Self::Style) -> text_input::Appearance { +        let palette = self.extended_palette(); + +        text_input::Appearance { +            background: palette.background.base.color.into(), +            border_radius: 2.0, +            border_width: 1.0, +            border_color: palette.background.base.text, +        } +    } + +    fn focused(&self, _style: Self::Style) -> text_input::Appearance { +        let palette = self.extended_palette(); + +        text_input::Appearance { +            background: palette.background.base.color.into(), +            border_radius: 2.0, +            border_width: 1.0, +            border_color: palette.primary.strong.color, +        } +    } + +    fn placeholder_color(&self, _style: Self::Style) -> Color { +        let palette = self.extended_palette(); + +        palette.background.strong.color +    } + +    fn value_color(&self, _style: Self::Style) -> Color { +        let palette = self.extended_palette(); + +        palette.background.base.text +    } + +    fn selection_color(&self, _style: Self::Style) -> Color { +        let palette = self.extended_palette(); + +        palette.primary.weak.color +    } +} diff --git a/style/src/theme/palette.rs b/style/src/theme/palette.rs new file mode 100644 index 00000000..81aa9cc7 --- /dev/null +++ b/style/src/theme/palette.rs @@ -0,0 +1,277 @@ +use iced_core::Color; + +use lazy_static::lazy_static; +use palette::{FromColor, Hsl, Mix, RelativeContrast, Srgb}; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Palette { +    pub background: Color, +    pub text: Color, +    pub primary: Color, +    pub success: Color, +    pub danger: Color, +} + +impl Palette { +    pub const LIGHT: Self = Self { +        background: Color::WHITE, +        text: Color::BLACK, +        primary: Color::from_rgb( +            0x5E as f32 / 255.0, +            0x7C as f32 / 255.0, +            0xE2 as f32 / 255.0, +        ), +        success: Color::from_rgb( +            0x12 as f32 / 255.0, +            0x66 as f32 / 255.0, +            0x4F as f32 / 255.0, +        ), +        danger: Color::from_rgb( +            0xC3 as f32 / 255.0, +            0x42 as f32 / 255.0, +            0x3F as f32 / 255.0, +        ), +    }; + +    pub const DARK: Self = Self { +        background: Color::from_rgb( +            0x20 as f32 / 255.0, +            0x22 as f32 / 255.0, +            0x25 as f32 / 255.0, +        ), +        text: Color::from_rgb(0.90, 0.90, 0.90), +        primary: Color::from_rgb( +            0x5E as f32 / 255.0, +            0x7C as f32 / 255.0, +            0xE2 as f32 / 255.0, +        ), +        success: Color::from_rgb( +            0x12 as f32 / 255.0, +            0x66 as f32 / 255.0, +            0x4F as f32 / 255.0, +        ), +        danger: Color::from_rgb( +            0xC3 as f32 / 255.0, +            0x42 as f32 / 255.0, +            0x3F as f32 / 255.0, +        ), +    }; +} + +pub struct Extended { +    pub background: Background, +    pub primary: Primary, +    pub secondary: Secondary, +    pub success: Success, +    pub danger: Danger, +} + +lazy_static! { +    pub static ref EXTENDED_LIGHT: Extended = +        Extended::generate(Palette::LIGHT); +    pub static ref EXTENDED_DARK: Extended = Extended::generate(Palette::DARK); +} + +impl Extended { +    pub fn generate(palette: Palette) -> Self { +        Self { +            background: Background::new(palette.background, palette.text), +            primary: Primary::generate( +                palette.primary, +                palette.background, +                palette.text, +            ), +            secondary: Secondary::generate(palette.background, palette.text), +            success: Success::generate( +                palette.success, +                palette.background, +                palette.text, +            ), +            danger: Danger::generate( +                palette.danger, +                palette.background, +                palette.text, +            ), +        } +    } +} + +#[derive(Debug, Clone, Copy)] +pub struct Pair { +    pub color: Color, +    pub text: Color, +} + +impl Pair { +    pub fn new(color: Color, text: Color) -> Self { +        Self { +            color, +            text: readable(color, text), +        } +    } +} + +pub struct Background { +    pub base: Pair, +    pub weak: Pair, +    pub strong: Pair, +} + +impl Background { +    pub fn new(base: Color, text: Color) -> Self { +        let weak = mix(base, text, 0.15); +        let strong = mix(base, text, 0.40); + +        Self { +            base: Pair::new(base, text), +            weak: Pair::new(weak, text), +            strong: Pair::new(strong, text), +        } +    } +} + +pub struct Primary { +    pub base: Pair, +    pub weak: Pair, +    pub strong: Pair, +} + +impl Primary { +    pub fn generate(base: Color, background: Color, text: Color) -> Self { +        let weak = mix(base, background, 0.4); +        let strong = deviate(base, 0.1); + +        Self { +            base: Pair::new(base, text), +            weak: Pair::new(weak, text), +            strong: Pair::new(strong, text), +        } +    } +} + +pub struct Secondary { +    pub base: Pair, +    pub weak: Pair, +    pub strong: Pair, +} + +impl Secondary { +    pub fn generate(base: Color, text: Color) -> Self { +        let base = mix(base, text, 0.2); +        let weak = mix(base, text, 0.1); +        let strong = mix(base, text, 0.3); + +        Self { +            base: Pair::new(base, text), +            weak: Pair::new(weak, text), +            strong: Pair::new(strong, text), +        } +    } +} + +pub struct Success { +    pub base: Pair, +    pub weak: Pair, +    pub strong: Pair, +} + +impl Success { +    pub fn generate(base: Color, background: Color, text: Color) -> Self { +        let weak = mix(base, background, 0.4); +        let strong = deviate(base, 0.1); + +        Self { +            base: Pair::new(base, text), +            weak: Pair::new(weak, text), +            strong: Pair::new(strong, text), +        } +    } +} + +pub struct Danger { +    pub base: Pair, +    pub weak: Pair, +    pub strong: Pair, +} + +impl Danger { +    pub fn generate(base: Color, background: Color, text: Color) -> Self { +        let weak = mix(base, background, 0.4); +        let strong = deviate(base, 0.1); + +        Self { +            base: Pair::new(base, text), +            weak: Pair::new(weak, text), +            strong: Pair::new(strong, text), +        } +    } +} + +fn darken(color: Color, amount: f32) -> Color { +    let mut hsl = to_hsl(color); + +    hsl.lightness = if hsl.lightness - amount < 0.0 { +        0.0 +    } else { +        hsl.lightness - amount +    }; + +    from_hsl(hsl) +} + +fn lighten(color: Color, amount: f32) -> Color { +    let mut hsl = to_hsl(color); + +    hsl.lightness = if hsl.lightness + amount > 1.0 { +        1.0 +    } else { +        hsl.lightness + amount +    }; + +    from_hsl(hsl) +} + +fn deviate(color: Color, amount: f32) -> Color { +    if is_dark(color) { +        lighten(color, amount) +    } else { +        darken(color, amount) +    } +} + +fn mix(a: Color, b: Color, factor: f32) -> Color { +    let a_lin = Srgb::from(a).into_linear(); +    let b_lin = Srgb::from(b).into_linear(); + +    let mixed = a_lin.mix(&b_lin, factor); +    Srgb::from_linear(mixed).into() +} + +fn readable(background: Color, text: Color) -> Color { +    if is_readable(background, text) { +        text +    } else if is_dark(background) { +        Color::WHITE +    } else { +        Color::BLACK +    } +} + +fn is_dark(color: Color) -> bool { +    to_hsl(color).lightness < 0.6 +} + +fn is_readable(a: Color, b: Color) -> bool { +    let a_srgb = Srgb::from(a); +    let b_srgb = Srgb::from(b); + +    a_srgb.has_enhanced_contrast_text(&b_srgb) +} + +fn to_hsl(color: Color) -> Hsl { +    Hsl::from_color(Srgb::from(color)) +} + +fn from_hsl(hsl: Hsl) -> Color { +    Srgb::from_color(hsl).into() +} diff --git a/style/src/toggler.rs b/style/src/toggler.rs index c06a8cd1..4ee7db46 100644 --- a/style/src/toggler.rs +++ b/style/src/toggler.rs @@ -3,7 +3,7 @@ use iced_core::Color;  /// The appearance of a toggler.  #[derive(Debug)] -pub struct Style { +pub struct Appearance {      pub background: Color,      pub background_border: Option<Color>,      pub foreground: Color, @@ -12,46 +12,9 @@ pub struct Style {  /// A set of rules that dictate the style of a toggler.  pub trait StyleSheet { -    fn active(&self, is_active: bool) -> Style; +    type Style: Default + Copy; -    fn hovered(&self, is_active: bool) -> Style; -} - -struct Default; - -impl StyleSheet for Default { -    fn active(&self, is_active: bool) -> Style { -        Style { -            background: if is_active { -                Color::from_rgb(0.0, 1.0, 0.0) -            } else { -                Color::from_rgb(0.7, 0.7, 0.7) -            }, -            background_border: None, -            foreground: Color::WHITE, -            foreground_border: None, -        } -    } - -    fn hovered(&self, is_active: bool) -> Style { -        Style { -            foreground: Color::from_rgb(0.95, 0.95, 0.95), -            ..self.active(is_active) -        } -    } -} - -impl<'a> std::default::Default for Box<dyn StyleSheet + 'a> { -    fn default() -> Self { -        Box::new(Default) -    } -} +    fn active(&self, style: Self::Style, is_active: bool) -> Appearance; -impl<'a, T> From<T> for Box<dyn StyleSheet + 'a> -where -    T: 'a + StyleSheet, -{ -    fn from(style: T) -> Self { -        Box::new(style) -    } +    fn hovered(&self, style: Self::Style, is_active: bool) -> Appearance;  } | 
