diff --git a/Cargo.lock b/Cargo.lock index 056fab49cd576915fca5443922b0f094591237e1..1b0c6e4bb97ebc9fb8ce9615729fb370a5b18a34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1580,6 +1580,28 @@ dependencies = [ "rustc-hash", ] +[[package]] +name = "color" +version = "0.1.0" +dependencies = [ + "anyhow", + "fs", + "indexmap 1.9.3", + "itertools 0.11.0", + "palette", + "parking_lot 0.11.2", + "refineable", + "schemars", + "serde", + "serde_derive", + "serde_json", + "settings", + "story", + "toml 0.5.11", + "util", + "uuid 1.4.1", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -4976,6 +4998,7 @@ dependencies = [ "approx", "fast-srgb8", "palette_derive", + "phf", ] [[package]] @@ -5164,6 +5187,48 @@ dependencies = [ "indexmap 2.0.0", ] +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher 0.3.11", +] + [[package]] name = "picker" version = "0.1.0" @@ -7073,6 +7138,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -7643,7 +7714,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c536faaff1a10837cfe373142583f6e27d81e96beba339147e77b67c9f260ff" dependencies = [ "float-cmp", - "siphasher", + "siphasher 0.2.3", ] [[package]] @@ -8857,7 +8928,7 @@ dependencies = [ "roxmltree", "rustybuzz", "simplecss", - "siphasher", + "siphasher 0.2.3", "svgtypes", "ttf-parser 0.12.3", "unicode-bidi", @@ -9649,6 +9720,7 @@ dependencies = [ "client", "collab_ui", "collections", + "color", "command_palette", "copilot", "copilot_ui", diff --git a/crates/color/Cargo.toml b/crates/color/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..c6416f9691b3ca417c1f7426ace5359c199be88b --- /dev/null +++ b/crates/color/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "color" +version = "0.1.0" +edition = "2021" +publish = false + +[features] +default = [] +stories = ["dep:itertools", "dep:story"] + +[lib] +path = "src/color.rs" +doctest = true + +[dependencies] +# TODO: Clean up dependencies +anyhow.workspace = true +fs = { path = "../fs" } +indexmap = "1.6.2" +parking_lot.workspace = true +refineable.workspace = true +schemars.workspace = true +serde.workspace = true +serde_derive.workspace = true +serde_json.workspace = true +settings = { path = "../settings" } +story = { path = "../story", optional = true } +toml.workspace = true +uuid.workspace = true +util = { path = "../util" } +itertools = { version = "0.11.0", optional = true } +palette = "0.7.3" diff --git a/crates/color/src/color.rs b/crates/color/src/color.rs new file mode 100644 index 0000000000000000000000000000000000000000..77818eb7b8f76c23cc9b05e31402ad04a89aeca3 --- /dev/null +++ b/crates/color/src/color.rs @@ -0,0 +1,118 @@ +//! # Color +//! +//! The `color` crate provides a set utilities for working with colors. It is a wrapper around the [`palette`](https://docs.rs/palette) crate with some additional functionality. +//! +//! It is used to create a manipulate colors when building themes. +//! +//! **Note:** This crate does not depend on `gpui`, so it does not provide any +//! interfaces for converting to `gpui` style colors. + +use palette::{FromColor, Hsl, Hsla, Mix, Srgba, WithAlpha}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum BlendMode { + Multiply, + Screen, + Overlay, + Darken, + Lighten, + Dodge, + Burn, + HardLight, + SoftLight, + Difference, + Exclusion, +} + +/// Creates a new [`palette::Hsl`] color. +pub fn hsl(h: f32, s: f32, l: f32) -> Hsl { + Hsl::new_srgb(h, s, l) +} + +/// Converts a hexadecimal color string to a `palette::Hsla` color. +/// +/// This function supports the following hex formats: +/// `#RGB`, `#RGBA`, `#RRGGBB`, `#RRGGBBAA`. +pub fn hex_to_hsla(s: &str) -> Result { + let hex = s.trim_start_matches('#'); + + // Expand shorthand formats #RGB and #RGBA to #RRGGBB and #RRGGBBAA + let hex = match hex.len() { + 3 => hex + .chars() + .map(|c| c.to_string().repeat(2)) + .collect::(), + 4 => { + let (rgb, alpha) = hex.split_at(3); + let rgb = rgb + .chars() + .map(|c| c.to_string().repeat(2)) + .collect::(); + let alpha = alpha.chars().next().unwrap().to_string().repeat(2); + format!("{}{}", rgb, alpha) + } + 6 => format!("{}ff", hex), // Add alpha if missing + 8 => hex.to_string(), // Already in full format + _ => return Err("Invalid hexadecimal string length".to_string()), + }; + + let hex_val = + u32::from_str_radix(&hex, 16).map_err(|_| format!("Invalid hexadecimal string: {}", s))?; + + let r = ((hex_val >> 24) & 0xFF) as f32 / 255.0; + let g = ((hex_val >> 16) & 0xFF) as f32 / 255.0; + let b = ((hex_val >> 8) & 0xFF) as f32 / 255.0; + let a = (hex_val & 0xFF) as f32 / 255.0; + + let srgba = Srgba::new(r, g, b, a); + let hsl = Hsl::from_color(srgba); + let hsla = Hsla::from(hsl).with_alpha(a); + + Ok(hsla) +} + +/// Mixes two [`palette::Hsl`] colors at the given `mix_ratio`. +pub fn hsl_mix(hsla_1: Hsl, hsla_2: Hsl, mix_ratio: f32) -> Hsl { + hsla_1.mix(hsla_2, mix_ratio).into() +} + +/// Represents a color +/// An interstitial state used to provide a consistent API for colors +/// with additional functionality like color mixing, blending, etc. +/// +/// Does not return [gpui] colors as the `color` crate does not +/// depend on [gpui]. +#[derive(Debug, Copy, Clone)] +pub struct Color { + value: Hsla, +} + +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 { + Self { + value: Hsla::new(hue, saturation, lightness, alpha), + } + } + + /// Returns the [`palette::Hsla`] value of this color. + pub fn value(&self) -> Hsla { + self.value + } + + /// 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); + + Self { + value: mixed.into(), + } + } +} diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 734c225cb1e610c64dab92112accad9634632fee..1e21648408e4855e74cf4f411bc287345bac2bee 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -29,6 +29,7 @@ command_palette = { path = "../command_palette" } # component_test = { path = "../component_test" } client = { path = "../client" } # clock = { path = "../clock" } +color = { path = "../color" } copilot = { path = "../copilot" } copilot_ui = { path = "../copilot_ui" } diagnostics = { path = "../diagnostics" }