1use crate::{
2 border::BorderBuilder,
3 brush::Brush,
4 core::{
5 algebra::Vector2,
6 color::{Color, Hsv},
7 math::Rect,
8 pool::Handle,
9 },
10 define_constructor,
11 draw::{CommandTexture, Draw, DrawingContext},
12 grid::{Column, GridBuilder, Row},
13 message::{MessageDirection, MouseButton, UiMessage},
14 numeric::{NumericUpDownBuilder, NumericUpDownMessage},
15 popup::{Placement, PopupBuilder, PopupMessage},
16 text::TextBuilder,
17 widget::{Widget, WidgetBuilder, WidgetMessage},
18 BuildContext, Control, NodeHandleMapping, Orientation, Thickness, UiNode, UserInterface,
19 VerticalAlignment,
20};
21use std::sync::mpsc::Sender;
22use std::{
23 any::{Any, TypeId},
24 ops::{Deref, DerefMut},
25};
26
27#[derive(Debug, Clone, PartialEq)]
28pub enum HueBarMessage {
29 Hue(f32),
31
32 Orientation(Orientation),
34}
35
36impl HueBarMessage {
37 define_constructor!(HueBarMessage:Hue => fn hue(f32), layout: false);
38 define_constructor!(HueBarMessage:Orientation => fn orientation(Orientation), layout: false);
39}
40
41#[derive(Debug, Clone, PartialEq)]
42pub enum AlphaBarMessage {
43 Alpha(f32),
45
46 Orientation(Orientation),
48}
49
50impl AlphaBarMessage {
51 define_constructor!(AlphaBarMessage:Alpha => fn alpha(f32), layout: false);
52 define_constructor!(AlphaBarMessage:Orientation => fn orientation(Orientation), layout: false);
53}
54
55#[derive(Debug, Clone, PartialEq)]
56pub enum SaturationBrightnessFieldMessage {
57 Hue(f32),
59
60 Saturation(f32),
62
63 Brightness(f32),
65}
66
67impl SaturationBrightnessFieldMessage {
68 define_constructor!(SaturationBrightnessFieldMessage:Hue => fn hue(f32), layout: false);
69 define_constructor!(SaturationBrightnessFieldMessage:Saturation => fn saturation(f32), layout: false);
70 define_constructor!(SaturationBrightnessFieldMessage:Brightness => fn brightness(f32), layout: false);
71}
72
73#[derive(Debug, Clone, PartialEq)]
74pub enum ColorPickerMessage {
75 Color(Color),
79
80 Hsv(Hsv),
84}
85
86impl ColorPickerMessage {
87 define_constructor!(ColorPickerMessage:Color => fn color(Color), layout: false);
88 define_constructor!(ColorPickerMessage:Hsv => fn hsv(Hsv), layout: false);
89}
90
91#[derive(Debug, Clone, PartialEq)]
92pub enum ColorFieldMessage {
93 Color(Color),
94}
95
96impl ColorFieldMessage {
97 define_constructor!(ColorFieldMessage:Color => fn color(Color), layout: false);
98}
99
100#[derive(Clone)]
101pub struct AlphaBar {
102 widget: Widget,
103 orientation: Orientation,
104 alpha: f32,
105 is_picking: bool,
106}
107
108crate::define_widget_deref!(AlphaBar);
109
110impl AlphaBar {
111 fn alpha_at(&self, mouse_pos: Vector2<f32>) -> f32 {
112 let relative_pos = mouse_pos - self.screen_position;
113 let k = match self.orientation {
114 Orientation::Vertical => relative_pos.y / self.actual_size().y,
115 Orientation::Horizontal => relative_pos.x / self.actual_size().x,
116 };
117 k.min(1.0).max(0.0) * 255.0
118 }
119}
120
121fn push_gradient_rect(
122 drawing_context: &mut DrawingContext,
123 bounds: &Rect<f32>,
124 orientation: Orientation,
125 prev_k: f32,
126 prev_color: Color,
127 curr_k: f32,
128 curr_color: Color,
129) {
130 match orientation {
131 Orientation::Vertical => {
132 drawing_context.push_triangle_multicolor([
133 (
134 Vector2::new(bounds.x(), bounds.y() + bounds.h() * prev_k),
135 prev_color,
136 ),
137 (
138 Vector2::new(bounds.x() + bounds.w(), bounds.y() + bounds.h() * prev_k),
139 prev_color,
140 ),
141 (
142 Vector2::new(bounds.x(), bounds.y() + bounds.h() * curr_k),
143 curr_color,
144 ),
145 ]);
146 drawing_context.push_triangle_multicolor([
147 (
148 Vector2::new(bounds.x() + bounds.w(), bounds.y() + bounds.h() * prev_k),
149 prev_color,
150 ),
151 (
152 Vector2::new(bounds.x() + bounds.w(), bounds.y() + bounds.h() * curr_k),
153 curr_color,
154 ),
155 (
156 Vector2::new(bounds.x(), bounds.y() + bounds.h() * curr_k),
157 curr_color,
158 ),
159 ]);
160 }
161 Orientation::Horizontal => {
162 drawing_context.push_triangle_multicolor([
163 (
164 Vector2::new(bounds.x() + bounds.w() * prev_k, bounds.y()),
165 prev_color,
166 ),
167 (
168 Vector2::new(bounds.x() + bounds.w() * curr_k, bounds.y()),
169 curr_color,
170 ),
171 (
172 Vector2::new(bounds.x() + bounds.w() * prev_k, bounds.y() + bounds.h()),
173 prev_color,
174 ),
175 ]);
176 drawing_context.push_triangle_multicolor([
177 (
178 Vector2::new(bounds.x() + bounds.w() * curr_k, bounds.y()),
179 curr_color,
180 ),
181 (
182 Vector2::new(bounds.x() + bounds.w() * curr_k, bounds.y() + bounds.h()),
183 curr_color,
184 ),
185 (
186 Vector2::new(bounds.x() + bounds.w() * prev_k, bounds.y() + bounds.h()),
187 prev_color,
188 ),
189 ]);
190 }
191 }
192}
193
194const CHECKERBOARD_SIZE: f32 = 6.0;
195
196impl Control for AlphaBar {
197 fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
198 if type_id == TypeId::of::<Self>() {
199 Some(self)
200 } else {
201 None
202 }
203 }
204
205 fn draw(&self, drawing_context: &mut DrawingContext) {
206 let bounds = self.screen_bounds();
207
208 let h_amount = (bounds.w() / CHECKERBOARD_SIZE).ceil() as usize;
210 let v_amount = (bounds.h() / CHECKERBOARD_SIZE).ceil() as usize;
211 for y in 0..v_amount {
212 for x in 0..h_amount {
213 let rect = Rect::new(
214 bounds.x() + x as f32 * CHECKERBOARD_SIZE,
215 bounds.y() + y as f32 * CHECKERBOARD_SIZE,
216 CHECKERBOARD_SIZE,
217 CHECKERBOARD_SIZE,
218 );
219 let color = if (x + y) & 1 == 0 {
220 Color::opaque(127, 127, 127)
221 } else {
222 Color::WHITE
223 };
224 drawing_context.push_rect_multicolor(&rect, [color; 4]);
225 }
226 }
227 drawing_context.commit(
228 self.clip_bounds(),
229 Brush::Solid(Color::WHITE),
230 CommandTexture::None,
231 None,
232 );
233
234 for alpha in 1..255 {
236 let prev_color = Color::from_rgba(0, 0, 0, alpha - 1);
237 let curr_color = Color::from_rgba(0, 0, 0, alpha);
238 let prev_k = (alpha - 1) as f32 / 255.0;
239 let curr_k = alpha as f32 / 255.0;
240 push_gradient_rect(
241 drawing_context,
242 &bounds,
243 self.orientation,
244 prev_k,
245 prev_color,
246 curr_k,
247 curr_color,
248 );
249 }
250
251 let k = self.alpha / 255.0;
252 match self.orientation {
253 Orientation::Vertical => drawing_context.push_rect_multicolor(
254 &Rect::new(bounds.x(), bounds.y() + bounds.h() * k, bounds.w(), 1.0),
255 [Color::WHITE; 4],
256 ),
257 Orientation::Horizontal => drawing_context.push_rect_multicolor(
258 &Rect::new(bounds.x() + k * bounds.w(), bounds.y(), 1.0, bounds.h()),
259 [Color::WHITE; 4],
260 ),
261 }
262
263 drawing_context.commit(
264 self.clip_bounds(),
265 Brush::Solid(Color::WHITE),
266 CommandTexture::None,
267 None,
268 );
269 }
270
271 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
272 self.widget.handle_routed_message(ui, message);
273
274 if message.destination() == self.handle {
275 if let Some(msg) = message.data::<WidgetMessage>() {
276 if message.direction() == MessageDirection::FromWidget {
277 match *msg {
278 WidgetMessage::MouseDown { button, .. } => {
279 if button == MouseButton::Left {
280 self.is_picking = true;
281 ui.capture_mouse(self.handle);
282 }
283 }
284 WidgetMessage::MouseMove { pos, .. } => {
285 if self.is_picking {
286 ui.send_message(AlphaBarMessage::alpha(
287 self.handle,
288 MessageDirection::ToWidget,
289 self.alpha_at(pos),
290 ))
291 }
292 }
293 WidgetMessage::MouseUp { button, .. } => {
294 if self.is_picking && button == MouseButton::Left {
295 self.is_picking = false;
296 ui.release_mouse_capture();
297 }
298 }
299 _ => (),
300 }
301 }
302 } else if let Some(msg) = message.data::<AlphaBarMessage>() {
303 if message.direction() == MessageDirection::ToWidget {
304 match *msg {
305 AlphaBarMessage::Alpha(alpha) => {
306 if self.alpha != alpha {
307 self.alpha = alpha;
308 ui.send_message(message.reverse());
309 }
310 }
311 AlphaBarMessage::Orientation(orientation) => {
312 if self.orientation != orientation {
313 self.orientation = orientation;
314 ui.send_message(message.reverse());
315 }
316 }
317 }
318 }
319 }
320 }
321 }
322}
323
324pub struct AlphaBarBuilder {
325 widget_builder: WidgetBuilder,
326 orientation: Orientation,
327 alpha: f32,
328}
329
330impl AlphaBarBuilder {
331 pub fn new(widget_builder: WidgetBuilder) -> Self {
332 Self {
333 widget_builder,
334 orientation: Orientation::Vertical,
335 alpha: 255.0,
336 }
337 }
338
339 pub fn with_alpha(mut self, alpha: f32) -> Self {
340 self.alpha = alpha;
341 self
342 }
343
344 pub fn build(self, ui: &mut BuildContext) -> Handle<UiNode> {
345 let canvas = AlphaBar {
346 widget: self.widget_builder.build(),
347 orientation: self.orientation,
348 alpha: self.alpha,
349 is_picking: false,
350 };
351 ui.add_node(UiNode::new(canvas))
352 }
353}
354
355#[derive(Clone)]
356pub struct HueBar {
357 widget: Widget,
358 orientation: Orientation,
359 is_picking: bool,
360 hue: f32,
361}
362
363crate::define_widget_deref!(HueBar);
364
365impl HueBar {
366 fn hue_at(&self, mouse_pos: Vector2<f32>) -> f32 {
367 let relative_pos = mouse_pos - self.screen_position;
368 let k = match self.orientation {
369 Orientation::Vertical => relative_pos.y / self.actual_size().y,
370 Orientation::Horizontal => relative_pos.x / self.actual_size().x,
371 };
372 k.min(1.0).max(0.0) * 360.0
373 }
374}
375
376impl Control for HueBar {
377 fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
378 if type_id == TypeId::of::<Self>() {
379 Some(self)
380 } else {
381 None
382 }
383 }
384
385 fn draw(&self, drawing_context: &mut DrawingContext) {
386 let bounds = self.screen_bounds();
387 for hue in 1..360 {
388 let prev_color = Color::from(Hsv::new((hue - 1) as f32, 100.0, 100.0));
389 let curr_color = Color::from(Hsv::new(hue as f32, 100.0, 100.0));
390 let prev_k = (hue - 1) as f32 / 360.0;
391 let curr_k = hue as f32 / 360.0;
392 push_gradient_rect(
393 drawing_context,
394 &bounds,
395 self.orientation,
396 prev_k,
397 prev_color,
398 curr_k,
399 curr_color,
400 );
401 }
402
403 let k = self.hue / 360.0;
404 match self.orientation {
405 Orientation::Vertical => drawing_context.push_rect_multicolor(
406 &Rect::new(bounds.x(), bounds.y() + bounds.h() * k, bounds.w(), 1.0),
407 [Color::BLACK; 4],
408 ),
409 Orientation::Horizontal => drawing_context.push_rect_multicolor(
410 &Rect::new(bounds.x() + k * bounds.w(), bounds.y(), 1.0, bounds.h()),
411 [Color::BLACK; 4],
412 ),
413 }
414
415 drawing_context.commit(
416 self.clip_bounds(),
417 Brush::Solid(Color::WHITE),
418 CommandTexture::None,
419 None,
420 );
421 }
422
423 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
424 self.widget.handle_routed_message(ui, message);
425
426 if message.destination() == self.handle {
427 if let Some(msg) = message.data::<WidgetMessage>() {
428 if message.direction() == MessageDirection::FromWidget {
429 match *msg {
430 WidgetMessage::MouseDown { button, .. } => {
431 if button == MouseButton::Left {
432 self.is_picking = true;
433 ui.capture_mouse(self.handle);
434 }
435 }
436 WidgetMessage::MouseMove { pos, .. } => {
437 if self.is_picking {
438 ui.send_message(HueBarMessage::hue(
439 self.handle,
440 MessageDirection::ToWidget,
441 self.hue_at(pos),
442 ))
443 }
444 }
445 WidgetMessage::MouseUp { button, .. } => {
446 if self.is_picking && button == MouseButton::Left {
447 self.is_picking = false;
448 ui.release_mouse_capture();
449 }
450 }
451 _ => (),
452 }
453 }
454 } else if let Some(msg) = message.data::<HueBarMessage>() {
455 if message.direction() == MessageDirection::ToWidget {
456 match *msg {
457 HueBarMessage::Hue(hue) => {
458 if self.hue != hue {
459 self.hue = hue;
460 ui.send_message(message.reverse());
461 }
462 }
463 HueBarMessage::Orientation(orientation) => {
464 if self.orientation != orientation {
465 self.orientation = orientation;
466 ui.send_message(message.reverse());
467 }
468 }
469 }
470 }
471 }
472 }
473 }
474}
475
476pub struct HueBarBuilder {
477 widget_builder: WidgetBuilder,
478 orientation: Orientation,
479 hue: f32,
480}
481
482impl HueBarBuilder {
483 pub fn new(widget_builder: WidgetBuilder) -> Self {
484 Self {
485 widget_builder,
486 orientation: Orientation::Vertical,
487 hue: 0.0, }
489 }
490
491 pub fn with_hue(mut self, hue: f32) -> Self {
492 self.hue = hue;
493 self
494 }
495
496 pub fn with_orientation(mut self, orientation: Orientation) -> Self {
497 self.orientation = orientation;
498 self
499 }
500
501 pub fn build(self, ui: &mut BuildContext) -> Handle<UiNode> {
502 let bar = HueBar {
503 widget: self.widget_builder.build(),
504 orientation: self.orientation,
505 is_picking: false,
506 hue: self.hue,
507 };
508 ui.add_node(UiNode::new(bar))
509 }
510}
511
512#[derive(Clone)]
513pub struct SaturationBrightnessField {
514 widget: Widget,
515 is_picking: bool,
516 hue: f32,
517 saturation: f32,
518 brightness: f32,
519}
520
521crate::define_widget_deref!(SaturationBrightnessField);
522
523impl SaturationBrightnessField {
524 fn saturation_at(&self, mouse_pos: Vector2<f32>) -> f32 {
525 ((mouse_pos.x - self.screen_position.x) / self.screen_bounds().w())
526 .min(1.0)
527 .max(0.0)
528 * 100.0
529 }
530
531 fn brightness_at(&self, mouse_pos: Vector2<f32>) -> f32 {
532 100.0
533 - ((mouse_pos.y - self.screen_position.y) / self.screen_bounds().h())
534 .min(1.0)
535 .max(0.0)
536 * 100.0
537 }
538}
539
540impl Control for SaturationBrightnessField {
541 fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
542 if type_id == TypeId::of::<Self>() {
543 Some(self)
544 } else {
545 None
546 }
547 }
548
549 fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
550 let size = self.deref().arrange_override(ui, final_size);
551 ui.send_message(WidgetMessage::width(
553 self.handle,
554 MessageDirection::ToWidget,
555 final_size.y,
556 ));
557 size
558 }
559
560 fn draw(&self, drawing_context: &mut DrawingContext) {
561 let bounds = self.screen_bounds();
562
563 drawing_context.push_rect_multicolor(
564 &bounds,
565 [
566 Color::from(Hsv::new(self.hue, 0.0, 100.0)),
567 Color::from(Hsv::new(self.hue, 100.0, 100.0)),
568 Color::from(Hsv::new(self.hue, 100.0, 0.0)),
569 Color::from(Hsv::new(self.hue, 0.0, 0.0)),
570 ],
571 );
572 drawing_context.commit(
573 self.clip_bounds(),
574 Brush::Solid(Color::WHITE),
575 CommandTexture::None,
576 None,
577 );
578
579 let origin = Vector2::new(
581 bounds.x() + self.saturation / 100.0 * bounds.w(),
582 bounds.y() + (100.0 - self.brightness) / 100.0 * bounds.h(),
583 );
584 drawing_context.push_circle(
585 origin,
586 3.0,
587 10,
588 Color::from(Hsv::new(360.0 - self.hue, 100.0, 100.0)),
589 );
590 drawing_context.commit(
591 self.clip_bounds(),
592 Brush::Solid(Color::WHITE),
593 CommandTexture::None,
594 None,
595 );
596 }
597
598 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
599 self.widget.handle_routed_message(ui, message);
600
601 if message.destination() == self.handle {
602 if let Some(msg) = message.data::<WidgetMessage>() {
603 if message.direction() == MessageDirection::FromWidget {
604 match *msg {
605 WidgetMessage::MouseDown { button, .. } => {
606 if button == MouseButton::Left {
607 self.is_picking = true;
608 ui.capture_mouse(self.handle);
609 }
610 }
611 WidgetMessage::MouseMove { pos, .. } => {
612 if self.is_picking {
613 ui.send_message(SaturationBrightnessFieldMessage::brightness(
614 self.handle,
615 MessageDirection::ToWidget,
616 self.brightness_at(pos),
617 ));
618
619 ui.send_message(SaturationBrightnessFieldMessage::saturation(
620 self.handle,
621 MessageDirection::ToWidget,
622 self.saturation_at(pos),
623 ));
624 }
625 }
626 WidgetMessage::MouseUp { button, .. } => {
627 if self.is_picking && button == MouseButton::Left {
628 self.is_picking = false;
629 ui.release_mouse_capture();
630 }
631 }
632 _ => (),
633 }
634 }
635 } else if let Some(msg) = message.data::<SaturationBrightnessFieldMessage>() {
636 if message.direction() == MessageDirection::ToWidget {
637 match *msg {
638 SaturationBrightnessFieldMessage::Hue(hue) => {
639 let clamped = hue.min(360.0).max(0.0);
640 if self.hue != clamped {
641 self.hue = clamped;
642 ui.send_message(SaturationBrightnessFieldMessage::hue(
643 self.handle,
644 MessageDirection::FromWidget,
645 self.hue,
646 ));
647 }
648 }
649 SaturationBrightnessFieldMessage::Saturation(saturation) => {
650 let clamped = saturation.min(100.0).max(0.0);
651 if self.saturation != clamped {
652 self.saturation = clamped;
653 ui.send_message(SaturationBrightnessFieldMessage::saturation(
654 self.handle,
655 MessageDirection::FromWidget,
656 self.saturation,
657 ));
658 }
659 }
660 SaturationBrightnessFieldMessage::Brightness(brightness) => {
661 let clamped = brightness.min(100.0).max(0.0);
662 if self.brightness != clamped {
663 self.brightness = clamped;
664 ui.send_message(SaturationBrightnessFieldMessage::brightness(
665 self.handle,
666 MessageDirection::FromWidget,
667 self.brightness,
668 ));
669 }
670 }
671 }
672 }
673 }
674 }
675 }
676}
677
678pub struct SaturationBrightnessFieldBuilder {
679 widget_builder: WidgetBuilder,
680 hue: f32,
681 saturation: f32,
682 brightness: f32,
683}
684
685impl SaturationBrightnessFieldBuilder {
686 pub fn new(widget_builder: WidgetBuilder) -> Self {
687 Self {
688 widget_builder,
689 hue: 0.0,
690 saturation: 100.0,
691 brightness: 100.0,
692 }
693 }
694
695 pub fn with_hue(mut self, hue: f32) -> Self {
696 self.hue = hue;
697 self
698 }
699
700 pub fn with_saturation(mut self, saturation: f32) -> Self {
701 self.saturation = saturation;
702 self
703 }
704
705 pub fn with_brightness(mut self, brightness: f32) -> Self {
706 self.brightness = brightness;
707 self
708 }
709
710 pub fn build(self, ui: &mut BuildContext) -> Handle<UiNode> {
711 let bar = SaturationBrightnessField {
712 widget: self.widget_builder.build(),
713 is_picking: false,
714 saturation: self.saturation,
715 brightness: self.brightness,
716 hue: self.hue,
717 };
718 ui.add_node(UiNode::new(bar))
719 }
720}
721
722#[derive(Clone)]
723pub struct ColorPicker {
724 widget: Widget,
725 hue_bar: Handle<UiNode>,
726 alpha_bar: Handle<UiNode>,
727 saturation_brightness_field: Handle<UiNode>,
728 red: Handle<UiNode>,
729 green: Handle<UiNode>,
730 blue: Handle<UiNode>,
731 alpha: Handle<UiNode>,
732 hue: Handle<UiNode>,
733 saturation: Handle<UiNode>,
734 brightness: Handle<UiNode>,
735 color_mark: Handle<UiNode>,
736 color: Color,
737 hsv: Hsv,
738}
739
740crate::define_widget_deref!(ColorPicker);
741
742fn mark_handled(message: UiMessage) -> UiMessage {
743 message.set_handled(true);
744 message
745}
746
747impl ColorPicker {
748 fn sync_fields(&self, ui: &mut UserInterface, color: Color, hsv: Hsv) {
749 ui.send_message(mark_handled(NumericUpDownMessage::value(
750 self.hue,
751 MessageDirection::ToWidget,
752 hsv.hue(),
753 )));
754
755 ui.send_message(mark_handled(NumericUpDownMessage::value(
756 self.saturation,
757 MessageDirection::ToWidget,
758 hsv.saturation(),
759 )));
760
761 ui.send_message(mark_handled(NumericUpDownMessage::value(
762 self.brightness,
763 MessageDirection::ToWidget,
764 hsv.brightness(),
765 )));
766
767 ui.send_message(mark_handled(NumericUpDownMessage::value(
768 self.red,
769 MessageDirection::ToWidget,
770 color.r as f32,
771 )));
772
773 ui.send_message(mark_handled(NumericUpDownMessage::value(
774 self.green,
775 MessageDirection::ToWidget,
776 color.g as f32,
777 )));
778
779 ui.send_message(mark_handled(NumericUpDownMessage::value(
780 self.blue,
781 MessageDirection::ToWidget,
782 color.b as f32,
783 )));
784
785 ui.send_message(mark_handled(NumericUpDownMessage::value(
786 self.alpha,
787 MessageDirection::ToWidget,
788 color.a as f32,
789 )));
790
791 ui.send_message(mark_handled(WidgetMessage::background(
792 self.color_mark,
793 MessageDirection::ToWidget,
794 Brush::Solid(color),
795 )));
796 }
797}
798
799impl Control for ColorPicker {
800 fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
801 if type_id == TypeId::of::<Self>() {
802 Some(self)
803 } else {
804 None
805 }
806 }
807
808 fn resolve(&mut self, node_map: &NodeHandleMapping) {
809 node_map.resolve(&mut self.hue_bar);
810 node_map.resolve(&mut self.alpha_bar);
811 node_map.resolve(&mut self.saturation_brightness_field);
812 node_map.resolve(&mut self.red);
813 node_map.resolve(&mut self.green);
814 node_map.resolve(&mut self.blue);
815 node_map.resolve(&mut self.alpha);
816 node_map.resolve(&mut self.hue);
817 node_map.resolve(&mut self.saturation);
818 node_map.resolve(&mut self.brightness);
819 node_map.resolve(&mut self.color_mark);
820 }
821
822 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
823 self.widget.handle_routed_message(ui, message);
824
825 if let Some(&HueBarMessage::Hue(hue)) = message.data::<HueBarMessage>() {
826 if message.destination() == self.hue_bar
827 && message.direction() == MessageDirection::FromWidget
828 {
829 ui.send_message(SaturationBrightnessFieldMessage::hue(
830 self.saturation_brightness_field,
831 MessageDirection::ToWidget,
832 hue,
833 ));
834
835 let mut hsv = self.hsv;
836 hsv.set_hue(hue);
837 ui.send_message(ColorPickerMessage::hsv(
838 self.handle,
839 MessageDirection::ToWidget,
840 hsv,
841 ));
842 }
843 } else if let Some(&AlphaBarMessage::Alpha(alpha)) = message.data::<AlphaBarMessage>() {
844 if message.destination() == self.alpha_bar
845 && message.direction() == MessageDirection::FromWidget
846 {
847 ui.send_message(ColorPickerMessage::color(
848 self.handle,
849 MessageDirection::ToWidget,
850 Color::from_rgba(self.color.r, self.color.g, self.color.b, alpha as u8),
851 ));
852 }
853 } else if let Some(msg) = message.data::<SaturationBrightnessFieldMessage>() {
854 if message.destination() == self.saturation_brightness_field
855 && message.direction() == MessageDirection::FromWidget
856 {
857 match *msg {
858 SaturationBrightnessFieldMessage::Brightness(brightness) => {
859 let mut hsv = self.hsv;
860 hsv.set_brightness(brightness);
861 ui.send_message(ColorPickerMessage::hsv(
862 self.handle,
863 MessageDirection::ToWidget,
864 hsv,
865 ));
866 }
867 SaturationBrightnessFieldMessage::Saturation(saturation) => {
868 let mut hsv = self.hsv;
869 hsv.set_saturation(saturation);
870 ui.send_message(ColorPickerMessage::hsv(
871 self.handle,
872 MessageDirection::ToWidget,
873 hsv,
874 ));
875 }
876 _ => {}
877 }
878 }
879 } else if let Some(&NumericUpDownMessage::Value(value)) =
880 message.data::<NumericUpDownMessage<f32>>()
881 {
882 if message.direction() == MessageDirection::FromWidget && !message.handled() {
883 if message.destination() == self.hue {
884 ui.send_message(HueBarMessage::hue(
885 self.hue_bar,
886 MessageDirection::ToWidget,
887 value,
888 ));
889 } else if message.destination() == self.saturation {
890 ui.send_message(SaturationBrightnessFieldMessage::saturation(
891 self.saturation_brightness_field,
892 MessageDirection::ToWidget,
893 value,
894 ));
895 } else if message.destination() == self.brightness {
896 ui.send_message(SaturationBrightnessFieldMessage::brightness(
897 self.saturation_brightness_field,
898 MessageDirection::ToWidget,
899 value,
900 ));
901 } else if message.destination() == self.red {
902 ui.send_message(ColorPickerMessage::color(
903 self.handle,
904 MessageDirection::ToWidget,
905 Color::from_rgba(value as u8, self.color.g, self.color.b, self.color.a),
906 ));
907 } else if message.destination() == self.green {
908 ui.send_message(ColorPickerMessage::color(
909 self.handle,
910 MessageDirection::ToWidget,
911 Color::from_rgba(self.color.r, value as u8, self.color.b, self.color.a),
912 ));
913 } else if message.destination() == self.blue {
914 ui.send_message(ColorPickerMessage::color(
915 self.handle,
916 MessageDirection::ToWidget,
917 Color::from_rgba(self.color.r, self.color.g, value as u8, self.color.a),
918 ));
919 } else if message.destination() == self.alpha {
920 ui.send_message(ColorPickerMessage::color(
921 self.handle,
922 MessageDirection::ToWidget,
923 Color::from_rgba(self.color.r, self.color.g, self.color.b, value as u8),
924 ));
925 }
926 }
927 } else if let Some(msg) = message.data::<ColorPickerMessage>() {
928 if message.destination() == self.handle
929 && message.direction() == MessageDirection::ToWidget
930 {
931 match *msg {
932 ColorPickerMessage::Color(color) => {
933 if self.color != color {
934 self.color = color;
935 self.hsv = Hsv::from(color);
936
937 self.sync_fields(ui, color, self.hsv);
938
939 ui.send_message(message.reverse());
940 }
941 }
942 ColorPickerMessage::Hsv(hsv) => {
943 if self.hsv != hsv {
944 self.hsv = hsv;
945 let opaque = Color::from(hsv);
946 self.color =
947 Color::from_rgba(opaque.r, opaque.g, opaque.b, self.color.a);
948
949 self.sync_fields(ui, self.color, hsv);
950
951 ui.send_message(message.reverse());
952 }
953 }
954 }
955 }
956 }
957 }
958}
959
960pub struct ColorPickerBuilder {
961 widget_builder: WidgetBuilder,
962 color: Color,
963}
964
965fn make_text_mark(ctx: &mut BuildContext, text: &str, row: usize, column: usize) -> Handle<UiNode> {
966 TextBuilder::new(
967 WidgetBuilder::new()
968 .with_vertical_alignment(VerticalAlignment::Center)
969 .on_row(row)
970 .on_column(column),
971 )
972 .with_text(text)
973 .build(ctx)
974}
975
976fn make_input_field(
977 ctx: &mut BuildContext,
978 value: f32,
979 max_value: f32,
980 row: usize,
981 column: usize,
982) -> Handle<UiNode> {
983 NumericUpDownBuilder::new(
984 WidgetBuilder::new()
985 .with_margin(Thickness::uniform(1.0))
986 .on_row(row)
987 .on_column(column),
988 )
989 .with_value(value)
990 .with_min_value(0.0)
991 .with_max_value(max_value)
992 .with_precision(0)
993 .with_step(1.0)
994 .build(ctx)
995}
996
997impl ColorPickerBuilder {
998 pub fn new(widget_builder: WidgetBuilder) -> Self {
999 Self {
1000 widget_builder,
1001 color: Color::WHITE,
1002 }
1003 }
1004
1005 pub fn with_color(mut self, color: Color) -> Self {
1006 self.color = color;
1007 self
1008 }
1009
1010 pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
1011 let hue_bar;
1012 let alpha_bar;
1013 let saturation_brightness_field;
1014 let red;
1015 let green;
1016 let blue;
1017 let hue;
1018 let saturation;
1019 let brightness;
1020 let color_mark;
1021 let alpha;
1022 let hsv = Hsv::from(self.color);
1023
1024 let numerics_grid = GridBuilder::new(
1025 WidgetBuilder::new()
1026 .on_row(1)
1027 .with_child(make_text_mark(ctx, "R", 0, 0))
1028 .with_child({
1029 red = make_input_field(ctx, self.color.r as f32, 255.0, 0, 1);
1030 red
1031 })
1032 .with_child(make_text_mark(ctx, "G", 1, 0))
1033 .with_child({
1034 green = make_input_field(ctx, self.color.g as f32, 255.0, 1, 1);
1035 green
1036 })
1037 .with_child(make_text_mark(ctx, "B", 2, 0))
1038 .with_child({
1039 blue = make_input_field(ctx, self.color.b as f32, 255.0, 2, 1);
1040 blue
1041 })
1042 .with_child(make_text_mark(ctx, "H", 0, 2))
1043 .with_child({
1044 hue = make_input_field(ctx, hsv.hue(), 360.0, 0, 3);
1045 hue
1046 })
1047 .with_child(make_text_mark(ctx, "S", 1, 2))
1048 .with_child({
1049 saturation = make_input_field(ctx, hsv.saturation(), 100.0, 1, 3);
1050 saturation
1051 })
1052 .with_child(make_text_mark(ctx, "B", 2, 2))
1053 .with_child({
1054 brightness = make_input_field(ctx, hsv.brightness(), 100.0, 2, 3);
1055 brightness
1056 })
1057 .with_child(make_text_mark(ctx, "A", 3, 0))
1058 .with_child({
1059 alpha = make_input_field(ctx, self.color.a as f32, 255.0, 3, 1);
1060 alpha
1061 }),
1062 )
1063 .add_column(Column::strict(10.0))
1064 .add_column(Column::stretch())
1065 .add_column(Column::strict(10.0))
1066 .add_column(Column::stretch())
1067 .add_row(Row::strict(25.0))
1068 .add_row(Row::strict(25.0))
1069 .add_row(Row::strict(25.0))
1070 .add_row(Row::strict(25.0))
1071 .add_row(Row::stretch())
1072 .build(ctx);
1073
1074 let widget = self
1075 .widget_builder
1076 .with_child(
1077 GridBuilder::new(
1078 WidgetBuilder::new()
1079 .with_child({
1080 saturation_brightness_field = SaturationBrightnessFieldBuilder::new(
1081 WidgetBuilder::new()
1082 .with_margin(Thickness::uniform(1.0))
1083 .on_column(0),
1084 )
1085 .build(ctx);
1086 saturation_brightness_field
1087 })
1088 .with_child({
1089 hue_bar = HueBarBuilder::new(
1090 WidgetBuilder::new()
1091 .with_margin(Thickness::uniform(1.0))
1092 .on_column(1),
1093 )
1094 .build(ctx);
1095 hue_bar
1096 })
1097 .with_child({
1098 alpha_bar = AlphaBarBuilder::new(
1099 WidgetBuilder::new()
1100 .with_margin(Thickness::uniform(1.0))
1101 .on_column(2),
1102 )
1103 .with_alpha(self.color.a as f32)
1104 .build(ctx);
1105 alpha_bar
1106 })
1107 .with_child(
1108 GridBuilder::new(
1109 WidgetBuilder::new()
1110 .on_column(3)
1111 .with_child({
1112 color_mark = BorderBuilder::new(
1113 WidgetBuilder::new()
1114 .on_row(0)
1115 .with_margin(Thickness::uniform(1.0)),
1116 )
1117 .build(ctx);
1118 color_mark
1119 })
1120 .with_child(numerics_grid),
1121 )
1122 .add_row(Row::strict(25.0))
1123 .add_row(Row::stretch())
1124 .add_column(Column::stretch())
1125 .build(ctx),
1126 ),
1127 )
1128 .add_column(Column::auto())
1129 .add_column(Column::strict(20.0))
1130 .add_column(Column::strict(20.0))
1131 .add_column(Column::stretch())
1132 .add_row(Row::auto())
1133 .build(ctx),
1134 )
1135 .build();
1136
1137 let picker = ColorPicker {
1138 widget,
1139 hue_bar,
1140 saturation_brightness_field,
1141 red,
1142 green,
1143 blue,
1144 hue,
1145 saturation,
1146 brightness,
1147 color: self.color,
1148 color_mark,
1149 hsv,
1150 alpha_bar,
1151 alpha,
1152 };
1153 ctx.add_node(UiNode::new(picker))
1154 }
1155}
1156
1157#[derive(Clone)]
1158pub struct ColorField {
1159 widget: Widget,
1160 popup: Handle<UiNode>,
1161 picker: Handle<UiNode>,
1162 color: Color,
1163}
1164
1165crate::define_widget_deref!(ColorField);
1166
1167impl Control for ColorField {
1168 fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
1169 if type_id == TypeId::of::<Self>() {
1170 Some(self)
1171 } else {
1172 None
1173 }
1174 }
1175
1176 fn on_remove(&self, sender: &Sender<UiMessage>) {
1177 sender
1180 .send(WidgetMessage::remove(
1181 self.popup,
1182 MessageDirection::ToWidget,
1183 ))
1184 .unwrap();
1185 }
1186
1187 fn draw(&self, drawing_context: &mut DrawingContext) {
1188 let bounds = self.screen_bounds();
1189
1190 drawing_context.push_rect_filled(&bounds, None);
1191 drawing_context.commit(
1192 self.clip_bounds(),
1193 Brush::Solid(self.color),
1194 CommandTexture::None,
1195 None,
1196 );
1197 }
1198
1199 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
1200 self.widget.handle_routed_message(ui, message);
1201
1202 if let Some(&WidgetMessage::MouseDown { button, .. }) = message.data::<WidgetMessage>() {
1203 if message.destination() == self.handle
1204 && message.direction() == MessageDirection::FromWidget
1205 && button == MouseButton::Left
1206 {
1207 ui.send_message(WidgetMessage::width(
1208 self.popup,
1209 MessageDirection::ToWidget,
1210 self.actual_size().x,
1211 ));
1212 ui.send_message(PopupMessage::placement(
1213 self.popup,
1214 MessageDirection::ToWidget,
1215 Placement::LeftBottom(self.handle),
1216 ));
1217 ui.send_message(PopupMessage::open(self.popup, MessageDirection::ToWidget));
1218 ui.send_message(ColorPickerMessage::color(
1219 self.picker,
1220 MessageDirection::ToWidget,
1221 self.color,
1222 ));
1223 }
1224 } else if let Some(&ColorFieldMessage::Color(color)) = message.data::<ColorFieldMessage>() {
1225 if message.destination() == self.handle
1226 && message.direction() == MessageDirection::ToWidget
1227 && self.color != color
1228 {
1229 self.color = color;
1230 ui.send_message(ColorPickerMessage::color(
1231 self.picker,
1232 MessageDirection::ToWidget,
1233 self.color,
1234 ));
1235 ui.send_message(message.reverse());
1236 }
1237 }
1238 }
1239
1240 fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
1243 if let Some(PopupMessage::Close) = message.data::<PopupMessage>() {
1244 if message.destination() == self.popup {
1245 let picker = ui
1246 .node(self.picker)
1247 .cast::<ColorPicker>()
1248 .expect("self.picker must be ColorPicker!");
1249 ui.send_message(ColorFieldMessage::color(
1250 self.handle,
1251 MessageDirection::ToWidget,
1252 picker.color,
1253 ));
1254 }
1255 }
1256 }
1257}
1258
1259pub struct ColorFieldBuilder {
1260 widget_builder: WidgetBuilder,
1261 color: Color,
1262}
1263
1264impl ColorFieldBuilder {
1265 pub fn new(widget_builder: WidgetBuilder) -> Self {
1266 Self {
1267 widget_builder,
1268 color: Color::WHITE,
1269 }
1270 }
1271
1272 pub fn with_color(mut self, color: Color) -> Self {
1273 self.color = color;
1274 self
1275 }
1276
1277 pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
1278 let picker;
1279 let popup = PopupBuilder::new(WidgetBuilder::new())
1280 .with_content({
1281 picker = ColorPickerBuilder::new(WidgetBuilder::new())
1282 .with_color(self.color)
1283 .build(ctx);
1284 picker
1285 })
1286 .build(ctx);
1287
1288 let field = ColorField {
1289 widget: self.widget_builder.with_preview_messages(true).build(),
1290 popup,
1291 picker,
1292 color: self.color,
1293 };
1294 ctx.add_node(UiNode::new(field))
1295 }
1296}