feat: rewrite color (#98)

This commit is contained in:
luoxiaozero 2024-02-07 21:42:34 +08:00
parent 70553b8069
commit 60eb4dbb56
5 changed files with 110 additions and 171 deletions

View file

@ -14,6 +14,7 @@ leptos_devtools = { version = "0.0.1", optional = true}
thaw = { path = "../thaw" } thaw = { path = "../thaw" }
demo_markdown = { path = "../demo_markdown" } demo_markdown = { path = "../demo_markdown" }
icondata = "0.3.0" icondata = "0.3.0"
palette = "0.7.4"
[features] [features]
default = ["csr"] default = ["csr"]

View file

@ -1,16 +1,38 @@
# Color Picker # Color Picker
```rust demo ```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! { view! {
<ColorPicker value/> <ColorPicker value/>
} }
``` ```
### 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! {
<Space vertical=true>
<ColorPicker value=rgb/>
<ColorPicker value=hsv/>
<ColorPicker value=hsl/>
</Space>
}
```
### DatePicker Props ### DatePicker Props
| Name | Type | Default | Desciption | | Name | Type | Default | Desciption |
| ----- | --------------------- | -------------------- | ----------------------------------------------- | | ----- | ----------------------------------- | -------------------- | ----------------------------------------------- |
| class | `MaybeSignal<String>` | `Default::default()` | Addtional classes for the color picker element. | | class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Addtional classes for the color picker element. |
| value | `RwSignal<RGBA>` | `Default::default()` | Value of the picker. | | value | `Model<Color>` | `Default::default()` | Value of the picker. |

View file

@ -27,6 +27,7 @@ icondata_ai = "0.0.10"
uuid = { version = "1.7.0", features = ["v4"] } uuid = { version = "1.7.0", features = ["v4"] }
cfg-if = "1.0.0" cfg-if = "1.0.0"
chrono = "0.4.33" chrono = "0.4.33"
palette = "0.7.4"
[features] [features]
csr = ["leptos/csr"] csr = ["leptos/csr"]

View file

@ -1,160 +1,32 @@
use palette::{Hsl, Hsv, Srgb};
#[derive(Clone)] #[derive(Clone)]
pub struct RGBA { pub enum Color {
pub red: u8, RGB(Srgb),
pub green: u8, HSV(Hsv),
pub blue: u8, HSL(Hsl),
pub alpha: u8,
} }
impl Default for RGBA { impl Default for Color {
fn default() -> Self { fn default() -> Self {
Self { Self::RGB(Srgb::new(0.0, 0.0, 0.0))
red: Default::default(),
green: Default::default(),
blue: Default::default(),
alpha: u8::MAX,
}
} }
} }
impl RGBA { impl From<Srgb> for Color {
pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self { fn from(value: Srgb) -> Self {
Self { Self::RGB(value)
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<HSV> for RGBA { impl From<Hsv> for Color {
fn from(value: HSV) -> Self { fn from(value: Hsv) -> Self {
let HSV { Self::HSV(value)
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)
} }
} }
#[derive(Clone)] impl From<Hsl> for Color {
pub struct HSV { fn from(value: Hsl) -> Self {
pub hue: u16, Self::HSL(value)
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<RGBA> 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,
}
} }
} }

View file

@ -1,20 +1,22 @@
mod color; mod color;
mod theme; mod theme;
pub use color::*;
pub use theme::ColorPickerTheme;
use crate::{ use crate::{
components::{Binder, Follower, FollowerPlacement}, components::{Binder, Follower, FollowerPlacement},
use_theme, use_theme,
utils::{class_list::class_list, mount_style, Model, OptionalProp}, utils::{class_list::class_list, mount_style, Model, OptionalProp},
Theme, Theme,
}; };
pub use color::*;
use leptos::leptos_dom::helpers::WindowListenerHandle; use leptos::leptos_dom::helpers::WindowListenerHandle;
use leptos::*; use leptos::*;
pub use theme::ColorPickerTheme; use palette::{Hsv, IntoColor, Srgb};
#[component] #[component]
pub fn ColorPicker( pub fn ColorPicker(
#[prop(optional, into)] value: Model<RGBA>, #[prop(optional, into)] value: Model<Color>,
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>, #[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
) -> impl IntoView { ) -> impl IntoView {
mount_style("color-picker", include_str!("./color-picker.css")); mount_style("color-picker", include_str!("./color-picker.css"));
@ -28,22 +30,51 @@ pub fn ColorPicker(
}) })
}); });
let hue = create_rw_signal(0); let hue = create_rw_signal(0f32);
let sv = create_rw_signal((0.0, 0.0)); let sv = create_rw_signal((0f32, 0f32));
let label = create_rw_signal(String::new()); let label = create_rw_signal(String::new());
let style = create_memo(move |_| { let style = create_memo(move |_| {
let mut style = String::new(); let mut style = String::new();
value.with(|value| { value.with(|color| {
let value = value.to_hex_string();
style.push_str(&format!("background-color: {value};"));
let (s, v) = sv.get_untracked(); let (s, v) = sv.get_untracked();
if s < 0.5 && v > 0.5 { if s < 0.5 && v > 0.5 {
style.push_str("color: #000;"); style.push_str("color: #000;");
} else { } else {
style.push_str("color: #fff;"); style.push_str("color: #fff;");
} }
label.set(value); match color {
Color::RGB(rgb) => {
let rgb = Srgb::<u8>::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::<u8>::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 style
@ -53,16 +84,28 @@ pub fn ColorPicker(
let (s, v) = sv.get(); let (s, v) = sv.get();
let hue_value = hue.get(); let hue_value = hue.get();
if prev.is_none() { 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, hue: h,
saturation: s, saturation: s,
value: v, value: v,
.. ..
} = value.get_untracked().into(); } = hsv;
hue.set(h); hue.set(h.into_inner());
sv.set((s, v)) sv.set((s.into(), v.into()))
} else { } 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] #[component]
fn ColorPanel(hue: ReadSignal<u16>, sv: RwSignal<(f64, f64)>) -> impl IntoView { fn ColorPanel(hue: ReadSignal<f32>, sv: RwSignal<(f32, f32)>) -> impl IntoView {
let panel_ref = create_node_ref::<html::Div>(); let panel_ref = create_node_ref::<html::Div>();
let mouse = store_value(Vec::<WindowListenerHandle>::new()); let mouse = store_value(Vec::<WindowListenerHandle>::new());
@ -143,14 +186,14 @@ fn ColorPanel(hue: ReadSignal<u16>, sv: RwSignal<(f64, f64)>) -> impl IntoView {
} else if v < 0.0 { } else if v < 0.0 {
0.0 0.0
} else { } else {
v format!("{:.2}", v).parse::<f32>().unwrap()
}; };
let s = if s > 1.0 { let s = if s > 1.0 {
1.0 1.0
} else if s < 0.0 { } else if s < 0.0 {
0.0 0.0
} else { } else {
s format!("{:.2}", s).parse::<f32>().unwrap()
}; };
sv.set((s, v)) sv.set((s, v))
@ -197,7 +240,7 @@ fn ColorPanel(hue: ReadSignal<u16>, sv: RwSignal<(f64, f64)>) -> impl IntoView {
} }
#[component] #[component]
fn HueSlider(hue: RwSignal<u16>) -> impl IntoView { fn HueSlider(hue: RwSignal<f32>) -> impl IntoView {
let rail_ref = create_node_ref::<html::Div>(); let rail_ref = create_node_ref::<html::Div>();
let mouse = store_value(Vec::<WindowListenerHandle>::new()); let mouse = store_value(Vec::<WindowListenerHandle>::new());
@ -208,11 +251,11 @@ fn HueSlider(hue: RwSignal<u16>) -> impl IntoView {
let ev_x = f64::from(ev.x()); let ev_x = f64::from(ev.x());
let value = (ev_x - rect.left() - 6.0) / (rect.width() - 12.0) * 359.0; let value = (ev_x - rect.left() - 6.0) / (rect.width() - 12.0) * 359.0;
let value = if value < 0.0 { let value = if value < 0.0 {
0 0.0
} else if value > 359.0 { } else if value > 359.0 {
359 359.0
} else { } else {
value.round().to_string().parse::<u16>().unwrap() value.round().to_string().parse::<f32>().unwrap()
}; };
hue.set(value); hue.set(value);
} }