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