Initial Commit
This commit is contained in:
225
frontend/src/components/color_picker.rs
Normal file
225
frontend/src/components/color_picker.rs
Normal file
@ -0,0 +1,225 @@
|
||||
use crate::css::C;
|
||||
use lighter_lib::BulbColor;
|
||||
use seed::prelude::*;
|
||||
use seed::{attrs, div, C};
|
||||
use std::f32::consts::PI;
|
||||
use web_sys::MouseEvent;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ColorPicker {
|
||||
hue: f32,
|
||||
saturation: f32,
|
||||
brightness: f32,
|
||||
temperature: f32,
|
||||
|
||||
mode: ColorPickerSetting,
|
||||
dragging: Option<ColorPickerAttr>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
enum ColorPickerSetting {
|
||||
#[default]
|
||||
HSB,
|
||||
Kelvin,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum ColorPickerAttr {
|
||||
HueSat,
|
||||
Brightness,
|
||||
Temperature,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ColorPickerMsg {
|
||||
MouseDown(ColorPickerAttr, MouseEvent),
|
||||
MouseMove(ColorPickerAttr, MouseEvent),
|
||||
MouseUp(Option<ColorPickerAttr>, MouseEvent),
|
||||
|
||||
SetColor(BulbColor),
|
||||
}
|
||||
|
||||
impl ColorPicker {
|
||||
pub fn update(&mut self, msg: ColorPickerMsg, orders: &mut impl Orders<ColorPickerMsg>) {
|
||||
match msg {
|
||||
ColorPickerMsg::MouseDown(target, event) => {
|
||||
self.dragging = Some(target);
|
||||
self.handle_mouse_event(target, event);
|
||||
}
|
||||
ColorPickerMsg::MouseMove(target, event) => {
|
||||
self.handle_mouse_event(target, event);
|
||||
}
|
||||
ColorPickerMsg::MouseUp(target, event) => {
|
||||
if let Some(target) = target {
|
||||
self.handle_mouse_event(target, event);
|
||||
}
|
||||
|
||||
match self.dragging.take() {
|
||||
Some(ColorPickerAttr::HueSat) => self.mode = ColorPickerSetting::HSB,
|
||||
Some(ColorPickerAttr::Brightness) => {}
|
||||
Some(ColorPickerAttr::Temperature) => self.mode = ColorPickerSetting::Kelvin,
|
||||
None => return,
|
||||
}
|
||||
|
||||
let color = match self.mode {
|
||||
ColorPickerSetting::HSB => {
|
||||
BulbColor::hsb(self.hue, self.saturation, self.brightness)
|
||||
}
|
||||
ColorPickerSetting::Kelvin => {
|
||||
BulbColor::kelvin(self.temperature, self.brightness)
|
||||
}
|
||||
};
|
||||
|
||||
orders.send_msg(ColorPickerMsg::SetColor(color));
|
||||
}
|
||||
ColorPickerMsg::SetColor { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_mouse_event(&mut self, target: ColorPickerAttr, event: MouseEvent) {
|
||||
if Some(target) != self.dragging {
|
||||
return;
|
||||
}
|
||||
|
||||
let handle_bar = || {
|
||||
let y = event.offset_y() as f32;
|
||||
let height = 200.0;
|
||||
1.0 - (y / height).clamp(0.0, 1.0)
|
||||
};
|
||||
|
||||
match target {
|
||||
ColorPickerAttr::HueSat => {
|
||||
let (x, y) = (event.offset_x() as f32, event.offset_y() as f32);
|
||||
let radius = 100.0;
|
||||
|
||||
let x = x - radius;
|
||||
let y = y - radius;
|
||||
|
||||
let angle = (x.atan2(y) + PI) / (2.0 * PI);
|
||||
self.hue = 1.0 - angle;
|
||||
self.saturation = ((x.powi(2) + y.powi(2)).sqrt() / radius).clamp(0.0, 1.0);
|
||||
}
|
||||
ColorPickerAttr::Brightness => self.brightness = handle_bar(),
|
||||
ColorPickerAttr::Temperature => self.temperature = handle_bar(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view(&self) -> Node<ColorPickerMsg> {
|
||||
use ColorPickerAttr::{Brightness, HueSat, Temperature};
|
||||
use ColorPickerMsg::{MouseDown, MouseMove, MouseUp};
|
||||
|
||||
let (hs_x, hs_y) = {
|
||||
let radius = 100.0;
|
||||
let angle = -self.hue * PI * 2.0;
|
||||
let x = -self.saturation * radius * angle.sin();
|
||||
let y = -self.saturation * radius * angle.cos();
|
||||
(x + radius - 5.0, y + radius - 5.0)
|
||||
};
|
||||
|
||||
let br_y = {
|
||||
let height = 200.0;
|
||||
let marker_height = 10.0;
|
||||
(1.0 - self.brightness) * height - marker_height / 2.0
|
||||
};
|
||||
|
||||
let temp_y = {
|
||||
let height = 200.0;
|
||||
let marker_height = 10.0;
|
||||
(1.0 - self.temperature) * height - marker_height / 2.0
|
||||
};
|
||||
|
||||
let (r, g, b) = hsb_to_rgb(self.hue, 1.0, 1.0);
|
||||
let saturation_gradient = match self.mode {
|
||||
ColorPickerSetting::HSB => {
|
||||
format!("background: linear-gradient(0deg, #000, rgba({r},{g},{b},1));")
|
||||
}
|
||||
ColorPickerSetting::Kelvin => format!("background: linear-gradient(0deg, #000, #fff);"),
|
||||
};
|
||||
|
||||
div![
|
||||
C![C.color_picker],
|
||||
mouse_ev(Ev::MouseUp, |ev| MouseUp(None, ev)),
|
||||
div![
|
||||
C![C.color_wheel],
|
||||
div![
|
||||
C![C.color_wheel_marker],
|
||||
attrs! {
|
||||
At::Style => format!("margin-left: {hs_x}px; margin-top: {hs_y}px;"),
|
||||
},
|
||||
],
|
||||
mouse_ev(Ev::MouseDown, |ev| MouseDown(HueSat, ev)),
|
||||
mouse_ev(Ev::MouseMove, |ev| MouseMove(HueSat, ev)),
|
||||
mouse_ev(Ev::MouseUp, |ev| MouseUp(Some(HueSat), ev)),
|
||||
//mouse_ev(Ev::MouseLeave, |ev| MouseUp(HueSat, ev)),
|
||||
],
|
||||
div![
|
||||
C![C.brightness_bar],
|
||||
attrs! { At::Style => saturation_gradient },
|
||||
div![
|
||||
C![C.color_bar_marker],
|
||||
attrs! {
|
||||
At::Style => format!("margin-top: {br_y}px;"),
|
||||
},
|
||||
],
|
||||
mouse_ev(Ev::MouseDown, |ev| MouseDown(Brightness, ev)),
|
||||
mouse_ev(Ev::MouseMove, |ev| MouseMove(Brightness, ev)),
|
||||
mouse_ev(Ev::MouseUp, |ev| MouseUp(Some(Brightness), ev)),
|
||||
//mouse_ev(Ev::MouseLeave, |ev| MouseUp(Brightness, ev)),
|
||||
],
|
||||
div![
|
||||
C![C.temperature_bar],
|
||||
div![
|
||||
C![C.color_bar_marker],
|
||||
attrs! {
|
||||
At::Style => format!("margin-top: {temp_y}px;"),
|
||||
},
|
||||
],
|
||||
mouse_ev(Ev::MouseDown, |ev| MouseDown(Temperature, ev)),
|
||||
mouse_ev(Ev::MouseMove, |ev| MouseMove(Temperature, ev)),
|
||||
mouse_ev(Ev::MouseUp, |ev| MouseUp(Some(Temperature), ev)),
|
||||
//mouse_ev(Ev::MouseLeave, |ev| MouseUp(Temperature, ev)),
|
||||
],
|
||||
]
|
||||
}
|
||||
|
||||
pub fn set_hsb(&mut self, h: f32, s: f32, b: f32) {
|
||||
self.hue = h;
|
||||
self.saturation = s;
|
||||
self.brightness = b;
|
||||
self.mode = ColorPickerSetting::HSB;
|
||||
}
|
||||
|
||||
pub fn set_kelvin(&mut self, t: f32, b: f32) {
|
||||
self.temperature = t;
|
||||
self.brightness = b;
|
||||
self.mode = ColorPickerSetting::Kelvin;
|
||||
}
|
||||
|
||||
pub fn set_color(&mut self, color: BulbColor) {
|
||||
match color {
|
||||
BulbColor::HSB { h, s, b } => self.set_hsb(h, s, b),
|
||||
BulbColor::Kelvin { t, b } => self.set_kelvin(t, b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn hsb_to_rgb(h: f32, s: f32, b: f32) -> (u8, u8, u8) {
|
||||
let h = 360.0 * h.clamp(0.0, 1.0);
|
||||
|
||||
let c = b * s; // chroma
|
||||
|
||||
let x = c * (1. - ((h / 60.) % 2. - 1.).abs());
|
||||
let m = b - c;
|
||||
let m = |v| ((v + m) * 255.0) as u8;
|
||||
|
||||
let (r, g, b) = match h {
|
||||
_ if h < 60. => (c, x, 0.0),
|
||||
_ if h < 120.0 => (x, c, 0.0),
|
||||
_ if h < 180.0 => (0.0, c, x),
|
||||
_ if h < 240.0 => (0.0, x, c),
|
||||
_ if h < 300.0 => (x, 0.0, c),
|
||||
_ => (c, 0.0, x),
|
||||
};
|
||||
|
||||
(m(r), m(g), m(b))
|
||||
}
|
||||
1
frontend/src/components/mod.rs
Normal file
1
frontend/src/components/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod color_picker;
|
||||
Reference in New Issue
Block a user