use std::cell::Cell;
use std::rc::Rc;
use std::sync::Arc;
use crate::color::Color;
use crate::draw_ctx::DrawCtx;
use crate::event::{Event, EventResult, MouseButton};
use crate::geometry::{Point, Rect, Size};
use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
use crate::text::Font;
use crate::widget::{paint_subtree, Widget};
use crate::widgets::button::Button;
use crate::widgets::checkbox::Checkbox;
const SWATCH_H: f64 = 22.0;
const SWATCH_MIN_W: f64 = 48.0;
const PANEL_W: f64 = 228.0;
const PAD: f64 = 8.0;
const ROW_GAP: f64 = 6.0;
const HUE_H: f64 = 16.0;
const SV_H: f64 = 140.0;
const ALPHA_H: f64 = 16.0;
const HEX_H: f64 = 20.0;
const CHECK_H: f64 = 20.0;
const BTN_H: f64 = 26.0;
fn panel_body_h(allow_none: bool) -> f64 {
let mut h = PAD;
h += HUE_H + ROW_GAP;
h += SV_H + ROW_GAP;
h += ALPHA_H + ROW_GAP;
h += HEX_H + ROW_GAP;
if allow_none {
h += CHECK_H + ROW_GAP;
}
h += BTN_H + PAD;
h
}
fn rgb_to_hsv(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
let max = r.max(g).max(b);
let min = r.min(g).min(b);
let d = max - min;
let v = max;
let s = if max <= 0.0 { 0.0 } else { d / max };
let h = if d <= 0.0 {
0.0
} else if max == r {
60.0 * (((g - b) / d) % 6.0)
} else if max == g {
60.0 * (((b - r) / d) + 2.0)
} else {
60.0 * (((r - g) / d) + 4.0)
};
let h = if h < 0.0 { h + 360.0 } else { h };
(h / 360.0, s, v)
}
fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (f32, f32, f32) {
let h6 = (h * 6.0) % 6.0;
let c = v * s;
let x = c * (1.0 - (h6 % 2.0 - 1.0).abs());
let (r1, g1, b1) = match h6 as i32 {
0 => (c, x, 0.0),
1 => (x, c, 0.0),
2 => (0.0, c, x),
3 => (0.0, x, c),
4 => (x, 0.0, c),
_ => (c, 0.0, x),
};
let m = v - c;
(r1 + m, g1 + m, b1 + m)
}
fn format_hex(c: Color) -> String {
let r = (c.r * 255.0).clamp(0.0, 255.0) as u32;
let g = (c.g * 255.0).clamp(0.0, 255.0) as u32;
let b = (c.b * 255.0).clamp(0.0, 255.0) as u32;
let a = (c.a * 255.0).clamp(0.0, 255.0) as u32;
format!("#{:02X}{:02X}{:02X}{:02X}", r, g, b, a)
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum Drag {
None,
Hue,
Sv,
Alpha,
}
pub struct ColorPicker {
bounds: Rect,
children: Vec<Box<dyn Widget>>, base: WidgetBase,
font: Arc<Font>,
font_size: f64,
color_cell: Rc<Cell<Color>>,
saved: Color,
open: bool,
h: f32,
s: f32,
v: f32,
a: f32,
no_color: bool,
allow_none: bool,
drag: Drag,
hovered: bool,
on_select: Option<Box<dyn FnMut(Color)>>,
idx_cancel: usize,
idx_select: usize,
idx_none: Option<usize>,
none_cell: Rc<Cell<bool>>,
cancel_flag: Rc<Cell<bool>>,
select_flag: Rc<Cell<bool>>,
}
impl ColorPicker {
pub fn new(color_cell: Rc<Cell<Color>>, font: Arc<Font>) -> Self {
let initial = color_cell.get();
let (h, s, v) = rgb_to_hsv(initial.r, initial.g, initial.b);
let none_cell = Rc::new(Cell::new(false));
let cancel_flag = Rc::new(Cell::new(false));
let select_flag = Rc::new(Cell::new(false));
let mut me = Self {
bounds: Rect::default(),
children: Vec::new(),
base: WidgetBase::new(),
font: Arc::clone(&font),
font_size: 13.0,
color_cell,
saved: initial,
open: false,
h,
s,
v,
a: initial.a,
no_color: initial.a <= 0.0,
allow_none: false,
drag: Drag::None,
hovered: false,
on_select: None,
idx_cancel: 0,
idx_select: 1,
idx_none: None,
none_cell,
cancel_flag,
select_flag,
};
me.build_children();
me
}
pub fn with_font_size(mut self, s: f64) -> Self {
self.font_size = s;
self
}
pub fn with_allow_none(mut self, allow: bool) -> Self {
self.allow_none = allow;
self.build_children();
self
}
pub fn on_select(mut self, cb: impl FnMut(Color) + 'static) -> Self {
self.on_select = Some(Box::new(cb));
self
}
pub fn with_margin(mut self, m: Insets) -> Self {
self.base.margin = m;
self
}
pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
self.base.h_anchor = h;
self
}
pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
self.base.v_anchor = v;
self
}
pub fn with_min_size(mut self, s: Size) -> Self {
self.base.min_size = s;
self
}
pub fn with_max_size(mut self, s: Size) -> Self {
self.base.max_size = s;
self
}
fn build_children(&mut self) {
self.children.clear();
let cf = Rc::clone(&self.cancel_flag);
let sf = Rc::clone(&self.select_flag);
let cancel = Button::new("Cancel", Arc::clone(&self.font)).on_click(move || cf.set(true));
let select = Button::new("Select", Arc::clone(&self.font)).on_click(move || sf.set(true));
if self.allow_none {
let none_check = Checkbox::new(
"No Color (Pass Through)",
Arc::clone(&self.font),
self.no_color,
)
.with_font_size(self.font_size)
.with_state_cell(Rc::clone(&self.none_cell));
self.children.push(Box::new(none_check));
self.idx_none = Some(0);
self.idx_cancel = 1;
self.idx_select = 2;
} else {
self.idx_none = None;
self.idx_cancel = 0;
self.idx_select = 1;
}
self.children.push(Box::new(cancel));
self.children.push(Box::new(select));
}
fn sync_color_from_hsva(&self) -> Color {
if self.no_color {
Color::transparent()
} else {
let (r, g, b) = hsv_to_rgb(self.h, self.s, self.v);
Color::rgba(r, g, b, self.a)
}
}
fn commit(&mut self) {
let c = self.sync_color_from_hsva();
self.color_cell.set(c);
if let Some(cb) = self.on_select.as_mut() {
cb(c);
}
self.open = false;
}
fn cancel(&mut self) {
self.color_cell.set(self.saved);
let (h, s, v) = rgb_to_hsv(self.saved.r, self.saved.g, self.saved.b);
self.h = h;
self.s = s;
self.v = v;
self.a = self.saved.a;
self.no_color = self.saved.a <= 0.0;
self.none_cell.set(self.no_color);
self.open = false;
}
fn regions(&self) -> PanelRegions {
let w = self.bounds.width;
let h = self.bounds.height;
let swatch = Rect::new(0.0, h - SWATCH_H, w, SWATCH_H);
let mut y = h - SWATCH_H - PAD;
y -= HUE_H;
let hue = Rect::new(PAD, y, w - PAD * 2.0, HUE_H);
y -= ROW_GAP;
y -= SV_H;
let sv = Rect::new(PAD, y, w - PAD * 2.0, SV_H);
y -= ROW_GAP;
y -= ALPHA_H;
let alpha = Rect::new(PAD, y, w - PAD * 2.0, ALPHA_H);
y -= ROW_GAP;
y -= HEX_H;
let hex = Rect::new(PAD, y, w - PAD * 2.0, HEX_H);
y -= ROW_GAP;
let none = if self.allow_none {
y -= CHECK_H;
let r = Rect::new(PAD, y, w - PAD * 2.0, CHECK_H);
Some(r)
} else {
None
};
let _ = y;
let btns_y = PAD;
let btn_w = (w - PAD * 3.0) * 0.5;
let cancel = Rect::new(PAD, btns_y, btn_w, BTN_H);
let select = Rect::new(PAD + btn_w + PAD, btns_y, btn_w, BTN_H);
PanelRegions {
swatch,
hue,
sv,
alpha,
hex,
none,
cancel,
select,
}
}
}
struct PanelRegions {
swatch: Rect,
hue: Rect,
sv: Rect,
alpha: Rect,
hex: Rect,
none: Option<Rect>,
cancel: Rect,
select: Rect,
}
mod widget_impl;