1use crate::menu::ContextMenuBuilder;
22use crate::{
23 brush::Brush,
24 color::{ColorFieldBuilder, ColorFieldMessage},
25 core::{
26 algebra::Vector2,
27 color::Color,
28 color_gradient::{ColorGradient, GradientPoint},
29 math::Rect,
30 pool::Handle,
31 reflect::prelude::*,
32 type_traits::prelude::*,
33 visitor::prelude::*,
34 },
35 define_constructor, define_widget_deref,
36 draw::{CommandTexture, Draw, DrawingContext},
37 grid::{Column, GridBuilder, Row},
38 menu::{MenuItemBuilder, MenuItemContent, MenuItemMessage},
39 message::{CursorIcon, MessageDirection, MouseButton, UiMessage},
40 popup::{Placement, PopupBuilder, PopupMessage},
41 stack_panel::StackPanelBuilder,
42 widget::{Widget, WidgetBuilder, WidgetMessage},
43 BuildContext, Control, RcUiNodeHandle, UiNode, UserInterface,
44};
45use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
46use fyrox_graph::BaseSceneGraph;
47use std::{
48 cell::Cell,
49 ops::{Deref, DerefMut},
50};
51
52#[derive(Debug, Clone, PartialEq)]
53pub enum ColorGradientEditorMessage {
54 Value(ColorGradient),
56}
57
58impl ColorGradientEditorMessage {
59 define_constructor!(ColorGradientEditorMessage:Value => fn value(ColorGradient), layout: false);
60}
61
62#[derive(Default, Clone, Debug, Visit, Reflect, TypeUuidProvider, ComponentProvider)]
63#[type_uuid(id = "50d00eb7-f30b-4973-8a36-03d6b8f007ec")]
64pub struct ColorGradientField {
65 widget: Widget,
66 color_gradient: ColorGradient,
67}
68
69impl ConstructorProvider<UiNode, UserInterface> for ColorGradientField {
70 fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
71 GraphNodeConstructor::new::<Self>()
72 .with_variant("Color Gradient Field", |ui| {
73 ColorGradientFieldBuilder::new(
74 WidgetBuilder::new().with_name("Color Gradient Field"),
75 )
76 .build(&mut ui.build_ctx())
77 .into()
78 })
79 .with_group("Color")
80 }
81}
82
83define_widget_deref!(ColorGradientField);
84
85const SYNC_FLAG: u64 = 1;
86
87impl Control for ColorGradientField {
88 fn draw(&self, drawing_context: &mut DrawingContext) {
89 super::draw_checker_board(
91 self.bounding_rect(),
92 self.clip_bounds(),
93 6.0,
94 drawing_context,
95 );
96
97 let size = self.bounding_rect().size;
98
99 if self.color_gradient.points().is_empty() {
100 drawing_context.push_rect_filled(&self.bounding_rect(), None);
101
102 drawing_context.commit(
103 self.clip_bounds(),
104 Brush::Solid(ColorGradient::STUB_COLOR),
105 CommandTexture::None,
106 None,
107 );
108 } else {
109 let first = self.color_gradient.points().first().unwrap();
110 drawing_context.push_rect_multicolor(
111 &Rect::new(0.0, 0.0, first.location() * size.x, size.y),
112 [first.color(), first.color(), first.color(), first.color()],
113 );
114
115 for pair in self.color_gradient.points().windows(2) {
116 let left = &pair[0];
117 let right = &pair[1];
118
119 let left_pos = left.location() * size.x;
120 let right_pos = right.location() * size.x;
121 let bounds = Rect::new(left_pos, 0.0, right_pos - left_pos, size.y);
122
123 drawing_context.push_rect_multicolor(
124 &bounds,
125 [left.color(), right.color(), right.color(), left.color()],
126 );
127 }
128
129 let last = self.color_gradient.points().last().unwrap();
130 let last_pos = last.location() * size.x;
131 drawing_context.push_rect_multicolor(
132 &Rect::new(last_pos, 0.0, size.x - last_pos, size.y),
133 [last.color(), last.color(), last.color(), last.color()],
134 );
135
136 drawing_context.commit(
137 self.clip_bounds(),
138 Brush::Solid(Color::WHITE),
139 CommandTexture::None,
140 None,
141 );
142 }
143 }
144
145 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
146 self.widget.handle_routed_message(ui, message);
147
148 if message.destination() == self.handle && message.direction() == MessageDirection::ToWidget
149 {
150 if let Some(ColorGradientEditorMessage::Value(value)) = message.data() {
151 self.color_gradient = value.clone();
152 }
153 }
154 }
155}
156
157pub struct ColorGradientFieldBuilder {
158 widget_builder: WidgetBuilder,
159 color_gradient: ColorGradient,
160}
161
162impl ColorGradientFieldBuilder {
163 pub fn new(widget_builder: WidgetBuilder) -> Self {
164 Self {
165 widget_builder,
166 color_gradient: Default::default(),
167 }
168 }
169
170 pub fn with_color_gradient(mut self, gradient: ColorGradient) -> Self {
171 self.color_gradient = gradient;
172 self
173 }
174
175 pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
176 let field = ColorGradientField {
177 widget: self.widget_builder.build(ctx),
178 color_gradient: self.color_gradient,
179 };
180
181 ctx.add_node(UiNode::new(field))
182 }
183}
184
185#[derive(Default, Clone, Debug, Visit, Reflect, TypeUuidProvider, ComponentProvider)]
186#[type_uuid(id = "82843d8b-1972-46e6-897c-9619b74059cc")]
187pub struct ColorGradientEditor {
188 widget: Widget,
189 gradient_field: Handle<UiNode>,
190 selector_field: Handle<UiNode>,
191 points_canvas: Handle<UiNode>,
192 context_menu: RcUiNodeHandle,
193 point_context_menu: RcUiNodeHandle,
194 add_point: Handle<UiNode>,
195 remove_point: Handle<UiNode>,
196 context_menu_target: Cell<Handle<UiNode>>,
197 context_menu_open_position: Cell<Vector2<f32>>,
198}
199
200impl ConstructorProvider<UiNode, UserInterface> for ColorGradientEditor {
201 fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
202 GraphNodeConstructor::new::<Self>()
203 .with_variant("Color Gradient Editor", |ui| {
204 ColorGradientEditorBuilder::new(
205 WidgetBuilder::new().with_name("Color Gradient Editor"),
206 )
207 .build(&mut ui.build_ctx())
208 .into()
209 })
210 .with_group("Color")
211 }
212}
213
214define_widget_deref!(ColorGradientEditor);
215
216impl Control for ColorGradientEditor {
217 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
218 self.widget.handle_routed_message(ui, message);
219
220 if message.destination() == self.handle && message.direction() == MessageDirection::ToWidget
221 {
222 if let Some(ColorGradientEditorMessage::Value(value)) = message.data() {
223 ui.send_message(ColorGradientEditorMessage::value(
225 self.gradient_field,
226 MessageDirection::ToWidget,
227 value.clone(),
228 ));
229
230 for &point in ui.node(self.points_canvas).children() {
231 ui.send_message(WidgetMessage::remove(point, MessageDirection::ToWidget));
232 }
233
234 let points = create_color_points(
235 value,
236 self.point_context_menu.clone(),
237 &mut ui.build_ctx(),
238 );
239
240 for point in points {
241 ui.send_message(WidgetMessage::link(
242 point,
243 MessageDirection::ToWidget,
244 self.points_canvas,
245 ));
246 }
247 }
248 }
249
250 if message.direction() == MessageDirection::FromWidget {
251 if let Some(ColorPointMessage::Location(_)) = message.data() {
252 let gradient = self.fetch_gradient(Handle::NONE, ui);
253
254 ui.send_message(ColorGradientEditorMessage::value(
255 self.handle,
256 MessageDirection::FromWidget,
257 gradient,
258 ));
259 }
260 }
261 }
262
263 fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
264 if let Some(MenuItemMessage::Click) = message.data() {
265 if message.destination() == self.add_point {
266 let mut gradient = self.fetch_gradient(Handle::NONE, ui);
267
268 let location = (self
269 .screen_to_local(self.context_menu_open_position.get())
270 .x
271 / self.actual_local_size().x)
272 .clamp(0.0, 1.0);
273
274 gradient.add_point(GradientPoint::new(location, Color::WHITE));
275
276 ui.send_message(ColorGradientEditorMessage::value(
277 self.handle,
278 MessageDirection::FromWidget,
279 gradient,
280 ));
281 } else if message.destination() == self.remove_point
282 && ui.try_get(self.context_menu_target.get()).is_some()
283 {
284 let gradient = self.fetch_gradient(self.context_menu_target.get(), ui);
285
286 ui.send_message(ColorGradientEditorMessage::value(
287 self.handle,
288 MessageDirection::FromWidget,
289 gradient,
290 ));
291 }
292 } else if let Some(ColorFieldMessage::Color(color)) = message.data() {
293 if message.destination() == self.selector_field
294 && message.direction() == MessageDirection::FromWidget
295 && message.flags != SYNC_FLAG
296 {
297 let mut gradient = ColorGradient::new();
298
299 for (handle, pt) in ui
300 .node(self.points_canvas)
301 .children()
302 .iter()
303 .map(|c| (*c, ui.node(*c).query_component::<ColorPoint>().unwrap()))
304 {
305 gradient.add_point(GradientPoint::new(
306 pt.location,
307 if handle == self.context_menu_target.get() {
308 *color
309 } else {
310 pt.color()
311 },
312 ));
313 }
314
315 ui.send_message(ColorGradientEditorMessage::value(
316 self.handle,
317 MessageDirection::FromWidget,
318 gradient,
319 ));
320 }
321 } else if let Some(PopupMessage::Placement(Placement::Cursor(target))) = message.data() {
322 if message.destination() == self.context_menu.handle()
323 || message.destination() == self.point_context_menu.handle()
324 {
325 self.context_menu_open_position.set(ui.cursor_position());
326 self.context_menu_target.set(*target);
327
328 if message.destination() == self.point_context_menu.handle() {
329 if let Some(point) = ui
330 .try_get(self.context_menu_target.get())
331 .and_then(|n| n.query_component::<ColorPoint>())
332 {
333 let mut msg = ColorFieldMessage::color(
334 self.selector_field,
335 MessageDirection::ToWidget,
336 point.color(),
337 );
338
339 msg.flags = SYNC_FLAG;
340
341 ui.send_message(msg)
342 }
343 }
344 }
345 }
346 }
347}
348
349impl ColorGradientEditor {
350 fn fetch_gradient(&self, exclude: Handle<UiNode>, ui: &UserInterface) -> ColorGradient {
351 let mut gradient = ColorGradient::new();
352
353 for pt in ui
354 .node(self.points_canvas)
355 .children()
356 .iter()
357 .filter(|c| **c != exclude)
358 .map(|c| ui.node(*c).query_component::<ColorPoint>().unwrap())
359 {
360 gradient.add_point(GradientPoint::new(pt.location, pt.color()));
361 }
362
363 gradient
364 }
365}
366
367pub struct ColorGradientEditorBuilder {
368 widget_builder: WidgetBuilder,
369 color_gradient: ColorGradient,
370}
371
372fn create_color_points(
373 color_gradient: &ColorGradient,
374 point_context_menu: RcUiNodeHandle,
375 ctx: &mut BuildContext,
376) -> Vec<Handle<UiNode>> {
377 color_gradient
378 .points()
379 .iter()
380 .map(|pt| {
381 ColorPointBuilder::new(
382 WidgetBuilder::new()
383 .with_context_menu(point_context_menu.clone())
384 .with_cursor(Some(CursorIcon::EwResize))
385 .with_width(6.0)
386 .with_foreground(Brush::Solid(pt.color()).into()),
387 )
388 .with_location(pt.location())
389 .build(ctx)
390 })
391 .collect::<Vec<_>>()
392}
393
394impl ColorGradientEditorBuilder {
395 pub fn new(widget_builder: WidgetBuilder) -> Self {
396 Self {
397 widget_builder,
398 color_gradient: Default::default(),
399 }
400 }
401
402 pub fn with_color_gradient(mut self, gradient: ColorGradient) -> Self {
403 self.color_gradient = gradient;
404 self
405 }
406
407 pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
408 let add_point;
409 let context_menu = ContextMenuBuilder::new(
410 PopupBuilder::new(WidgetBuilder::new()).with_content(
411 StackPanelBuilder::new(WidgetBuilder::new().with_child({
412 add_point = MenuItemBuilder::new(WidgetBuilder::new())
413 .with_content(MenuItemContent::text("Add Point"))
414 .build(ctx);
415 add_point
416 }))
417 .build(ctx),
418 ),
419 )
420 .build(ctx);
421 let context_menu = RcUiNodeHandle::new(context_menu, ctx.sender());
422
423 let selector_field;
424 let remove_point;
425 let point_context_menu = ContextMenuBuilder::new(
426 PopupBuilder::new(WidgetBuilder::new().with_width(200.0)).with_content(
427 StackPanelBuilder::new(
428 WidgetBuilder::new()
429 .with_child({
430 remove_point = MenuItemBuilder::new(WidgetBuilder::new())
431 .with_content(MenuItemContent::text("Remove Point"))
432 .build(ctx);
433 remove_point
434 })
435 .with_child({
436 selector_field =
437 ColorFieldBuilder::new(WidgetBuilder::new().with_height(18.0))
438 .build(ctx);
439 selector_field
440 }),
441 )
442 .build(ctx),
443 ),
444 )
445 .build(ctx);
446 let point_context_menu = RcUiNodeHandle::new(point_context_menu, ctx.sender());
447
448 let points_canvas = ColorPointsCanvasBuilder::new(
449 WidgetBuilder::new()
450 .with_height(10.0)
451 .on_row(0)
452 .on_column(0)
453 .with_children(create_color_points(
454 &self.color_gradient,
455 point_context_menu.clone(),
456 ctx,
457 )),
458 )
459 .build(ctx);
460
461 let gradient_field = ColorGradientFieldBuilder::new(
462 WidgetBuilder::new()
463 .with_height(20.0)
464 .on_row(1)
465 .on_column(0),
466 )
467 .with_color_gradient(self.color_gradient)
468 .build(ctx);
469
470 let grid = GridBuilder::new(
471 WidgetBuilder::new()
472 .with_child(points_canvas)
473 .with_child(gradient_field),
474 )
475 .add_row(Row::auto())
476 .add_row(Row::stretch())
477 .add_column(Column::stretch())
478 .build(ctx);
479
480 let editor = ColorGradientEditor {
481 widget: self
482 .widget_builder
483 .with_preview_messages(true)
484 .with_context_menu(context_menu.clone())
485 .with_child(grid)
486 .build(ctx),
487 points_canvas,
488 gradient_field,
489 selector_field,
490 context_menu,
491 point_context_menu,
492 add_point,
493 remove_point,
494 context_menu_target: Cell::new(Default::default()),
495 context_menu_open_position: Cell::new(Default::default()),
496 };
497
498 ctx.add_node(UiNode::new(editor))
499 }
500}
501
502#[derive(Debug, Clone, PartialEq)]
503pub enum ColorPointMessage {
504 Location(f32),
505}
506
507impl ColorPointMessage {
508 define_constructor!(ColorPointMessage:Location => fn location(f32), layout: false);
509}
510
511#[derive(Default, Clone, Debug, Visit, Reflect, TypeUuidProvider, ComponentProvider)]
512#[type_uuid(id = "a493a603-3451-4005-8c80-559707729e70")]
513pub struct ColorPoint {
514 pub widget: Widget,
515 pub location: f32,
516 pub dragging: bool,
517}
518
519impl ConstructorProvider<UiNode, UserInterface> for ColorPoint {
520 fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
521 GraphNodeConstructor::new::<Self>()
522 .with_variant("Color Point", |ui| {
523 ColorPointBuilder::new(WidgetBuilder::new().with_name("Color Point"))
524 .build(&mut ui.build_ctx())
525 .into()
526 })
527 .with_group("Color")
528 }
529}
530
531define_widget_deref!(ColorPoint);
532
533impl Control for ColorPoint {
534 fn draw(&self, ctx: &mut DrawingContext) {
535 let size = self.bounding_rect().size;
536
537 ctx.push_triangle_filled([
538 Vector2::new(0.0, 0.0),
539 Vector2::new(size.x, 0.0),
540 Vector2::new(size.x * 0.5, size.y),
541 ]);
542
543 ctx.commit(
544 self.clip_bounds(),
545 self.foreground(),
546 CommandTexture::None,
547 None,
548 );
549 }
550
551 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
552 self.widget.handle_routed_message(ui, message);
553
554 if message.destination() == self.handle {
555 if message.direction() == MessageDirection::ToWidget {
556 if let Some(msg) = message.data::<ColorPointMessage>() {
557 match msg {
558 ColorPointMessage::Location(location) => {
559 if *location != self.location {
560 self.location = *location;
561 self.invalidate_layout();
562 ui.send_message(message.reverse());
563 }
564 }
565 }
566 }
567 }
568
569 if message.direction() == MessageDirection::FromWidget {
570 if let Some(msg) = message.data::<WidgetMessage>() {
571 match msg {
572 WidgetMessage::MouseDown { button, .. } => {
573 if *button == MouseButton::Left {
574 ui.capture_mouse(self.handle);
575
576 self.dragging = true;
577 }
578 }
579 WidgetMessage::MouseUp { button, .. } => {
580 if *button == MouseButton::Left {
581 ui.release_mouse_capture();
582
583 self.dragging = false;
584
585 ui.send_message(ColorPointMessage::location(
586 self.handle,
587 MessageDirection::FromWidget,
588 self.location,
589 ));
590 }
591 }
592 WidgetMessage::MouseMove { pos, .. } => {
593 if self.dragging {
594 let parent_canvas = ui.node(self.parent);
595
596 let cursor_x_local_to_parent =
597 parent_canvas.screen_to_local(*pos).x;
598
599 self.location = (cursor_x_local_to_parent
600 / parent_canvas.actual_local_size().x)
601 .clamp(0.0, 1.0);
602
603 self.invalidate_layout();
604 }
605 }
606 _ => (),
607 }
608 }
609 }
610 }
611 }
612}
613
614impl ColorPoint {
615 fn color(&self) -> Color {
616 if let Brush::Solid(color) = self.foreground.property {
617 color
618 } else {
619 unreachable!()
620 }
621 }
622}
623
624struct ColorPointBuilder {
625 widget_builder: WidgetBuilder,
626 location: f32,
627}
628
629impl ColorPointBuilder {
630 pub fn new(widget_builder: WidgetBuilder) -> Self {
631 Self {
632 widget_builder,
633 location: 0.0,
634 }
635 }
636
637 pub fn with_location(mut self, location: f32) -> Self {
638 self.location = location;
639 self
640 }
641
642 pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
643 ctx.add_node(UiNode::new(ColorPoint {
644 widget: self.widget_builder.build(ctx),
645 location: self.location,
646 dragging: false,
647 }))
648 }
649}
650
651#[derive(Clone, Visit, Reflect, Debug, TypeUuidProvider, ComponentProvider)]
652#[type_uuid(id = "2608955a-4095-4fd1-af71-99bcdf2600f0")]
653struct ColorPointsCanvas {
654 widget: Widget,
655}
656
657define_widget_deref!(ColorPointsCanvas);
658
659impl Control for ColorPointsCanvas {
660 fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
661 for &child in self.children() {
662 let child_ref = ui.node(child);
663 if let Some(color_point) = child_ref.query_component::<ColorPoint>() {
664 let x_pos = final_size.x * color_point.location - child_ref.desired_size().x * 0.5;
665
666 ui.arrange_node(
667 child,
668 &Rect::new(x_pos, 0.0, child_ref.desired_size().x, final_size.y),
669 );
670 }
671 }
672
673 final_size
674 }
675
676 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
677 self.widget.handle_routed_message(ui, message)
678 }
679}
680
681pub struct ColorPointsCanvasBuilder {
682 widget_builder: WidgetBuilder,
683}
684
685impl ColorPointsCanvasBuilder {
686 pub fn new(widget_builder: WidgetBuilder) -> Self {
687 Self { widget_builder }
688 }
689
690 pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
691 ctx.add_node(UiNode::new(ColorPointsCanvas {
692 widget: self.widget_builder.build(ctx),
693 }))
694 }
695}
696
697#[cfg(test)]
698mod test {
699 use crate::color::gradient::ColorGradientEditorBuilder;
700 use crate::{test::test_widget_deletion, widget::WidgetBuilder};
701
702 #[test]
703 fn test_deletion() {
704 test_widget_deletion(|ctx| {
705 ColorGradientEditorBuilder::new(WidgetBuilder::new()).build(ctx)
706 });
707 }
708}