mirror of
https://github.com/adoyle0/thaw.git
synced 2025-02-02 16:44:15 -05:00
feat: rewrite color (#98)
This commit is contained in:
parent
70553b8069
commit
60eb4dbb56
5 changed files with 110 additions and 171 deletions
|
@ -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"]
|
||||||
|
|
|
@ -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. |
|
||||||
|
|
|
@ -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"]
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue