summaryrefslogtreecommitdiffstats
path: root/style/src
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón <hector0193@gmail.com>2022-07-09 02:28:52 +0200
committerLibravatar GitHub <noreply@github.com>2022-07-09 02:28:52 +0200
commite053e25d2ccb17f7a162685a106a8bbd915a873f (patch)
tree5304f3ea2712e8889c7278ec5e57418f484d8f6c /style/src
parent66eb6263003c1bbedd1fd14d6b12f172d20a6211 (diff)
parent7105db97a53d90adf429091298f31c90974d8f08 (diff)
downloadiced-e053e25d2ccb17f7a162685a106a8bbd915a873f.tar.gz
iced-e053e25d2ccb17f7a162685a106a8bbd915a873f.tar.bz2
iced-e053e25d2ccb17f7a162685a106a8bbd915a873f.zip
Merge pull request #1362 from iced-rs/theming
Theming
Diffstat (limited to 'style/src')
-rw-r--r--style/src/application.rs13
-rw-r--r--style/src/button.rs56
-rw-r--r--style/src/checkbox.rs43
-rw-r--r--style/src/container.rs39
-rw-r--r--style/src/lib.rs5
-rw-r--r--style/src/menu.rs17
-rw-r--r--style/src/pane_grid.rs33
-rw-r--r--style/src/pick_list.rs65
-rw-r--r--style/src/progress_bar.rs33
-rw-r--r--style/src/radio.rs42
-rw-r--r--style/src/rule.rs74
-rw-r--r--style/src/scrollable.rs51
-rw-r--r--style/src/slider.rs68
-rw-r--r--style/src/text.rs18
-rw-r--r--style/src/text_input.rs76
-rw-r--r--style/src/theme.rs718
-rw-r--r--style/src/theme/palette.rs277
-rw-r--r--style/src/toggler.rs45
18 files changed, 1130 insertions, 543 deletions
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;
}