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