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