From 2cfb307f8c3927a0876c6b754a5d7d673b9edfee Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector0193@gmail.com>
Date: Sat, 21 May 2022 17:33:31 -0400
Subject: Implement basic theming `Palette`

---
 style/src/theme/palette.rs | 202 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 202 insertions(+)
 create mode 100644 style/src/theme/palette.rs

(limited to 'style/src/theme')

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()
+}
-- 
cgit