rg3d_ui/
color.rs

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    /// Sets new hue value.
30    Hue(f32),
31
32    /// Sets new orientation
33    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    /// Sets new hue value.
44    Alpha(f32),
45
46    /// Sets new orientation
47    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    /// Sets new hue value on the field.
58    Hue(f32),
59
60    /// Sets new saturation value on the field.
61    Saturation(f32),
62
63    /// Sets new brightness value on the field.
64    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    /// Sets color in RGB.
76    ///
77    /// Direction: **To/From Widget**.
78    Color(Color),
79
80    /// Sets color in HSV.
81    ///
82    /// Direction: **To Widget**.
83    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        // Draw checker board first.
209        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        // Then draw alpha gradient.
235        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, // Red
488        }
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        // Make sure field is always square.
552        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        // Indicator must be drawn separately, otherwise it may be drawn incorrectly.
580        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        // Popup won't be deleted with the color field, because it is not the child of the field.
1178        // So we have to remove it manually.
1179        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    // We have to use preview message because popup it *not* in visual tree of our control and
1241    // handle_routed_message won't trigger because of it.
1242    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}