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