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