1use crate::message::{CursorIcon, MessageData};
22use crate::style::resource::StyleResourceExt;
23use crate::style::Style;
24use crate::{
25 brush::Brush,
26 core::{
27 algebra::{Matrix3, Point2, SimdPartialOrd, Vector2, Vector3},
28 color::Color,
29 math::{
30 cubicf,
31 curve::{Curve, CurveKeyKind},
32 lerpf, wrap_angle, Rect,
33 },
34 parking_lot::{MappedMutexGuard, Mutex, MutexGuard},
35 pool::Handle,
36 reflect::prelude::*,
37 type_traits::prelude::*,
38 uuid_provider,
39 visitor::prelude::*,
40 SafeLock,
41 },
42 curve::key::{CurveKeyView, CurveKeyViewContainer},
43 draw::{CommandTexture, Draw, DrawingContext},
44 formatted_text::{FormattedText, FormattedTextBuilder},
45 grid::{Column, GridBuilder, Row},
46 menu::{ContextMenuBuilder, MenuItemBuilder, MenuItemContent, MenuItemMessage},
47 message::{ButtonState, KeyCode, MessageDirection, MouseButton, UiMessage},
48 numeric::{NumericUpDownBuilder, NumericUpDownMessage},
49 popup::PopupBuilder,
50 stack_panel::StackPanelBuilder,
51 text::TextBuilder,
52 widget::{Widget, WidgetBuilder, WidgetMessage},
53 BuildContext, Control, RcUiNodeHandle, Thickness, UiNode, UserInterface, VerticalAlignment,
54};
55use fxhash::FxHashSet;
56
57use crate::grid::Grid;
58use crate::menu::MenuItem;
59use crate::numeric::NumericUpDown;
60use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
61use fyrox_graph::SceneGraph;
62use std::{
63 cell::{Cell, RefCell},
64 sync::mpsc::Sender,
65};
66
67pub mod key;
68
69#[derive(Debug, Clone, PartialEq)]
70pub enum CurveEditorMessage {
71 SyncBackground(Vec<Curve>),
72 Sync(Vec<Curve>),
73 Colorize(Vec<(Uuid, Brush)>),
74 ViewPosition(Vector2<f32>),
75 Zoom(Vector2<f32>),
76 ZoomToFit {
77 after_layout: bool,
80 },
81 HighlightZones(Vec<HighlightZone>),
82
83 ChangeSelectedKeysKind(CurveKeyKind),
87 ChangeSelectedKeysValue(f32),
88 ChangeSelectedKeysLocation(f32),
89 RemoveSelection,
90 CopySelection,
91 PasteSelection,
92 AddKey(Vector2<f32>),
94 ShowBackgroundCurves(bool),
95}
96
97impl MessageData for CurveEditorMessage {
98 fn need_perform_layout(&self) -> bool {
99 matches!(self, Self::ZoomToFit { .. })
100 }
101}
102
103#[derive(Clone, Debug, PartialEq, Visit, Reflect, Default)]
105pub struct HighlightZone {
106 pub rect: Rect<f32>,
107 pub brush: Brush,
108}
109
110#[derive(Debug, Default)]
111pub struct CurveTransformCell(Mutex<CurveTransform>);
112
113impl Clone for CurveTransformCell {
114 fn clone(&self) -> Self {
115 Self(Mutex::new(self.0.safe_lock().clone()))
116 }
117}
118
119impl CurveTransformCell {
120 pub fn position(&self) -> Vector2<f32> {
122 self.0.safe_lock().position
123 }
124 pub fn scale(&self) -> Vector2<f32> {
126 self.0.safe_lock().scale
127 }
128 pub fn bounds(&self) -> Rect<f32> {
130 self.0.safe_lock().bounds
131 }
132 pub fn set_position(&self, position: Vector2<f32>) {
134 self.0.safe_lock().position = position
135 }
136 pub fn set_scale(&self, scale: Vector2<f32>) {
138 self.0.safe_lock().scale = scale;
139 }
140 pub fn set_bounds(&self, bounds: Rect<f32>) {
143 self.0.safe_lock().bounds = bounds;
144 }
145 pub fn x_step_iter(&self, grid_size: f32) -> StepIterator {
153 self.0.safe_lock().x_step_iter(grid_size)
154 }
155 pub fn y_step_iter(&self, grid_size: f32) -> StepIterator {
163 self.0.safe_lock().y_step_iter(grid_size)
164 }
165 pub fn update_transform(&self) {
167 self.0.safe_lock().update_transform();
168 }
169 pub fn curve_to_local(&self) -> MappedMutexGuard<Matrix3<f32>> {
171 MutexGuard::map(self.0.safe_lock(), |t| &mut t.curve_to_local)
172 }
173 pub fn curve_to_screen(&self) -> MappedMutexGuard<Matrix3<f32>> {
175 MutexGuard::map(self.0.safe_lock(), |t| &mut t.curve_to_screen)
176 }
177 pub fn local_to_curve(&self) -> MappedMutexGuard<Matrix3<f32>> {
181 MutexGuard::map(self.0.safe_lock(), |t| &mut t.local_to_curve)
182 }
183 pub fn screen_to_curve(&self) -> MappedMutexGuard<Matrix3<f32>> {
187 MutexGuard::map(self.0.safe_lock(), |t| &mut t.screen_to_curve)
188 }
189}
190
191pub const STANDARD_GRID_SIZE: f32 = 50.0;
193const STANDARD_STEP_SIZES: [f32; 10] = [0.1, 0.2, 0.5, 1.0, 2.0, 5.0, 10.0, 25.0, 50.0, 100.0];
197
198fn standardize_step(step: f32) -> f32 {
200 STANDARD_STEP_SIZES
201 .iter()
202 .copied()
203 .find(|x| step <= *x)
204 .unwrap_or(step)
205}
206
207#[derive(Clone, Debug)]
215pub struct CurveTransform {
216 pub position: Vector2<f32>,
218 pub scale: Vector2<f32>,
220 pub bounds: Rect<f32>,
222 pub curve_to_local: Matrix3<f32>,
228 pub local_to_curve: Matrix3<f32>,
231 pub screen_to_curve: Matrix3<f32>,
235 pub curve_to_screen: Matrix3<f32>,
237}
238
239impl Default for CurveTransform {
240 fn default() -> Self {
241 Self {
242 position: Vector2::default(),
243 scale: Vector2::new(1.0, 1.0),
244 bounds: Rect::default(),
245 curve_to_local: Matrix3::identity(),
246 local_to_curve: Matrix3::identity(),
247 screen_to_curve: Matrix3::identity(),
248 curve_to_screen: Matrix3::identity(),
249 }
250 }
251}
252
253impl CurveTransform {
254 pub fn update_transform(&mut self) {
256 let bounds = self.bounds;
257 let local_center = bounds.size.scale(0.5);
258 let mut curve_to_local = Matrix3::<f32>::identity();
259 curve_to_local.append_translation_mut(&-self.position);
261 curve_to_local.append_nonuniform_scaling_mut(&self.scale);
263 curve_to_local.append_nonuniform_scaling_mut(&Vector2::new(1.0, -1.0));
265 curve_to_local.append_translation_mut(&local_center);
267 let local_to_curve = curve_to_local.try_inverse().unwrap_or_default();
269 let mut curve_to_screen = curve_to_local;
270 curve_to_screen.append_translation_mut(&bounds.position);
272 let screen_to_curve = curve_to_screen.try_inverse().unwrap_or_default();
274 *self = CurveTransform {
275 curve_to_local,
276 local_to_curve,
277 screen_to_curve,
278 curve_to_screen,
279 ..*self
280 };
281 }
282 pub fn x_step_iter(&self, grid_size: f32) -> StepIterator {
290 let zoom = self.scale;
291 let step_size = grid_size / zoom.x.clamp(0.001, 1000.0);
292 let screen_left = self.bounds.position.x;
293 let screen_right = self.bounds.position.x + self.bounds.size.x;
294 let left = self
295 .screen_to_curve
296 .transform_point(&Point2::new(screen_left, 0.0))
297 .x;
298 let right = self
299 .screen_to_curve
300 .transform_point(&Point2::new(screen_right, 0.0))
301 .x;
302 StepIterator::new(standardize_step(step_size), left, right)
303 }
304 pub fn y_step_iter(&self, grid_size: f32) -> StepIterator {
312 let zoom = self.scale;
313 let step_size = grid_size / zoom.y.clamp(0.001, 1000.0);
314 let screen_top = self.bounds.position.y;
315 let screen_bottom = self.bounds.position.y + self.bounds.size.y;
316 let start = self
317 .screen_to_curve
318 .transform_point(&Point2::new(0.0, screen_bottom))
319 .y;
320 let end = self
321 .screen_to_curve
322 .transform_point(&Point2::new(0.0, screen_top))
323 .y;
324 StepIterator::new(standardize_step(step_size), start, end)
325 }
326}
327
328#[derive(Debug, Clone)]
330pub struct StepIterator {
331 pub step_size: f32,
332 index: isize,
333 end: isize,
334}
335
336impl StepIterator {
337 pub fn new(step: f32, start: f32, end: f32) -> Self {
340 Self {
341 step_size: step,
342 index: (start / step).floor() as isize,
343 end: ((end / step).ceil() as isize).saturating_add(1),
344 }
345 }
346}
347
348impl Iterator for StepIterator {
349 type Item = f32;
350
351 fn next(&mut self) -> Option<Self::Item> {
352 if self.index >= self.end {
353 return None;
354 }
355 let value = (self.index as f32) * self.step_size;
356 self.index += 1;
357 Some(value)
358 }
359}
360
361#[derive(Default, Clone, Visit, Reflect, Debug)]
362pub struct CurvesContainer {
363 curves: Vec<CurveKeyViewContainer>,
364}
365
366impl CurvesContainer {
367 pub fn from_native(brush: Brush, curves: &[Curve]) -> Self {
368 Self {
369 curves: curves
370 .iter()
371 .map(|curve| CurveKeyViewContainer::new(curve, brush.clone()))
372 .collect::<Vec<_>>(),
373 }
374 }
375
376 pub fn to_native(&self) -> Vec<Curve> {
377 self.curves
378 .iter()
379 .map(|view| view.curve())
380 .collect::<Vec<_>>()
381 }
382
383 pub fn container_of(&self, key_id: Uuid) -> Option<&CurveKeyViewContainer> {
384 self.curves
385 .iter()
386 .find(|curve| curve.key_ref(key_id).is_some())
387 }
388
389 pub fn key_ref(&self, uuid: Uuid) -> Option<&CurveKeyView> {
390 self.curves.iter().find_map(|keys| keys.key_ref(uuid))
392 }
393
394 pub fn key_mut(&mut self, uuid: Uuid) -> Option<&mut CurveKeyView> {
395 self.curves.iter_mut().find_map(|keys| keys.key_mut(uuid))
397 }
398
399 pub fn remove(&mut self, uuid: Uuid) {
400 for curve in self.curves.iter_mut() {
401 curve.remove(uuid);
402 }
403 }
404
405 pub fn iter(&self) -> impl Iterator<Item = &CurveKeyViewContainer> {
406 self.curves.iter()
407 }
408
409 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut CurveKeyViewContainer> {
410 self.curves.iter_mut()
411 }
412}
413
414#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
415#[reflect(derived_type = "UiNode")]
416pub struct CurveEditor {
417 widget: Widget,
418 background_curves: CurvesContainer,
419 curves: CurvesContainer,
420 #[visit(skip)]
421 #[reflect(hidden)]
422 curve_transform: CurveTransformCell,
423 key_brush: Brush,
424 selected_key_brush: Brush,
425 key_size: f32,
426 grid_brush: Brush,
427 background_curve_brush: Brush,
428 #[visit(skip)]
429 #[reflect(hidden)]
430 operation_context: Option<OperationContext>,
431 #[visit(skip)]
432 #[reflect(hidden)]
433 selection: Option<Selection>,
434 handle_radius: f32,
435 context_menu: ContextMenu,
436 #[visit(skip)]
437 #[reflect(hidden)]
438 text: RefCell<FormattedText>,
439 view_bounds: Option<Rect<f32>>,
440 show_x_values: bool,
441 show_y_values: bool,
442 grid_size: Vector2<f32>,
443 min_zoom: Vector2<f32>,
444 max_zoom: Vector2<f32>,
445 highlight_zones: Vec<HighlightZone>,
446 #[visit(skip)]
447 #[reflect(hidden)]
448 zoom_to_fit_timer: Option<usize>,
449 #[visit(skip)]
450 #[reflect(hidden)]
451 clipboard: Vec<(Vector2<f32>, CurveKeyKind)>,
452 show_background_curves: bool,
453}
454
455impl ConstructorProvider<UiNode, UserInterface> for CurveEditor {
456 fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
457 GraphNodeConstructor::new::<Self>()
458 .with_variant("Curve Editor", |ui| {
459 CurveEditorBuilder::new(WidgetBuilder::new().with_name("Curve Editor"))
460 .build(&mut ui.build_ctx())
461 .to_base()
462 .into()
463 })
464 .with_group("Input")
465 }
466}
467
468crate::define_widget_deref!(CurveEditor);
469
470#[derive(Default, Clone, Visit, Reflect, Debug)]
471struct ContextMenu {
472 widget: RcUiNodeHandle,
473 add_key: Handle<MenuItem>,
474 remove: Handle<MenuItem>,
475 key: Handle<MenuItem>,
476 make_constant: Handle<MenuItem>,
477 make_linear: Handle<MenuItem>,
478 make_cubic: Handle<MenuItem>,
479 zoom_to_fit: Handle<MenuItem>,
480 key_properties: Handle<Grid>,
481 key_value: Handle<NumericUpDown<f32>>,
482 key_location: Handle<NumericUpDown<f32>>,
483 copy_keys: Handle<MenuItem>,
484 paste_keys: Handle<MenuItem>,
485}
486
487#[derive(Clone, Debug)]
488struct DragEntry {
489 key_id: Uuid,
490 initial_position: Vector2<f32>,
491}
492
493#[derive(Clone, Debug)]
494enum OperationContext {
495 DragKeys {
496 initial_mouse_pos: Vector2<f32>,
498 entries: Vec<DragEntry>,
499 },
500 MoveView {
501 initial_mouse_pos: Vector2<f32>,
502 initial_view_pos: Vector2<f32>,
503 },
504 DragTangent {
505 key_id: Uuid,
506 left: bool,
507 },
508 BoxSelection {
509 initial_mouse_pos: Vector2<f32>,
511 min: Cell<Vector2<f32>>,
512 max: Cell<Vector2<f32>>,
513 },
514}
515
516impl OperationContext {
517 fn is_dragging(&self) -> bool {
518 matches!(
519 self,
520 OperationContext::DragKeys { .. } | OperationContext::DragTangent { .. }
521 )
522 }
523}
524
525#[derive(Clone, Debug)]
526enum Selection {
527 Keys { keys: FxHashSet<Uuid> },
528 LeftTangent { key_id: Uuid },
529 RightTangent { key_id: Uuid },
530}
531
532#[derive(Copy, Clone)]
533enum PickResult {
534 Key(Uuid),
535 LeftTangent(Uuid),
536 RightTangent(Uuid),
537}
538
539impl Selection {
540 fn single_key(key: Uuid) -> Self {
541 let mut keys = FxHashSet::default();
542 keys.insert(key);
543 Self::Keys { keys }
544 }
545}
546
547uuid_provider!(CurveEditor = "5c7b087e-871e-498d-b064-187b604a37d8");
548
549impl Control for CurveEditor {
550 fn draw(&self, ctx: &mut DrawingContext) {
551 self.curve_transform.set_bounds(self.screen_bounds());
552 self.curve_transform.update_transform();
553 self.draw_background(ctx);
554 self.draw_highlight_zones(ctx);
555 self.draw_grid(ctx);
556 if self.show_background_curves {
557 self.draw_curves(&self.background_curves, ctx);
558 self.draw_keys(&self.background_curves, &self.background_curve_brush, ctx);
559 }
560 self.draw_curves(&self.curves, ctx);
561 self.draw_keys(&self.curves, &self.key_brush, ctx);
562 self.draw_operation(ctx);
563 }
564
565 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
566 self.widget.handle_routed_message(ui, message);
567
568 if message.destination() == self.handle {
569 if let Some(msg) = message.data::<WidgetMessage>() {
570 match msg {
571 WidgetMessage::KeyUp(code) => match code {
572 KeyCode::Delete => self.remove_selection(ui),
573 KeyCode::KeyF => self.zoom_to_fit(&ui.sender()),
574 _ => (),
575 },
576 WidgetMessage::MouseMove { pos, state } => {
577 let is_dragging = self
578 .operation_context
579 .as_ref()
580 .is_some_and(|ctx| ctx.is_dragging());
581 if self.pick(*pos).is_some() || is_dragging {
582 if self.cursor.is_none() {
583 ui.send(
584 self.handle,
585 WidgetMessage::Cursor(Some(if is_dragging {
586 CursorIcon::Grabbing
587 } else {
588 CursorIcon::Grab
589 })),
590 );
591 }
592 } else if self.cursor.is_some() {
593 ui.send(self.handle, WidgetMessage::Cursor(None));
594 }
595
596 let curve_mouse_pos = self.screen_to_curve_space(*pos);
597 if let Some(operation_context) = self.operation_context.as_ref() {
598 match operation_context {
599 OperationContext::DragKeys {
600 entries,
601 initial_mouse_pos,
602 } => {
603 let local_delta = curve_mouse_pos - initial_mouse_pos;
604 for entry in entries {
605 if let Some(key) = self.curves.key_mut(entry.key_id) {
606 key.position = entry.initial_position + local_delta;
607 }
608 }
609 self.sort_keys();
610 self.invalidate_visual();
611 }
612 OperationContext::MoveView {
613 initial_mouse_pos,
614 initial_view_pos,
615 } => {
616 let d = *pos - initial_mouse_pos;
617 let zoom = self.curve_transform.scale();
618 let delta = Vector2::<f32>::new(-d.x / zoom.x, d.y / zoom.y);
621 ui.send(
622 self.handle,
623 CurveEditorMessage::ViewPosition(initial_view_pos + delta),
624 );
625 }
626 OperationContext::DragTangent { key_id: key, left } => {
627 if let Some(key) = self.curves.key_mut(*key) {
628 let key_pos = key.position;
629
630 let screen_key_pos = self
631 .curve_transform
632 .curve_to_screen()
633 .transform_point(&Point2::from(key_pos))
634 .coords;
635
636 if let CurveKeyKind::Cubic {
637 left_tangent,
638 right_tangent,
639 } = &mut key.kind
640 {
641 let mut local_delta = pos - screen_key_pos;
642 if *left {
643 local_delta.x = local_delta.x.min(f32::EPSILON);
644 } else {
645 local_delta.x = local_delta.x.max(f32::EPSILON);
646 }
647 let tangent =
648 (local_delta.y / local_delta.x).clamp(-10e6, 10e6);
649
650 if *left {
651 *left_tangent = tangent;
652 } else {
653 *right_tangent = tangent;
654 }
655
656 self.invalidate_visual();
657 } else {
658 unreachable!(
659 "attempt to edit tangents of non-cubic curve key!"
660 )
661 }
662 }
663 }
664 OperationContext::BoxSelection {
665 initial_mouse_pos,
666 min,
667 max,
668 ..
669 } => {
670 min.set(curve_mouse_pos.inf(initial_mouse_pos));
671 max.set(curve_mouse_pos.sup(initial_mouse_pos));
672 self.invalidate_visual();
673 }
674 }
675 } else if state.left == ButtonState::Pressed {
676 if let Some(selection) = self.selection.as_ref() {
677 match selection {
678 Selection::Keys { keys } => {
679 self.operation_context = Some(OperationContext::DragKeys {
680 entries: keys
681 .iter()
682 .map(|k| DragEntry {
683 key_id: *k,
684 initial_position: self
685 .curves
686 .key_ref(*k)
687 .map(|k| k.position)
688 .unwrap_or_default(),
689 })
690 .collect::<Vec<_>>(),
691 initial_mouse_pos: curve_mouse_pos,
692 });
693 }
694 Selection::LeftTangent { key_id: key } => {
695 self.operation_context =
696 Some(OperationContext::DragTangent {
697 key_id: *key,
698 left: true,
699 })
700 }
701 Selection::RightTangent { key_id: key } => {
702 self.operation_context =
703 Some(OperationContext::DragTangent {
704 key_id: *key,
705 left: false,
706 })
707 }
708 }
709 } else {
710 self.operation_context = Some(OperationContext::BoxSelection {
711 initial_mouse_pos: curve_mouse_pos,
712 min: Default::default(),
713 max: Default::default(),
714 })
715 }
716
717 if self.operation_context.is_some() {
718 ui.capture_mouse(self.handle);
719 }
720 }
721 }
722
723 WidgetMessage::MouseUp { .. } => {
724 if let Some(context) = self.operation_context.take() {
725 ui.release_mouse_capture();
726
727 match context {
729 OperationContext::DragKeys { .. }
730 | OperationContext::DragTangent { .. } => {
731 self.sort_keys();
733
734 self.send_curves(ui);
735 self.invalidate_visual();
736 }
737 OperationContext::BoxSelection { min, max, .. } => {
738 let min = min.get();
739 let max = max.get();
740
741 let rect =
742 Rect::new(min.x, min.y, max.x - min.x, max.y - min.y);
743
744 let mut selection = FxHashSet::default();
745 for curve in self.curves.iter() {
746 for key in curve.keys() {
747 if rect.contains(key.position) {
748 selection.insert(key.id);
749 }
750 }
751 }
752
753 if !selection.is_empty() {
754 self.set_selection(
755 Some(Selection::Keys { keys: selection }),
756 ui,
757 );
758 }
759 self.invalidate_visual();
760 }
761 _ => {}
762 }
763 }
764 }
765 WidgetMessage::MouseDown { pos, button } => match button {
766 MouseButton::Left => {
767 let pick_result = self.pick(*pos);
768
769 if let Some(picked) = pick_result {
770 match picked {
771 PickResult::Key(picked_key) => {
772 if let Some(picked_key_id) =
773 self.curves.key_ref(picked_key).map(|key| key.id)
774 {
775 if let Some(selection) = self.selection.as_mut() {
776 match selection {
777 Selection::Keys { keys } => {
778 if ui.keyboard_modifiers().control {
779 keys.insert(picked_key_id);
780 }
781 if !keys.contains(&picked_key_id) {
782 self.set_selection(
783 Some(Selection::single_key(
784 picked_key_id,
785 )),
786 ui,
787 );
788 }
789 }
790 Selection::LeftTangent { .. }
791 | Selection::RightTangent { .. } => self
792 .set_selection(
793 Some(Selection::single_key(
794 picked_key_id,
795 )),
796 ui,
797 ),
798 }
799 } else {
800 self.set_selection(
801 Some(Selection::single_key(picked_key_id)),
802 ui,
803 );
804 }
805 }
806 }
807 PickResult::LeftTangent(picked_key) => {
808 self.set_selection(
809 Some(Selection::LeftTangent { key_id: picked_key }),
810 ui,
811 );
812 }
813 PickResult::RightTangent(picked_key) => {
814 self.set_selection(
815 Some(Selection::RightTangent { key_id: picked_key }),
816 ui,
817 );
818 }
819 }
820 } else {
821 self.set_selection(None, ui);
822 }
823 }
824 MouseButton::Middle => {
825 ui.capture_mouse(self.handle);
826 self.operation_context = Some(OperationContext::MoveView {
827 initial_mouse_pos: *pos,
828 initial_view_pos: self.curve_transform.position(),
829 });
830 }
831 _ => (),
832 },
833 WidgetMessage::MouseWheel { amount, .. } => {
834 let k = if *amount < 0.0 { 0.9 } else { 1.1 };
835
836 let zoom = self.curve_transform.scale();
837 let new_zoom = if ui.keyboard_modifiers().shift {
838 Vector2::new(zoom.x * k, zoom.y)
839 } else if ui.keyboard_modifiers.control {
840 Vector2::new(zoom.x, zoom.y * k)
841 } else {
842 zoom * k
843 };
844
845 ui.send(self.handle, CurveEditorMessage::Zoom(new_zoom));
846
847 message.set_handled(true);
848 }
849 _ => {}
850 }
851 } else if let Some(msg) = message.data_for::<CurveEditorMessage>(self.handle) {
852 match msg {
853 CurveEditorMessage::SyncBackground(curves) => {
854 self.background_curves =
855 CurvesContainer::from_native(self.key_brush.clone(), curves);
856
857 for curve in self.background_curves.iter_mut() {
858 curve.brush = self.background_curve_brush.clone();
859 }
860
861 self.invalidate_visual();
862 }
863 CurveEditorMessage::Sync(curves) => {
864 let color_map = self
865 .curves
866 .iter()
867 .map(|curve| (curve.id(), curve.brush.clone()))
868 .collect::<Vec<_>>();
869
870 self.curves = CurvesContainer::from_native(self.key_brush.clone(), curves);
871
872 self.colorize(&color_map);
873
874 self.invalidate_visual();
875 }
876 CurveEditorMessage::Colorize(color_map) => {
877 self.colorize(color_map);
878
879 self.invalidate_visual();
880 }
881 CurveEditorMessage::ViewPosition(view_position) => {
882 self.set_view_position(*view_position);
883 ui.try_send_response(message);
884
885 self.invalidate_visual();
886 }
887 CurveEditorMessage::Zoom(zoom) => {
888 self.curve_transform
889 .set_scale(zoom.simd_clamp(self.min_zoom, self.max_zoom));
890 ui.try_send_response(message);
891
892 self.invalidate_visual();
893 }
894 CurveEditorMessage::RemoveSelection => {
895 self.remove_selection(ui);
896
897 self.invalidate_visual();
898 }
899 CurveEditorMessage::ChangeSelectedKeysKind(kind) => {
900 self.change_selected_keys_kind(kind.clone(), ui);
901
902 self.invalidate_visual();
903 }
904 CurveEditorMessage::AddKey(screen_pos) => {
905 let local_pos = self.screen_to_curve_space(*screen_pos);
906
907 let mut curves = Vec::new();
908 if let Some(selection) = self.selection.as_ref() {
909 if let Selection::Keys { keys } = selection {
910 curves.extend(self.curves.iter_mut().filter(|curve| {
911 for key in curve.keys() {
912 if keys.contains(&key.id) {
913 return true;
914 }
915 }
916 false
917 }));
918 }
919 } else {
920 curves.extend(self.curves.curves.iter_mut());
921 };
922
923 let mut added_keys = FxHashSet::default();
924 for curve in curves {
925 let id = Uuid::new_v4();
926 curve.add(CurveKeyView {
927 position: local_pos,
928 kind: CurveKeyKind::Linear,
929 id,
930 });
931 added_keys.insert(id);
932 }
933
934 self.set_selection(Some(Selection::Keys { keys: added_keys }), ui);
935 self.sort_keys();
936 self.send_curves(ui);
937
938 self.invalidate_visual();
939 }
940 CurveEditorMessage::ZoomToFit { after_layout } => {
941 if *after_layout {
942 self.zoom_to_fit_timer = Some(10);
945 } else {
946 self.zoom_to_fit(&ui.sender);
947 }
948
949 self.invalidate_visual();
950 }
951 CurveEditorMessage::ChangeSelectedKeysValue(value) => {
952 self.change_selected_keys_value(*value, ui);
953
954 self.invalidate_visual();
955 }
956 CurveEditorMessage::ChangeSelectedKeysLocation(location) => {
957 self.change_selected_keys_location(*location, ui);
958
959 self.invalidate_visual();
960 }
961 CurveEditorMessage::HighlightZones(zones) => {
962 self.highlight_zones.clone_from(zones);
963
964 self.invalidate_visual();
965 }
966 CurveEditorMessage::CopySelection => {
967 if let Some(Selection::Keys { keys }) = self.selection.as_ref() {
968 let menu_pos =
969 ui.node(self.context_menu.widget.handle()).screen_position();
970 let local_menu_pos = self.screen_to_curve_space(menu_pos);
971
972 self.clipboard.clear();
973 for key in keys {
974 for curve in self.curves.iter() {
975 if let Some(key) = curve.key_ref(*key) {
976 self.clipboard.push((
977 key.position - local_menu_pos,
978 key.kind.clone(),
979 ));
980 }
981 }
982 }
983 }
984 }
985 CurveEditorMessage::PasteSelection => {
986 if !self.clipboard.is_empty() {
987 let menu_pos =
988 ui.node(self.context_menu.widget.handle()).screen_position();
989 let local_menu_pos = self.screen_to_curve_space(menu_pos);
990
991 let mut selection = FxHashSet::default();
992 for (offset, kind) in self.clipboard.iter().cloned() {
993 for curve in self.curves.iter_mut() {
994 let id = Uuid::new_v4();
995
996 selection.insert(id);
997
998 curve.add(CurveKeyView {
999 position: local_menu_pos + offset,
1000 kind: kind.clone(),
1001 id,
1002 });
1003 }
1004 }
1005
1006 self.set_selection(Some(Selection::Keys { keys: selection }), ui);
1007 self.sort_keys();
1008 self.send_curves(ui);
1009
1010 self.invalidate_visual();
1011 }
1012 }
1013 CurveEditorMessage::ShowBackgroundCurves(show) => {
1014 if self.show_background_curves != *show {
1015 self.show_background_curves = *show;
1016 self.invalidate_layout();
1017 }
1018 }
1019 }
1020 }
1021 }
1022 }
1023
1024 fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
1025 if let Some(MenuItemMessage::Click) = message.data::<MenuItemMessage>() {
1026 if message.destination() == self.context_menu.remove {
1027 ui.send(self.handle, CurveEditorMessage::RemoveSelection);
1028 } else if message.destination() == self.context_menu.make_constant {
1029 ui.send(
1030 self.handle,
1031 CurveEditorMessage::ChangeSelectedKeysKind(CurveKeyKind::Constant),
1032 );
1033 } else if message.destination() == self.context_menu.make_linear {
1034 ui.send(
1035 self.handle,
1036 CurveEditorMessage::ChangeSelectedKeysKind(CurveKeyKind::Linear),
1037 );
1038 } else if message.destination() == self.context_menu.make_cubic {
1039 ui.send(
1040 self.handle,
1041 CurveEditorMessage::ChangeSelectedKeysKind(CurveKeyKind::Cubic {
1042 left_tangent: 0.0,
1043 right_tangent: 0.0,
1044 }),
1045 );
1046 } else if message.destination() == self.context_menu.add_key {
1047 let screen_pos = ui.node(self.context_menu.widget.handle()).screen_position();
1048 ui.send(self.handle, CurveEditorMessage::AddKey(screen_pos));
1049 } else if message.destination() == self.context_menu.zoom_to_fit {
1050 ui.send(
1051 self.handle,
1052 CurveEditorMessage::ZoomToFit {
1053 after_layout: false,
1054 },
1055 );
1056 } else if message.destination() == self.context_menu.copy_keys {
1057 ui.send(self.handle, CurveEditorMessage::CopySelection);
1058 } else if message.destination() == self.context_menu.paste_keys {
1059 ui.send(self.handle, CurveEditorMessage::PasteSelection);
1060 }
1061 } else if let Some(NumericUpDownMessage::<f32>::Value(value)) = message.data() {
1062 if message.direction() == MessageDirection::FromWidget && !message.handled() {
1063 if message.destination() == self.context_menu.key_value {
1064 ui.send(
1065 self.handle,
1066 CurveEditorMessage::ChangeSelectedKeysValue(*value),
1067 );
1068 } else if message.destination() == self.context_menu.key_location {
1069 ui.send(
1070 self.handle,
1071 CurveEditorMessage::ChangeSelectedKeysLocation(*value),
1072 );
1073 }
1074 }
1075 }
1076 }
1077
1078 fn update(&mut self, _dt: f32, ui: &mut UserInterface) {
1079 if let Some(timer) = self.zoom_to_fit_timer.as_mut() {
1080 *timer = timer.saturating_sub(1);
1081 if *timer == 0 {
1082 self.zoom_to_fit(&ui.sender);
1083 self.zoom_to_fit_timer = None;
1084 }
1085 }
1086 }
1087}
1088
1089fn draw_cubic(
1090 left_pos: Vector2<f32>,
1091 left_tangent: f32,
1092 right_pos: Vector2<f32>,
1093 right_tangent: f32,
1094 steps: usize,
1095 ctx: &mut DrawingContext,
1096) {
1097 let mut prev = left_pos;
1098 for i in 0..steps {
1099 let t = i as f32 / (steps - 1) as f32;
1100 let middle_x = lerpf(left_pos.x, right_pos.x, t);
1101 let middle_y = cubicf(left_pos.y, right_pos.y, t, left_tangent, right_tangent);
1102 let pt = Vector2::new(middle_x, middle_y);
1103 ctx.push_line(prev, pt, 1.0);
1104 prev = pt;
1105 }
1106}
1107
1108fn round_to_step(x: f32, step: f32) -> f32 {
1109 x - x % step
1110}
1111
1112impl CurveEditor {
1113 #[allow(clippy::let_and_return)] fn set_view_position(&mut self, position: Vector2<f32>) {
1115 self.curve_transform
1116 .set_position(self.view_bounds.map_or(position, |bounds| {
1117 let local_space_position = -position;
1118 let clamped_local_space_position = Vector2::new(
1119 local_space_position
1120 .x
1121 .clamp(bounds.position.x, bounds.position.x + 2.0 * bounds.size.x),
1122 local_space_position
1123 .y
1124 .clamp(bounds.position.y, bounds.position.y + 2.0 * bounds.size.y),
1125 );
1126 let clamped_view_space = -clamped_local_space_position;
1127 clamped_view_space
1128 }));
1129 }
1130
1131 fn colorize(&mut self, color_map: &[(Uuid, Brush)]) {
1132 for (curve_id, brush) in color_map.iter() {
1133 if let Some(curve) = self.curves.iter_mut().find(|curve| &curve.id() == curve_id) {
1134 curve.brush = brush.clone();
1135 }
1136 }
1137 }
1138
1139 fn zoom_to_fit(&mut self, sender: &Sender<UiMessage>) {
1140 let mut min = Vector2::repeat(f32::MAX);
1141 let mut max = Vector2::repeat(-f32::MAX);
1142
1143 for curve in self.curves.iter() {
1144 let bounds = curve.curve().bounds();
1145 if bounds.position.x < min.x {
1146 min.x = bounds.position.x;
1147 }
1148 if bounds.position.y < min.y {
1149 min.y = bounds.position.y;
1150 }
1151 let local_max = bounds.position + bounds.size;
1152 if local_max.x > max.x {
1153 max.x = local_max.x;
1154 }
1155 if local_max.y > max.y {
1156 max.y = local_max.y;
1157 }
1158 }
1159
1160 let mut bounds = Rect {
1161 position: min,
1162 size: max - min,
1163 };
1164
1165 if bounds.size.x < 0.001 {
1167 bounds.size.x = 0.001;
1168 }
1169 if bounds.size.y < 0.001 {
1170 bounds.size.y = 0.001;
1171 }
1172
1173 let center = bounds.center();
1174
1175 sender
1176 .send(UiMessage::for_widget(
1177 self.handle,
1178 CurveEditorMessage::Zoom(Vector2::new(
1179 self.actual_local_size().x / bounds.w(),
1180 self.actual_local_size().y / bounds.h(),
1181 )),
1182 ))
1183 .unwrap();
1184
1185 sender
1186 .send(UiMessage::for_widget(
1187 self.handle,
1188 CurveEditorMessage::ViewPosition(center),
1189 ))
1190 .unwrap();
1191 }
1192
1193 pub fn point_to_view_space(&self, point: Vector2<f32>) -> Vector2<f32> {
1195 self.curve_transform
1196 .local_to_curve()
1197 .transform_point(&Point2::from(point))
1198 .coords
1199 }
1200
1201 pub fn point_to_screen_space(&self, point: Vector2<f32>) -> Vector2<f32> {
1203 self.curve_transform
1204 .curve_to_screen()
1205 .transform_point(&Point2::from(point))
1206 .coords
1207 }
1208
1209 pub fn point_to_local_space(&self, point: Vector2<f32>) -> Vector2<f32> {
1211 self.curve_transform
1212 .curve_to_local()
1213 .transform_point(&Point2::from(point))
1214 .coords
1215 }
1216
1217 pub fn vector_to_screen_space(&self, vector: Vector2<f32>) -> Vector2<f32> {
1219 (*self.curve_transform.curve_to_screen() * Vector3::new(vector.x, vector.y, 0.0)).xy()
1220 }
1221
1222 pub fn screen_to_curve_space(&self, point: Vector2<f32>) -> Vector2<f32> {
1224 self.curve_transform
1225 .screen_to_curve()
1226 .transform_point(&Point2::from(point))
1227 .coords
1228 }
1229
1230 fn sort_keys(&mut self) {
1231 for curve in self.curves.iter_mut() {
1232 curve.sort_keys();
1233 }
1234 }
1235
1236 fn set_selection(&mut self, selection: Option<Selection>, ui: &UserInterface) {
1237 self.selection = selection;
1238
1239 ui.send(
1240 self.context_menu.remove,
1241 WidgetMessage::Enabled(self.selection.is_some()),
1242 );
1243 ui.send(
1244 self.context_menu.key,
1245 WidgetMessage::Enabled(self.selection.is_some()),
1246 );
1247 ui.send(
1248 self.context_menu.key_properties,
1249 WidgetMessage::Enabled(self.selection.is_some()),
1250 );
1251
1252 if let Some(Selection::Keys { keys }) = self.selection.as_ref() {
1253 if let Some(first) = keys.iter().next() {
1254 if let Some(key) = self.curves.key_ref(*first) {
1255 ui.send_handled(
1256 self.context_menu.key_location,
1257 NumericUpDownMessage::Value(key.position.x),
1258 );
1259 ui.send_handled(
1260 self.context_menu.key_value,
1261 NumericUpDownMessage::Value(key.position.y),
1262 );
1263 }
1264 }
1265 }
1266 }
1267
1268 fn remove_selection(&mut self, ui: &mut UserInterface) {
1269 if let Some(Selection::Keys { keys }) = self.selection.as_ref() {
1270 for &id in keys {
1271 self.curves.remove(id);
1272 }
1273
1274 self.set_selection(None, ui);
1275
1276 self.send_curves(ui);
1278 }
1279 }
1280
1281 fn change_selected_keys_kind(&mut self, kind: CurveKeyKind, ui: &mut UserInterface) {
1282 if let Some(Selection::Keys { keys }) = self.selection.as_ref() {
1283 for key in keys {
1284 if let Some(key) = self.curves.key_mut(*key) {
1285 key.kind = kind.clone();
1286 }
1287 }
1288
1289 self.send_curves(ui);
1290 }
1291 }
1292
1293 fn change_selected_keys_value(&mut self, value: f32, ui: &mut UserInterface) {
1294 if let Some(Selection::Keys { keys }) = self.selection.as_ref() {
1295 let mut modified = false;
1296 for key in keys {
1297 if let Some(key) = self.curves.key_mut(*key) {
1298 let key_value = &mut key.position.y;
1299 if (*key_value).ne(&value) {
1300 *key_value = value;
1301 modified = true;
1302 }
1303 }
1304 }
1305
1306 if modified {
1307 self.send_curves(ui);
1308 }
1309 }
1310 }
1311
1312 fn change_selected_keys_location(&mut self, location: f32, ui: &mut UserInterface) {
1313 if let Some(Selection::Keys { keys }) = self.selection.as_ref() {
1314 let mut modified = false;
1315 for key in keys {
1316 if let Some(key) = self.curves.key_mut(*key) {
1317 let key_location = &mut key.position.x;
1318 if (*key_location).ne(&location) {
1319 *key_location = location;
1320 modified = true;
1321 }
1322 }
1323 }
1324
1325 if modified {
1326 self.send_curves(ui);
1327 }
1328 }
1329 }
1330
1331 fn pick(&self, pos: Vector2<f32>) -> Option<PickResult> {
1333 for curve in self.curves.iter() {
1334 for key in curve.keys().iter() {
1337 let screen_pos = self.point_to_screen_space(key.position);
1338 let bounds = Rect::new(
1339 screen_pos.x - self.key_size * 0.5,
1340 screen_pos.y - self.key_size * 0.5,
1341 self.key_size,
1342 self.key_size,
1343 );
1344 if bounds.contains(pos) {
1345 return Some(PickResult::Key(key.id));
1346 }
1347
1348 if let CurveKeyKind::Cubic {
1350 left_tangent,
1351 right_tangent,
1352 } = key.kind
1353 {
1354 let left_handle_pos = self.tangent_screen_position(
1355 wrap_angle(left_tangent.atan()) + std::f32::consts::PI,
1356 key.position,
1357 );
1358
1359 if (left_handle_pos - pos).norm() <= self.key_size * 0.5 {
1360 return Some(PickResult::LeftTangent(key.id));
1361 }
1362
1363 let right_handle_pos = self
1364 .tangent_screen_position(wrap_angle(right_tangent.atan()), key.position);
1365
1366 if (right_handle_pos - pos).norm() <= self.key_size * 0.5 {
1367 return Some(PickResult::RightTangent(key.id));
1368 }
1369 }
1370 }
1371 }
1372 None
1373 }
1374
1375 fn tangent_screen_position(&self, angle: f32, key_position: Vector2<f32>) -> Vector2<f32> {
1376 self.point_to_screen_space(key_position)
1377 + Vector2::new(angle.cos(), angle.sin()).scale(self.handle_radius)
1378 }
1379
1380 fn tangent_local_position(&self, angle: f32, key_position: Vector2<f32>) -> Vector2<f32> {
1381 self.point_to_local_space(key_position)
1382 + Vector2::new(angle.cos(), angle.sin()).scale(self.handle_radius)
1383 }
1384
1385 fn send_curves(&self, ui: &UserInterface) {
1386 ui.post(
1387 self.handle,
1388 CurveEditorMessage::Sync(self.curves.to_native()),
1389 );
1390 }
1391
1392 fn draw_background(&self, ctx: &mut DrawingContext) {
1393 let bounds = self.bounding_rect();
1394 ctx.push_rect_filled(&bounds, None);
1396 ctx.commit(
1397 self.clip_bounds(),
1398 self.background(),
1399 CommandTexture::None,
1400 &self.material,
1401 None,
1402 );
1403 }
1404
1405 fn draw_highlight_zones(&self, ctx: &mut DrawingContext) {
1406 for zone in self.highlight_zones.iter() {
1407 let left_top_corner = self.point_to_local_space(zone.rect.left_top_corner());
1408 let bottom_right_corner = self.point_to_local_space(zone.rect.right_bottom_corner());
1409 ctx.push_rect_filled(
1410 &Rect::new(
1411 left_top_corner.x,
1412 left_top_corner.y,
1413 bottom_right_corner.x - left_top_corner.x,
1414 bottom_right_corner.y - left_top_corner.y,
1415 ),
1416 None,
1417 );
1418 ctx.commit(
1419 self.clip_bounds(),
1420 zone.brush.clone(),
1421 CommandTexture::None,
1422 &self.material,
1423 None,
1424 );
1425 }
1426 }
1427
1428 fn draw_grid(&self, ctx: &mut DrawingContext) {
1429 let screen_bounds = self.screen_bounds();
1430
1431 let zoom = self.curve_transform.scale();
1432 let step_size_x = self.grid_size.x / zoom.x;
1433 let step_size_y = self.grid_size.y / zoom.y;
1434
1435 let mut local_left_bottom = self.screen_to_curve_space(screen_bounds.left_top_corner());
1436 let local_left_bottom_n = local_left_bottom;
1437 local_left_bottom.x = round_to_step(local_left_bottom.x, step_size_x);
1438 local_left_bottom.y = round_to_step(local_left_bottom.y, step_size_y);
1439
1440 let mut local_right_top = self.screen_to_curve_space(screen_bounds.right_bottom_corner());
1441 local_right_top.x = round_to_step(local_right_top.x, step_size_x);
1442 local_right_top.y = round_to_step(local_right_top.y, step_size_y);
1443
1444 for y in self.curve_transform.y_step_iter(self.grid_size.y) {
1445 ctx.push_line(
1446 self.point_to_local_space(Vector2::new(local_left_bottom.x - step_size_x, y)),
1447 self.point_to_local_space(Vector2::new(local_right_top.x + step_size_x, y)),
1448 1.0,
1449 );
1450 }
1451
1452 for x in self.curve_transform.x_step_iter(self.grid_size.x) {
1453 ctx.push_line(
1454 self.point_to_local_space(Vector2::new(x, local_left_bottom.y + step_size_y)),
1455 self.point_to_local_space(Vector2::new(x, local_right_top.y - step_size_y)),
1456 1.0,
1457 );
1458 }
1459
1460 let vb = self.point_to_local_space(Vector2::new(0.0, -10e6));
1462 let ve = self.point_to_local_space(Vector2::new(0.0, 10e6));
1463 ctx.push_line(vb, ve, 2.0);
1464
1465 let hb = self.point_to_local_space(Vector2::new(-10e6, 0.0));
1466 let he = self.point_to_local_space(Vector2::new(10e6, 0.0));
1467 ctx.push_line(hb, he, 2.0);
1468
1469 ctx.commit(
1470 self.clip_bounds(),
1471 self.grid_brush.clone(),
1472 CommandTexture::None,
1473 &self.material,
1474 None,
1475 );
1476
1477 let mut text = self.text.borrow_mut();
1479
1480 if self.show_y_values {
1481 for y in self.curve_transform.y_step_iter(self.grid_size.y) {
1482 text.set_text(format!("{y:.1}")).measure_and_arrange();
1483 ctx.draw_text(
1484 self.clip_bounds(),
1485 self.point_to_local_space(Vector2::new(local_left_bottom_n.x, y)),
1486 &self.material,
1487 &text,
1488 );
1489 }
1490 }
1491
1492 if self.show_x_values {
1493 for x in self.curve_transform.x_step_iter(self.grid_size.x) {
1494 text.set_text(format!("{x:.1}")).measure_and_arrange();
1495 ctx.draw_text(
1496 self.clip_bounds(),
1497 self.point_to_local_space(Vector2::new(x, local_left_bottom_n.y)),
1498 &self.material,
1499 &text,
1500 );
1501 }
1502 }
1503 }
1504
1505 fn draw_curves(&self, curves: &CurvesContainer, ctx: &mut DrawingContext) {
1506 let screen_bounds = self.screen_bounds();
1507
1508 for curve in curves.iter() {
1509 let draw_keys = curve.keys();
1510
1511 if let Some(first) = draw_keys.first() {
1512 let screen_pos = self.point_to_local_space(first.position);
1513 ctx.push_line(Vector2::new(0.0, screen_pos.y), screen_pos, 1.0);
1514 }
1515 if let Some(last) = draw_keys.last() {
1516 let screen_pos = self.point_to_local_space(last.position);
1517 ctx.push_line(
1518 screen_pos,
1519 Vector2::new(screen_bounds.x() + screen_bounds.w(), screen_pos.y),
1520 1.0,
1521 );
1522 }
1523
1524 for pair in draw_keys.windows(2) {
1525 let left = &pair[0];
1526 let right = &pair[1];
1527
1528 let left_pos = self.point_to_local_space(left.position);
1529 let right_pos = self.point_to_local_space(right.position);
1530
1531 let steps = ((right_pos.x - left_pos.x).abs() / 2.0) as usize;
1532
1533 match (&left.kind, &right.kind) {
1534 (CurveKeyKind::Constant, CurveKeyKind::Constant)
1536 | (CurveKeyKind::Constant, CurveKeyKind::Linear)
1537 | (CurveKeyKind::Constant, CurveKeyKind::Cubic { .. }) => {
1538 ctx.push_line(left_pos, Vector2::new(right_pos.x, left_pos.y), 1.0);
1539 ctx.push_line(Vector2::new(right_pos.x, left_pos.y), right_pos, 1.0);
1540 }
1541
1542 (CurveKeyKind::Linear, CurveKeyKind::Constant)
1544 | (CurveKeyKind::Linear, CurveKeyKind::Linear)
1545 | (CurveKeyKind::Linear, CurveKeyKind::Cubic { .. }) => {
1546 ctx.push_line(left_pos, right_pos, 1.0)
1547 }
1548
1549 (
1551 CurveKeyKind::Cubic {
1552 right_tangent: left_tangent,
1553 ..
1554 },
1555 CurveKeyKind::Constant,
1556 )
1557 | (
1558 CurveKeyKind::Cubic {
1559 right_tangent: left_tangent,
1560 ..
1561 },
1562 CurveKeyKind::Linear,
1563 ) => draw_cubic(left_pos, *left_tangent, right_pos, 0.0, steps, ctx),
1564
1565 (
1567 CurveKeyKind::Cubic {
1568 right_tangent: left_tangent,
1569 ..
1570 },
1571 CurveKeyKind::Cubic {
1572 left_tangent: right_tangent,
1573 ..
1574 },
1575 ) => draw_cubic(
1576 left_pos,
1577 *left_tangent,
1578 right_pos,
1579 *right_tangent,
1580 steps,
1581 ctx,
1582 ),
1583 }
1584 }
1585 ctx.commit(
1586 self.clip_bounds(),
1587 curve.brush.clone(),
1588 CommandTexture::None,
1589 &self.material,
1590 None,
1591 );
1592 }
1593 }
1594
1595 fn draw_keys(&self, curves: &CurvesContainer, brush: &Brush, ctx: &mut DrawingContext) {
1596 for curve in curves.iter() {
1597 let keys_to_draw = curve.keys();
1598
1599 for key in keys_to_draw.iter() {
1600 let origin = self.point_to_local_space(key.position);
1601 let size = Vector2::new(self.key_size, self.key_size);
1602 let half_size = size.scale(0.5);
1603
1604 ctx.push_rect_filled(
1605 &Rect::new(
1606 origin.x - half_size.x,
1607 origin.y - half_size.x,
1608 size.x,
1609 size.y,
1610 ),
1611 None,
1612 );
1613
1614 let mut selected = false;
1615 if let Some(selection) = self.selection.as_ref() {
1616 match selection {
1617 Selection::Keys { keys } => {
1618 selected = keys.contains(&key.id);
1619 }
1620 Selection::LeftTangent { key_id } | Selection::RightTangent { key_id } => {
1621 selected = key.id == *key_id;
1622 }
1623 }
1624 }
1625
1626 if selected {
1628 let (show_left, show_right) =
1629 match self.curves.container_of(key.id).and_then(|container| {
1630 container
1631 .key_position(key.id)
1632 .and_then(|i| keys_to_draw.get(i.wrapping_sub(1)))
1633 }) {
1634 Some(left) => match (&left.kind, &key.kind) {
1635 (CurveKeyKind::Cubic { .. }, CurveKeyKind::Cubic { .. }) => {
1636 (true, true)
1637 }
1638 (CurveKeyKind::Linear, CurveKeyKind::Cubic { .. })
1639 | (CurveKeyKind::Constant, CurveKeyKind::Cubic { .. }) => {
1640 (false, true)
1641 }
1642 _ => (false, false),
1643 },
1644 None => match key.kind {
1645 CurveKeyKind::Cubic { .. } => (false, true),
1646 _ => (false, false),
1647 },
1648 };
1649
1650 if let CurveKeyKind::Cubic {
1651 left_tangent,
1652 right_tangent,
1653 } = key.kind
1654 {
1655 if show_left {
1656 let left_handle_pos = self.tangent_local_position(
1657 wrap_angle(left_tangent.atan()) + std::f32::consts::PI,
1658 key.position,
1659 );
1660 ctx.push_line(origin, left_handle_pos, 1.0);
1661 ctx.push_circle_filled(
1662 left_handle_pos,
1663 self.key_size * 0.5,
1664 6,
1665 Default::default(),
1666 );
1667 }
1668
1669 if show_right {
1670 let right_handle_pos = self.tangent_local_position(
1671 wrap_angle(right_tangent.atan()),
1672 key.position,
1673 );
1674 ctx.push_line(origin, right_handle_pos, 1.0);
1675 ctx.push_circle_filled(
1676 right_handle_pos,
1677 self.key_size * 0.5,
1678 6,
1679 Default::default(),
1680 );
1681 }
1682 }
1683 }
1684
1685 ctx.commit(
1686 self.clip_bounds(),
1687 if selected {
1688 self.selected_key_brush.clone()
1689 } else {
1690 brush.clone()
1691 },
1692 CommandTexture::None,
1693 &self.material,
1694 None,
1695 );
1696 }
1697 }
1698 }
1699
1700 fn draw_operation(&self, ctx: &mut DrawingContext) {
1701 if let Some(OperationContext::BoxSelection { min, max, .. }) =
1702 self.operation_context.as_ref()
1703 {
1704 let min = self.point_to_local_space(min.get());
1705 let max = self.point_to_local_space(max.get());
1706 let rect = Rect::new(min.x, min.y, max.x - min.x, max.y - min.y);
1707
1708 ctx.push_rect(&rect, 1.0);
1709 ctx.commit(
1710 self.clip_bounds(),
1711 Brush::Solid(Color::WHITE),
1712 CommandTexture::None,
1713 &self.material,
1714 None,
1715 );
1716 }
1717 }
1718}
1719
1720pub struct CurveEditorBuilder {
1721 widget_builder: WidgetBuilder,
1722 background_curves: Vec<Curve>,
1723 curves: Vec<Curve>,
1724 view_position: Vector2<f32>,
1725 zoom: f32,
1726 view_bounds: Option<Rect<f32>>,
1727 show_x_values: bool,
1728 show_y_values: bool,
1729 grid_size: Vector2<f32>,
1730 min_zoom: Vector2<f32>,
1731 max_zoom: Vector2<f32>,
1732 highlight_zones: Vec<HighlightZone>,
1733 show_background_curves: bool,
1734}
1735
1736impl CurveEditorBuilder {
1737 pub fn new(widget_builder: WidgetBuilder) -> Self {
1738 Self {
1739 widget_builder,
1740 background_curves: Default::default(),
1741 curves: Default::default(),
1742 view_position: Default::default(),
1743 zoom: 1.0,
1744 view_bounds: None,
1745 show_x_values: true,
1746 show_y_values: true,
1747 grid_size: Vector2::new(STANDARD_GRID_SIZE, STANDARD_GRID_SIZE),
1748 min_zoom: Vector2::new(0.001, 0.001),
1749 max_zoom: Vector2::new(1000.0, 1000.0),
1750 highlight_zones: Default::default(),
1751 show_background_curves: true,
1752 }
1753 }
1754
1755 pub fn with_background_curves(mut self, curves: Vec<Curve>) -> Self {
1756 self.background_curves = curves;
1757 self
1758 }
1759
1760 pub fn with_curves(mut self, curves: Vec<Curve>) -> Self {
1761 self.curves = curves;
1762 self
1763 }
1764
1765 pub fn with_zoom(mut self, zoom: f32) -> Self {
1766 self.zoom = zoom;
1767 self
1768 }
1769
1770 pub fn with_view_position(mut self, view_position: Vector2<f32>) -> Self {
1771 self.view_position = view_position;
1772 self
1773 }
1774
1775 pub fn with_show_x_values(mut self, show_x_values: bool) -> Self {
1776 self.show_x_values = show_x_values;
1777 self
1778 }
1779
1780 pub fn with_show_y_values(mut self, show_y_values: bool) -> Self {
1781 self.show_y_values = show_y_values;
1782 self
1783 }
1784
1785 pub fn with_view_bounds(mut self, bounds: Rect<f32>) -> Self {
1787 self.view_bounds = Some(bounds);
1788 self
1789 }
1790
1791 pub fn with_grid_size(mut self, size: Vector2<f32>) -> Self {
1792 self.grid_size = size;
1793 self
1794 }
1795
1796 pub fn with_min_zoom(mut self, min_zoom: Vector2<f32>) -> Self {
1797 self.min_zoom = min_zoom;
1798 self
1799 }
1800
1801 pub fn with_max_zoom(mut self, max_zoom: Vector2<f32>) -> Self {
1802 self.max_zoom = max_zoom;
1803 self
1804 }
1805
1806 pub fn with_highlight_zone(mut self, zones: Vec<HighlightZone>) -> Self {
1807 self.highlight_zones = zones;
1808 self
1809 }
1810
1811 pub fn with_show_background_curves(mut self, show: bool) -> Self {
1812 self.show_background_curves = show;
1813 self
1814 }
1815
1816 pub fn build(mut self, ctx: &mut BuildContext) -> Handle<CurveEditor> {
1817 let background_curve_brush = ctx.style.get_or_default::<Brush>(Style::BRUSH_LIGHT);
1818 let key_brush = Brush::Solid(Color::opaque(140, 140, 140));
1819
1820 let mut background_curves = CurvesContainer::from_native(key_brush.clone(), &self.curves);
1821 for curve in background_curves.iter_mut() {
1822 curve.brush = background_curve_brush.clone();
1823 }
1824
1825 let curves = CurvesContainer::from_native(key_brush.clone(), &self.curves);
1826
1827 let add_key;
1828 let remove;
1829 let make_constant;
1830 let make_linear;
1831 let make_cubic;
1832 let key;
1833 let zoom_to_fit;
1834 let key_properties;
1835 let key_value;
1836 let key_location;
1837 let copy_keys;
1838 let paste_keys;
1839 let context_menu =
1840 ContextMenuBuilder::new(
1841 PopupBuilder::new(WidgetBuilder::new())
1842 .with_content(
1843 StackPanelBuilder::new(
1844 WidgetBuilder::new()
1845 .with_child({
1846 key_properties = GridBuilder::new(
1847 WidgetBuilder::new()
1848 .with_enabled(false)
1849 .with_child(
1850 TextBuilder::new(
1851 WidgetBuilder::new()
1852 .with_vertical_alignment(
1853 VerticalAlignment::Center,
1854 )
1855 .with_margin(Thickness::uniform(1.0))
1856 .on_row(0)
1857 .on_column(0),
1858 )
1859 .with_text("Location")
1860 .build(ctx),
1861 )
1862 .with_child({
1863 key_location = NumericUpDownBuilder::<f32>::new(
1864 WidgetBuilder::new()
1865 .with_margin(Thickness::uniform(1.0))
1866 .on_row(0)
1867 .on_column(1),
1868 )
1869 .build(ctx);
1870 key_location
1871 })
1872 .with_child(
1873 TextBuilder::new(
1874 WidgetBuilder::new()
1875 .with_vertical_alignment(
1876 VerticalAlignment::Center,
1877 )
1878 .with_margin(Thickness::uniform(1.0))
1879 .on_row(1)
1880 .on_column(0),
1881 )
1882 .with_text("Value")
1883 .build(ctx),
1884 )
1885 .with_child({
1886 key_value = NumericUpDownBuilder::<f32>::new(
1887 WidgetBuilder::new()
1888 .with_margin(Thickness::uniform(1.0))
1889 .on_row(1)
1890 .on_column(1),
1891 )
1892 .build(ctx);
1893 key_value
1894 }),
1895 )
1896 .add_column(Column::auto())
1897 .add_column(Column::stretch())
1898 .add_row(Row::strict(22.0))
1899 .add_row(Row::strict(22.0))
1900 .build(ctx);
1901 key_properties
1902 })
1903 .with_child({
1904 add_key = MenuItemBuilder::new(WidgetBuilder::new())
1905 .with_content(MenuItemContent::text("Add Key"))
1906 .build(ctx);
1907 add_key
1908 })
1909 .with_child({
1910 remove = MenuItemBuilder::new(
1911 WidgetBuilder::new().with_enabled(false),
1912 )
1913 .with_content(MenuItemContent::text("Remove"))
1914 .build(ctx);
1915 remove
1916 })
1917 .with_child({
1918 key = MenuItemBuilder::new(
1919 WidgetBuilder::new().with_enabled(false),
1920 )
1921 .with_content(MenuItemContent::text("Key..."))
1922 .with_items(vec![
1923 {
1924 make_constant =
1925 MenuItemBuilder::new(WidgetBuilder::new())
1926 .with_content(MenuItemContent::text("Constant"))
1927 .build(ctx);
1928 make_constant
1929 },
1930 {
1931 make_linear =
1932 MenuItemBuilder::new(WidgetBuilder::new())
1933 .with_content(MenuItemContent::text("Linear"))
1934 .build(ctx);
1935 make_linear
1936 },
1937 {
1938 make_cubic = MenuItemBuilder::new(WidgetBuilder::new())
1939 .with_content(MenuItemContent::text("Cubic"))
1940 .build(ctx);
1941 make_cubic
1942 },
1943 ])
1944 .build(ctx);
1945 key
1946 })
1947 .with_child({
1948 zoom_to_fit = MenuItemBuilder::new(WidgetBuilder::new())
1949 .with_content(MenuItemContent::text("Zoom To Fit"))
1950 .build(ctx);
1951 zoom_to_fit
1952 })
1953 .with_child({
1954 copy_keys = MenuItemBuilder::new(WidgetBuilder::new())
1955 .with_content(MenuItemContent::text("Copy Selected Keys"))
1956 .build(ctx);
1957 copy_keys
1958 })
1959 .with_child({
1960 paste_keys = MenuItemBuilder::new(WidgetBuilder::new())
1961 .with_content(MenuItemContent::text("Paste Keys"))
1962 .build(ctx);
1963 paste_keys
1964 }),
1965 )
1966 .build(ctx),
1967 )
1968 .with_restrict_picking(false),
1969 )
1970 .build(ctx);
1971 let context_menu = RcUiNodeHandle::new(context_menu, ctx.sender());
1972
1973 if self.widget_builder.foreground.is_none() {
1974 self.widget_builder.foreground = Some(ctx.style.property(Style::BRUSH_BRIGHT))
1975 }
1976
1977 let editor = CurveEditor {
1978 widget: self
1979 .widget_builder
1980 .with_context_menu(context_menu.clone())
1981 .with_preview_messages(true)
1982 .with_need_update(true)
1983 .build(ctx),
1984 background_curves,
1985 curves,
1986 curve_transform: Default::default(),
1987 key_brush,
1988 selected_key_brush: Brush::Solid(Color::opaque(220, 220, 220)),
1989 key_size: 8.0,
1990 handle_radius: 36.0,
1991 operation_context: None,
1992 grid_brush: Brush::Solid(Color::from_rgba(110, 110, 110, 50)),
1993 selection: None,
1994 text: RefCell::new(
1995 FormattedTextBuilder::new(ctx.default_font())
1996 .with_brush(Brush::Solid(Color::opaque(100, 100, 100)))
1997 .build(),
1998 ),
1999 context_menu: ContextMenu {
2000 widget: context_menu,
2001 add_key,
2002 remove,
2003 make_constant,
2004 make_linear,
2005 make_cubic,
2006 key,
2007 zoom_to_fit,
2008 key_properties,
2009 key_value,
2010 key_location,
2011 copy_keys,
2012 paste_keys,
2013 },
2014 view_bounds: self.view_bounds,
2015 show_x_values: self.show_x_values,
2016 show_y_values: self.show_y_values,
2017 grid_size: self.grid_size,
2018 min_zoom: self.min_zoom,
2019 max_zoom: self.max_zoom,
2020 highlight_zones: self.highlight_zones,
2021 zoom_to_fit_timer: None,
2022 clipboard: Default::default(),
2023 background_curve_brush,
2024 show_background_curves: self.show_background_curves,
2025 };
2026
2027 ctx.add(editor)
2028 }
2029}
2030
2031#[cfg(test)]
2032mod test {
2033 use crate::{curve::CurveEditorBuilder, test::test_widget_deletion, widget::WidgetBuilder};
2034 #[test]
2035 fn test_curve_editor_deletion() {
2036 test_widget_deletion(|ctx| CurveEditorBuilder::new(WidgetBuilder::new()).build(ctx));
2037 }
2038}