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