Skip to main content

fyrox_ui/curve/
mod.rs

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