Skip to main content

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