fyrox_ui/color/
mod.rs

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