diff options
Diffstat (limited to 'style')
-rw-r--r-- | style/Cargo.toml | 7 | ||||
-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 | 5 | ||||
-rw-r--r-- | style/src/menu.rs | 17 | ||||
-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 | 18 | ||||
-rw-r--r-- | style/src/text_input.rs | 76 | ||||
-rw-r--r-- | style/src/theme.rs | 718 | ||||
-rw-r--r-- | style/src/theme/palette.rs | 277 | ||||
-rw-r--r-- | style/src/toggler.rs | 45 |
19 files changed, 1137 insertions, 543 deletions
diff --git a/style/Cargo.toml b/style/Cargo.toml index bb2a9645..cf9d328b 100644 --- a/style/Cargo.toml +++ b/style/Cargo.toml @@ -13,3 +13,10 @@ categories = ["gui"] [dependencies.iced_core] 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..ee426e98 100644 --- a/style/src/lib.rs +++ b/style/src/lib.rs @@ -9,6 +9,7 @@ )] pub use iced_core::{Background, Color}; +pub mod application; pub mod button; pub mod checkbox; pub mod container; @@ -20,5 +21,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..b1dd5ea0 100644 --- a/style/src/menu.rs +++ b/style/src/menu.rs @@ -2,7 +2,7 @@ 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, @@ -11,15 +11,8 @@ pub struct Style { 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..69a4ed85 --- /dev/null +++ b/style/src/text.rs @@ -0,0 +1,18 @@ +use iced_core::Color; + +pub trait StyleSheet { + type Style: Default + Copy; + + fn appearance(&self, style: Self::Style) -> Appearance; +} + +#[derive(Debug, Clone, Copy)] +pub struct Appearance { + pub color: Option<Color>, +} + +impl Default for Appearance { + fn default() -> Self { + Self { color: None } + } +} 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..d2de8a5d --- /dev/null +++ b/style/src/theme.rs @@ -0,0 +1,718 @@ +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_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.into(), + 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.into(), + 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.into(), + 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.into(), + 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..cb8bb6e6 --- /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 { + background: Color, + text: Color, + primary: Color, + success: Color, + 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; } |