diff options
Diffstat (limited to 'style')
-rw-r--r-- | style/Cargo.toml | 7 | ||||
-rw-r--r-- | style/src/theme.rs | 79 | ||||
-rw-r--r-- | style/src/theme/palette.rs | 202 |
3 files changed, 280 insertions, 8 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/theme.rs b/style/src/theme.rs index 7d420c8a..91a9e921 100644 --- a/style/src/theme.rs +++ b/style/src/theme.rs @@ -1,6 +1,10 @@ +mod palette; + +pub use self::palette::Palette; + use crate::button; -use iced_core::{Color, Vector}; +use iced_core::Background; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Theme { @@ -8,6 +12,22 @@ pub enum Theme { Dark, } +impl Theme { + pub fn palette(self) -> Palette { + match self { + Self::Light => Palette::LIGHT, + Self::Dark => Palette::DARK, + } + } + + 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 @@ -32,14 +52,57 @@ impl Default for Button { impl button::StyleSheet for Theme { type Variant = Button; - fn active(&self, _variant: Self::Variant) -> button::Style { + fn active(&self, variant: Self::Variant) -> button::Style { + let palette = self.extended_palette(); + + let style = button::Style { + border_radius: 2.0, + ..button::Style::default() + }; + + match variant { + Button::Primary => button::Style { + background: Some(palette.primary.strong.into()), + text_color: palette.primary.text, + ..style + }, + Button::Secondary => button::Style { + background: Some(palette.background.weak.into()), + text_color: palette.background.text, + ..style + }, + Button::Positive => button::Style { + background: Some(palette.success.base.into()), + text_color: palette.success.text, + ..style + }, + Button::Destructive => button::Style { + background: Some(palette.danger.base.into()), + text_color: palette.danger.text, + ..style + }, + Button::Text => button::Style { + text_color: palette.background.text, + ..style + }, + } + } + + fn hovered(&self, variant: Self::Variant) -> button::Style { + let active = self.active(variant); + let palette = self.extended_palette(); + + let background = match variant { + Button::Primary => Some(palette.primary.base), + Button::Secondary => Some(palette.background.strong), + Button::Positive => Some(palette.success.strong), + Button::Destructive => Some(palette.danger.strong), + Button::Text => None, + }; + button::Style { - shadow_offset: Vector::default(), - background: None, - border_radius: 0.0, - border_width: 0.0, - border_color: Color::TRANSPARENT, - text_color: Color::BLACK, + background: background.map(Background::from), + ..active } } } diff --git a/style/src/theme/palette.rs b/style/src/theme/palette.rs new file mode 100644 index 00000000..74139e6b --- /dev/null +++ b/style/src/theme/palette.rs @@ -0,0 +1,202 @@ +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::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 struct Extended { + pub background: Background, + pub primary: Group, + pub success: Group, + pub danger: Group, +} + +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: Group::new( + palette.primary, + palette.background, + palette.text, + ), + success: Group::new( + palette.success, + palette.background, + palette.text, + ), + danger: Group::new( + palette.danger, + palette.background, + palette.text, + ), + } + } +} + +pub struct Background { + pub base: Color, + pub weak: Color, + pub strong: Color, + pub text: Color, +} + +impl Background { + pub fn new(base: Color, text: Color) -> Self { + Self { + base, + weak: muted(base, 0.1), + strong: muted(base, 0.2), + text, + } + } +} + +pub struct Group { + pub base: Color, + pub weak: Color, + pub strong: Color, + pub text: Color, +} + +impl Group { + pub fn new(base: Color, background: Color, text: Color) -> Self { + Self { + base, + weak: mix(base, background, 0.4), + strong: if is_dark(background) { + lighten(base, 0.1) + } else { + darken(base, 0.1) + }, + text: if is_readable(base, text) { + text + } else if is_dark(text) { + Color::WHITE + } else { + Color::BLACK + }, + } + } +} + +fn muted(color: Color, amount: f32) -> Color { + let mut hsl = to_hsl(color); + + hsl.lightness = if is_dark(color) { + let delta = amount * (1.0 - hsl.lightness).powi(7); + hsl.lightness + delta + } else { + let delta = amount * hsl.lightness.powi(5); + hsl.lightness - delta + }; + + from_hsl(hsl) +} + +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 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 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 is_dark(color: Color) -> bool { + to_hsl(color).lightness < 0.5 +} + +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() +} |