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 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 Hue(f32),
59
60 Orientation(Orientation),
62}
63impl MessageData for HueBarMessage {}
64
65#[derive(Debug, Clone, PartialEq)]
66pub enum AlphaBarMessage {
67 Alpha(f32),
69
70 Orientation(Orientation),
72}
73impl MessageData for AlphaBarMessage {}
74
75#[derive(Debug, Clone, PartialEq)]
76pub enum SaturationBrightnessFieldMessage {
77 Hue(f32),
79
80 Saturation(f32),
82
83 Brightness(f32),
85}
86impl MessageData for SaturationBrightnessFieldMessage {}
87
88#[derive(Debug, Clone, PartialEq)]
89pub enum ColorPickerMessage {
90 Color(Color),
94
95 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_checker_board(
260 bounds,
261 self.clip_bounds(),
262 CHECKERBOARD_SIZE,
263 &self.material,
264 drawing_context,
265 );
266
267 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, }
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 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 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 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 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}