From 60eb4dbb567648600e0684b9f6a7bf3fb2de88be Mon Sep 17 00:00:00 2001 From: luoxiaozero <48741584+luoxiaozero@users.noreply.github.com> Date: Wed, 7 Feb 2024 21:42:34 +0800 Subject: [PATCH] feat: rewrite color (#98) --- demo/Cargo.toml | 1 + demo_markdown/docs/color_picker/mod.md | 32 ++++- thaw/Cargo.toml | 1 + thaw/src/color_picker/color.rs | 162 +++---------------------- thaw/src/color_picker/mod.rs | 85 +++++++++---- 5 files changed, 110 insertions(+), 171 deletions(-) diff --git a/demo/Cargo.toml b/demo/Cargo.toml index b614128..2aa85f7 100644 --- a/demo/Cargo.toml +++ b/demo/Cargo.toml @@ -14,6 +14,7 @@ leptos_devtools = { version = "0.0.1", optional = true} thaw = { path = "../thaw" } demo_markdown = { path = "../demo_markdown" } icondata = "0.3.0" +palette = "0.7.4" [features] default = ["csr"] diff --git a/demo_markdown/docs/color_picker/mod.md b/demo_markdown/docs/color_picker/mod.md index 39b9fba..6739e89 100644 --- a/demo_markdown/docs/color_picker/mod.md +++ b/demo_markdown/docs/color_picker/mod.md @@ -1,16 +1,38 @@ # Color Picker ```rust demo -let value = create_rw_signal(RGBA::default()); +use palette::Srgb; + +let value = create_rw_signal(Color::from(Srgb::new(0.0, 0.0, 0.0))); view! { } ``` +### Color Format + +Encoding formats, support RGB, HSV, HSL. + +```rust demo +use palette::{Hsl, Hsv, Srgb}; + +let rgb = create_rw_signal(Color::from(Srgb::new(0.0, 0.0, 0.0))); +let hsv = create_rw_signal(Color::from(Hsv::new(0.0, 0.0, 0.0))); +let hsl = create_rw_signal(Color::from(Hsl::new(0.0, 0.0, 0.0))); + +view! { + + + + + +} +``` + ### DatePicker Props -| Name | Type | Default | Desciption | -| ----- | --------------------- | -------------------- | ----------------------------------------------- | -| class | `MaybeSignal` | `Default::default()` | Addtional classes for the color picker element. | -| value | `RwSignal` | `Default::default()` | Value of the picker. | +| Name | Type | Default | Desciption | +| ----- | ----------------------------------- | -------------------- | ----------------------------------------------- | +| class | `OptionalProp>` | `Default::default()` | Addtional classes for the color picker element. | +| value | `Model` | `Default::default()` | Value of the picker. | diff --git a/thaw/Cargo.toml b/thaw/Cargo.toml index f192742..6432c4c 100644 --- a/thaw/Cargo.toml +++ b/thaw/Cargo.toml @@ -27,6 +27,7 @@ icondata_ai = "0.0.10" uuid = { version = "1.7.0", features = ["v4"] } cfg-if = "1.0.0" chrono = "0.4.33" +palette = "0.7.4" [features] csr = ["leptos/csr"] diff --git a/thaw/src/color_picker/color.rs b/thaw/src/color_picker/color.rs index 43b5a3d..a999b9e 100644 --- a/thaw/src/color_picker/color.rs +++ b/thaw/src/color_picker/color.rs @@ -1,160 +1,32 @@ +use palette::{Hsl, Hsv, Srgb}; + #[derive(Clone)] -pub struct RGBA { - pub red: u8, - pub green: u8, - pub blue: u8, - pub alpha: u8, +pub enum Color { + RGB(Srgb), + HSV(Hsv), + HSL(Hsl), } -impl Default for RGBA { +impl Default for Color { fn default() -> Self { - Self { - red: Default::default(), - green: Default::default(), - blue: Default::default(), - alpha: u8::MAX, - } + Self::RGB(Srgb::new(0.0, 0.0, 0.0)) } } -impl RGBA { - pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self { - Self { - red: r, - green: g, - blue: b, - alpha: a, - } - } - - pub fn new_rgb(r: u8, g: u8, b: u8) -> Self { - Self { - red: r, - green: g, - blue: b, - alpha: u8::MAX, - } - } - - pub fn to_hex_string(&self) -> String { - if self.alpha == u8::MAX { - format!("#{:02X}{:02X}{:02X}", self.red, self.green, self.blue) - } else { - format!( - "#{:02X}{:02X}{:02X}{:02X}", - self.red, self.green, self.blue, self.alpha - ) - } +impl From for Color { + fn from(value: Srgb) -> Self { + Self::RGB(value) } } -impl From for RGBA { - fn from(value: HSV) -> Self { - let HSV { - hue: h, - saturation: s, - value: v, - alpha, - } = value; - let h = f64::from(h); - - let c = v * s; - let x = c * (1.0 - f64::abs(((h / 60.0) % 2.0) - 1.0)); - let m = v - c; - - let (r, g, b) = if (0.0..60.0).contains(&h) { - (c, x, 0.0) - } else if (60.0..120.0).contains(&h) { - (x, c, 0.0) - } else if (120.0..180.0).contains(&h) { - (0.0, c, x) - } else if (180.0..240.0).contains(&h) { - (0.0, x, c) - } else if (240.0..300.0).contains(&h) { - (x, 0.0, c) - } else if (300.0..360.0).contains(&h) { - (c, 0.0, x) - } else { - (c, x, 0.0) - }; - - let (r, g, b) = ( - ((r + m) * 255.0) as u8, - ((g + m) * 255.0) as u8, - ((b + m) * 255.0) as u8, - ); - - RGBA::new(r, g, b, alpha) +impl From for Color { + fn from(value: Hsv) -> Self { + Self::HSV(value) } } -#[derive(Clone)] -pub struct HSV { - pub hue: u16, - pub saturation: f64, - pub value: f64, - pub alpha: u8, -} - -impl HSV { - pub fn new(hue: u16, saturation: f64, value: f64) -> Self { - Self { - hue, - saturation, - value, - alpha: u8::MAX, - } - } - - pub fn new_alpha(hue: u16, saturation: f64, value: f64, alpha: u8) -> Self { - Self { - hue, - saturation, - value, - alpha, - } - } -} - -impl From for HSV { - fn from(value: RGBA) -> Self { - let RGBA { - red: r, - green: g, - blue: b, - alpha, - } = value; - - let (r, g, b) = (r as f64 / 255.0, g as f64 / 255.0, b as f64 / 255.0); - - let c_max = f64::max(r, f64::max(g, b)); - let c_min = f64::min(r, f64::min(g, b)); - let delta = c_max - c_min; - - let hue = if delta == 0.0 { - 0.0 - } else if c_max == r { - 60.0 * (((g - b) / delta) % 6.0) - } else if c_max == g { - 60.0 * (((b - r) / delta) + 2.0) - } else if c_max == b { - 60.0 * (((r - g) / delta) + 4.0) - } else { - unreachable!() - }; - - let saturation = match c_max == 0.0 { - true => 0.0, - false => delta / c_max, - }; - - let value = c_max; - - HSV { - hue: hue.to_string().parse().unwrap(), - saturation, - value, - alpha, - } +impl From for Color { + fn from(value: Hsl) -> Self { + Self::HSL(value) } } diff --git a/thaw/src/color_picker/mod.rs b/thaw/src/color_picker/mod.rs index 076a81f..3fb09e0 100644 --- a/thaw/src/color_picker/mod.rs +++ b/thaw/src/color_picker/mod.rs @@ -1,20 +1,22 @@ mod color; mod theme; +pub use color::*; +pub use theme::ColorPickerTheme; + use crate::{ components::{Binder, Follower, FollowerPlacement}, use_theme, utils::{class_list::class_list, mount_style, Model, OptionalProp}, Theme, }; -pub use color::*; use leptos::leptos_dom::helpers::WindowListenerHandle; use leptos::*; -pub use theme::ColorPickerTheme; +use palette::{Hsv, IntoColor, Srgb}; #[component] pub fn ColorPicker( - #[prop(optional, into)] value: Model, + #[prop(optional, into)] value: Model, #[prop(optional, into)] class: OptionalProp>, ) -> impl IntoView { mount_style("color-picker", include_str!("./color-picker.css")); @@ -28,22 +30,51 @@ pub fn ColorPicker( }) }); - let hue = create_rw_signal(0); - let sv = create_rw_signal((0.0, 0.0)); + let hue = create_rw_signal(0f32); + let sv = create_rw_signal((0f32, 0f32)); let label = create_rw_signal(String::new()); let style = create_memo(move |_| { let mut style = String::new(); - value.with(|value| { - let value = value.to_hex_string(); - style.push_str(&format!("background-color: {value};")); + value.with(|color| { let (s, v) = sv.get_untracked(); if s < 0.5 && v > 0.5 { style.push_str("color: #000;"); } else { style.push_str("color: #fff;"); } - label.set(value); + match color { + Color::RGB(rgb) => { + let rgb = Srgb::::from_format(rgb.clone()); + let color = format!("rgb({}, {}, {})", rgb.red, rgb.green, rgb.blue); + style.push_str(&format!("background-color: {color};")); + label.set(color); + } + Color::HSV(hsv) => { + let rgb: Srgb = hsv.clone().into_color(); + let rgb = Srgb::::from_format(rgb); + let color = format!("rgb({}, {}, {})", rgb.red, rgb.green, rgb.blue); + style.push_str(&format!("background-color: {color};")); + + let color = format!( + "hsv({}, {:.0}%, {:.0}%)", + hsv.hue.into_inner(), + hsv.saturation * 100.0, + hsv.value * 100.0 + ); + label.set(color); + } + Color::HSL(hsl) => { + let color = format!( + "hsl({}, {:.0}%, {:.0}%)", + hsl.hue.into_inner(), + hsl.saturation * 100.0, + hsl.lightness * 100.0 + ); + style.push_str(&format!("background-color: {color};")); + label.set(color); + } + } }); style @@ -53,16 +84,28 @@ pub fn ColorPicker( let (s, v) = sv.get(); let hue_value = hue.get(); if prev.is_none() { - let HSV { + let hsv = match value.get_untracked() { + Color::RGB(rgb) => rgb.into_color(), + Color::HSV(hsv) => hsv, + Color::HSL(hsl) => hsl.into_color(), + }; + let Hsv { hue: h, saturation: s, value: v, .. - } = value.get_untracked().into(); - hue.set(h); - sv.set((s, v)) + } = hsv; + hue.set(h.into_inner()); + sv.set((s.into(), v.into())) } else { - value.set(RGBA::from(HSV::new(hue_value, s, v))); + value.update(|color| { + let new_hsv: Hsv = Hsv::new(hue_value, s, v); + match color { + Color::RGB(rgb) => *rgb = new_hsv.into_color(), + Color::HSV(hsv) => *hsv = new_hsv, + Color::HSL(hsl) => *hsl = new_hsv.into_color(), + } + }); } }); @@ -124,7 +167,7 @@ pub fn ColorPicker( } #[component] -fn ColorPanel(hue: ReadSignal, sv: RwSignal<(f64, f64)>) -> impl IntoView { +fn ColorPanel(hue: ReadSignal, sv: RwSignal<(f32, f32)>) -> impl IntoView { let panel_ref = create_node_ref::(); let mouse = store_value(Vec::::new()); @@ -143,14 +186,14 @@ fn ColorPanel(hue: ReadSignal, sv: RwSignal<(f64, f64)>) -> impl IntoView { } else if v < 0.0 { 0.0 } else { - v + format!("{:.2}", v).parse::().unwrap() }; let s = if s > 1.0 { 1.0 } else if s < 0.0 { 0.0 } else { - s + format!("{:.2}", s).parse::().unwrap() }; sv.set((s, v)) @@ -197,7 +240,7 @@ fn ColorPanel(hue: ReadSignal, sv: RwSignal<(f64, f64)>) -> impl IntoView { } #[component] -fn HueSlider(hue: RwSignal) -> impl IntoView { +fn HueSlider(hue: RwSignal) -> impl IntoView { let rail_ref = create_node_ref::(); let mouse = store_value(Vec::::new()); @@ -208,11 +251,11 @@ fn HueSlider(hue: RwSignal) -> impl IntoView { let ev_x = f64::from(ev.x()); let value = (ev_x - rect.left() - 6.0) / (rect.width() - 12.0) * 359.0; let value = if value < 0.0 { - 0 + 0.0 } else if value > 359.0 { - 359 + 359.0 } else { - value.round().to_string().parse::().unwrap() + value.round().to_string().parse::().unwrap() }; hue.set(value); }