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