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