1use std::cell::Cell;
27use std::rc::Rc;
28use std::sync::Arc;
29
30use crate::color::Color;
31use crate::draw_ctx::DrawCtx;
32use crate::event::{Event, EventResult, MouseButton};
33use crate::geometry::{Point, Rect, Size};
34use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
35use crate::text::Font;
36use crate::widget::{paint_subtree, Widget};
37use crate::widgets::button::Button;
38use crate::widgets::checkbox::Checkbox;
39
40const SWATCH_H: f64 = 22.0;
43const SWATCH_MIN_W: f64 = 48.0;
44
45const PANEL_W: f64 = 228.0;
46const PAD: f64 = 8.0;
47const ROW_GAP: f64 = 6.0;
48
49const HUE_H: f64 = 16.0;
50const SV_H: f64 = 140.0;
51const ALPHA_H: f64 = 16.0;
52const HEX_H: f64 = 20.0;
53const CHECK_H: f64 = 20.0;
54const BTN_H: f64 = 26.0;
55
56fn panel_body_h(allow_none: bool) -> f64 {
58 let mut h = PAD;
59 h += HUE_H + ROW_GAP;
60 h += SV_H + ROW_GAP;
61 h += ALPHA_H + ROW_GAP;
62 h += HEX_H + ROW_GAP;
63 if allow_none {
64 h += CHECK_H + ROW_GAP;
65 }
66 h += BTN_H + PAD;
67 h
68}
69
70fn rgb_to_hsv(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
73 let max = r.max(g).max(b);
74 let min = r.min(g).min(b);
75 let d = max - min;
76 let v = max;
77 let s = if max <= 0.0 { 0.0 } else { d / max };
78 let h = if d <= 0.0 {
79 0.0
80 } else if max == r {
81 60.0 * (((g - b) / d) % 6.0)
82 } else if max == g {
83 60.0 * (((b - r) / d) + 2.0)
84 } else {
85 60.0 * (((r - g) / d) + 4.0)
86 };
87 let h = if h < 0.0 { h + 360.0 } else { h };
88 (h / 360.0, s, v)
89}
90
91fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (f32, f32, f32) {
92 let h6 = (h * 6.0) % 6.0;
93 let c = v * s;
94 let x = c * (1.0 - (h6 % 2.0 - 1.0).abs());
95 let (r1, g1, b1) = match h6 as i32 {
96 0 => (c, x, 0.0),
97 1 => (x, c, 0.0),
98 2 => (0.0, c, x),
99 3 => (0.0, x, c),
100 4 => (x, 0.0, c),
101 _ => (c, 0.0, x),
102 };
103 let m = v - c;
104 (r1 + m, g1 + m, b1 + m)
105}
106
107fn format_hex(c: Color) -> String {
108 let r = (c.r * 255.0).clamp(0.0, 255.0) as u32;
109 let g = (c.g * 255.0).clamp(0.0, 255.0) as u32;
110 let b = (c.b * 255.0).clamp(0.0, 255.0) as u32;
111 let a = (c.a * 255.0).clamp(0.0, 255.0) as u32;
112 format!("#{:02X}{:02X}{:02X}{:02X}", r, g, b, a)
113}
114
115#[derive(Clone, Copy, Debug, PartialEq)]
118enum Drag {
119 None,
120 Hue,
121 Sv,
122 Alpha,
123}
124
125pub struct ColorPicker {
129 bounds: Rect,
130 children: Vec<Box<dyn Widget>>, base: WidgetBase,
132
133 font: Arc<Font>,
134 font_size: f64,
135
136 color_cell: Rc<Cell<Color>>,
139
140 saved: Color,
142
143 open: bool,
145 h: f32,
146 s: f32,
147 v: f32,
148 a: f32,
149 no_color: bool,
152 allow_none: bool,
153
154 drag: Drag,
156
157 hovered: bool,
159
160 on_select: Option<Box<dyn FnMut(Color)>>,
162
163 idx_cancel: usize,
166 idx_select: usize,
167 idx_none: Option<usize>,
168
169 none_cell: Rc<Cell<bool>>,
172 cancel_flag: Rc<Cell<bool>>,
174 select_flag: Rc<Cell<bool>>,
175}
176
177impl ColorPicker {
178 pub fn new(color_cell: Rc<Cell<Color>>, font: Arc<Font>) -> Self {
179 let initial = color_cell.get();
180 let (h, s, v) = rgb_to_hsv(initial.r, initial.g, initial.b);
181 let none_cell = Rc::new(Cell::new(false));
182 let cancel_flag = Rc::new(Cell::new(false));
183 let select_flag = Rc::new(Cell::new(false));
184
185 let mut me = Self {
186 bounds: Rect::default(),
187 children: Vec::new(),
188 base: WidgetBase::new(),
189 font: Arc::clone(&font),
190 font_size: 13.0,
191 color_cell,
192 saved: initial,
193 open: false,
194 h,
195 s,
196 v,
197 a: initial.a,
198 no_color: initial.a <= 0.0,
199 allow_none: false,
200 drag: Drag::None,
201 hovered: false,
202 on_select: None,
203 idx_cancel: 0,
204 idx_select: 1,
205 idx_none: None,
206 none_cell,
207 cancel_flag,
208 select_flag,
209 };
210 me.build_children();
211 me
212 }
213
214 pub fn with_font_size(mut self, s: f64) -> Self {
215 self.font_size = s;
216 self
217 }
218 pub fn with_allow_none(mut self, allow: bool) -> Self {
219 self.allow_none = allow;
220 self.build_children();
221 self
222 }
223 pub fn on_select(mut self, cb: impl FnMut(Color) + 'static) -> Self {
224 self.on_select = Some(Box::new(cb));
225 self
226 }
227
228 pub fn with_margin(mut self, m: Insets) -> Self {
229 self.base.margin = m;
230 self
231 }
232 pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
233 self.base.h_anchor = h;
234 self
235 }
236 pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
237 self.base.v_anchor = v;
238 self
239 }
240 pub fn with_min_size(mut self, s: Size) -> Self {
241 self.base.min_size = s;
242 self
243 }
244 pub fn with_max_size(mut self, s: Size) -> Self {
245 self.base.max_size = s;
246 self
247 }
248
249 fn build_children(&mut self) {
250 self.children.clear();
251
252 let cf = Rc::clone(&self.cancel_flag);
253 let sf = Rc::clone(&self.select_flag);
254
255 let cancel = Button::new("Cancel", Arc::clone(&self.font)).on_click(move || cf.set(true));
256 let select = Button::new("Select", Arc::clone(&self.font)).on_click(move || sf.set(true));
257
258 if self.allow_none {
259 let none_check = Checkbox::new(
260 "No Color (Pass Through)",
261 Arc::clone(&self.font),
262 self.no_color,
263 )
264 .with_font_size(self.font_size)
265 .with_state_cell(Rc::clone(&self.none_cell));
266 self.children.push(Box::new(none_check));
267 self.idx_none = Some(0);
268 self.idx_cancel = 1;
269 self.idx_select = 2;
270 } else {
271 self.idx_none = None;
272 self.idx_cancel = 0;
273 self.idx_select = 1;
274 }
275 self.children.push(Box::new(cancel));
276 self.children.push(Box::new(select));
277 }
278
279 fn sync_color_from_hsva(&self) -> Color {
280 if self.no_color {
281 Color::transparent()
282 } else {
283 let (r, g, b) = hsv_to_rgb(self.h, self.s, self.v);
284 Color::rgba(r, g, b, self.a)
285 }
286 }
287
288 fn commit(&mut self) {
289 let c = self.sync_color_from_hsva();
290 self.color_cell.set(c);
291 if let Some(cb) = self.on_select.as_mut() {
292 cb(c);
293 }
294 self.open = false;
295 }
296
297 fn cancel(&mut self) {
298 self.color_cell.set(self.saved);
299 let (h, s, v) = rgb_to_hsv(self.saved.r, self.saved.g, self.saved.b);
300 self.h = h;
301 self.s = s;
302 self.v = v;
303 self.a = self.saved.a;
304 self.no_color = self.saved.a <= 0.0;
305 self.none_cell.set(self.no_color);
306 self.open = false;
307 }
308
309 fn regions(&self) -> PanelRegions {
313 let w = self.bounds.width;
314 let h = self.bounds.height;
315
316 let swatch = Rect::new(0.0, h - SWATCH_H, w, SWATCH_H);
317
318 let mut y = h - SWATCH_H - PAD;
320
321 y -= HUE_H;
322 let hue = Rect::new(PAD, y, w - PAD * 2.0, HUE_H);
323 y -= ROW_GAP;
324
325 y -= SV_H;
326 let sv = Rect::new(PAD, y, w - PAD * 2.0, SV_H);
327 y -= ROW_GAP;
328
329 y -= ALPHA_H;
330 let alpha = Rect::new(PAD, y, w - PAD * 2.0, ALPHA_H);
331 y -= ROW_GAP;
332
333 y -= HEX_H;
334 let hex = Rect::new(PAD, y, w - PAD * 2.0, HEX_H);
335 y -= ROW_GAP;
336
337 let none = if self.allow_none {
338 y -= CHECK_H;
339 let r = Rect::new(PAD, y, w - PAD * 2.0, CHECK_H);
340 Some(r)
341 } else {
342 None
343 };
344 let _ = y;
345
346 let btns_y = PAD;
347 let btn_w = (w - PAD * 3.0) * 0.5;
348 let cancel = Rect::new(PAD, btns_y, btn_w, BTN_H);
349 let select = Rect::new(PAD + btn_w + PAD, btns_y, btn_w, BTN_H);
350
351 PanelRegions {
352 swatch,
353 hue,
354 sv,
355 alpha,
356 hex,
357 none,
358 cancel,
359 select,
360 }
361 }
362}
363
364struct PanelRegions {
365 swatch: Rect,
366 hue: Rect,
367 sv: Rect,
368 alpha: Rect,
369 hex: Rect,
370 none: Option<Rect>,
371 cancel: Rect,
372 select: Rect,
373}
374
375mod widget_impl;