1use 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 Hue(f32),
57
58 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 Alpha(f32),
71
72 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 Hue(f32),
85
86 Saturation(f32),
88
89 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 Color(Color),
105
106 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(
274 bounds,
275 self.clip_bounds(),
276 CHECKERBOARD_SIZE,
277 drawing_context,
278 );
279
280 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, }
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 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 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 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 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}