Add a rudimentary state color builder

Nate Butler created

Change summary

Cargo.lock                |  1 
crates/color/src/color.rs | 57 +++++++++++++++++++++++++++++++++++-----
crates/theme/Cargo.toml   |  1 
crates/theme/src/theme.rs |  7 +++++
4 files changed, 58 insertions(+), 8 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -7925,6 +7925,7 @@ name = "theme"
 version = "0.1.0"
 dependencies = [
  "anyhow",
+ "color",
  "fs",
  "gpui",
  "indexmap 1.9.3",

crates/color/src/color.rs 🔗

@@ -88,25 +88,28 @@ pub struct Color {
 }
 
 impl Color {
-    /// Creates a new [`Color`]
-    pub fn new(hue: f32, saturation: f32, lightness: f32) -> Self {
-        let hsl = hsl(hue, saturation, lightness);
-
-        Self { value: hsl.into() }
-    }
-
     /// Creates a new [`Color`] with an alpha value.
-    pub fn from_hsla(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Self {
+    pub fn new(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Self {
         Self {
             value: Hsla::new(hue, saturation, lightness, alpha),
         }
     }
 
+    /// Creates a new [`Color`] with an alpha value of `1.0`.
+    pub fn hsl(hue: f32, saturation: f32, lightness: f32) -> Self {
+        Self::new(hue, saturation, lightness, 1.0)
+    }
+
     /// Returns the [`palette::Hsla`] value of this color.
     pub fn value(&self) -> Hsla {
         self.value
     }
 
+    /// Returns a set of states for this color.
+    pub fn states(&self, is_light: bool) -> ColorStates {
+        states_for_color(*self, is_light)
+    }
+
     /// Mixes this color with another [`palette::Hsl`] color at the given `mix_ratio`.
     pub fn mix(&self, other: Hsl, mix_ratio: f32) -> Self {
         let mixed = self.value.mix(other.into(), mix_ratio);
@@ -116,3 +119,41 @@ impl Color {
         }
     }
 }
+
+/// A set of colors for different states of an element.
+#[derive(Debug, Copy, Clone)]
+pub struct ColorStates {
+    /// The default color.
+    pub default: Color,
+    /// The color when the mouse is hovering over the element.
+    pub hover: Color,
+    /// The color when the mouse button is held down on the element.
+    pub active: Color,
+    /// The color when the element is focused with the keyboard.
+    pub focused: Color,
+    /// The color when the element is disabled.
+    pub disabled: Color,
+}
+
+/// Returns a set of colors for different states of an element.
+///
+/// todo!("Test and improve this function")
+pub fn states_for_color(color: Color, is_light: bool) -> ColorStates {
+    let hover_lightness = if is_light { 0.9 } else { 0.1 };
+    let active_lightness = if is_light { 0.8 } else { 0.2 };
+    let focused_lightness = if is_light { 0.7 } else { 0.3 };
+    let disabled_lightness = if is_light { 0.6 } else { 0.5 };
+
+    let hover = color.mix(hsl(0.0, 0.0, hover_lightness), 0.1);
+    let active = color.mix(hsl(0.0, 0.0, active_lightness), 0.1);
+    let focused = color.mix(hsl(0.0, 0.0, focused_lightness), 0.1);
+    let disabled = color.mix(hsl(0.0, 0.0, disabled_lightness), 0.1);
+
+    ColorStates {
+        default: color,
+        hover,
+        active,
+        focused,
+        disabled,
+    }
+}

crates/theme/Cargo.toml 🔗

@@ -34,6 +34,7 @@ story = { path = "../story", optional = true }
 toml.workspace = true
 uuid.workspace = true
 util = { path = "../util" }
+color = {path = "../color"}
 itertools = { version = "0.11.0", optional = true }
 
 [dev-dependencies]

crates/theme/src/theme.rs 🔗

@@ -147,3 +147,10 @@ pub fn color_alpha(color: Hsla, alpha: f32) -> Hsla {
     color.a = alpha;
     color
 }
+
+pub fn to_gpui_hsla(color: color::Color) -> gpui::Hsla {
+    let hsla = color.value();
+    let hue: f32 = hsla.hue.into();
+
+    gpui::hsla(hue / 360.0, hsla.saturation, hsla.lightness, hsla.alpha)
+}