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