rg3d_ui/curve/
mod.rs

1use crate::{
2    brush::Brush,
3    core::{
4        algebra::{Matrix3, Point2, Vector2, Vector3},
5        color::Color,
6        curve::{Curve, CurveKeyKind},
7        math::{cubicf, inf_sup_cubicf, lerpf, wrap_angle, Rect},
8        pool::Handle,
9        uuid::Uuid,
10    },
11    curve::key::{CurveKeyView, KeyContainer},
12    define_constructor,
13    draw::{CommandTexture, Draw, DrawingContext},
14    formatted_text::{FormattedText, FormattedTextBuilder},
15    menu::{MenuItemBuilder, MenuItemContent, MenuItemMessage},
16    message::{ButtonState, KeyCode, MessageDirection, MouseButton, UiMessage},
17    popup::PopupBuilder,
18    stack_panel::StackPanelBuilder,
19    widget::{Widget, WidgetBuilder, WidgetMessage},
20    BuildContext, Control, UiNode, UserInterface,
21};
22use fxhash::FxHashSet;
23use std::{
24    any::{Any, TypeId},
25    cell::{Cell, RefCell},
26    ops::{Deref, DerefMut},
27};
28
29pub mod key;
30
31#[derive(Debug, Clone, PartialEq)]
32pub enum CurveEditorMessage {
33    Sync(Curve),
34    ViewPosition(Vector2<f32>),
35    Zoom(f32),
36    ZoomToFit,
37
38    // Internal messages. Use only when you know what you're doing.
39    // These are internal because you must use Sync message to request changes
40    // in the curve editor.
41    ChangeSelectedKeysKind(CurveKeyKind),
42    RemoveSelection,
43    // Position in screen coordinates.
44    AddKey(Vector2<f32>),
45}
46
47impl CurveEditorMessage {
48    define_constructor!(CurveEditorMessage:Sync => fn sync(Curve), layout: false);
49    define_constructor!(CurveEditorMessage:ViewPosition => fn view_position(Vector2<f32>), layout: false);
50    define_constructor!(CurveEditorMessage:Zoom => fn zoom(f32), layout: false);
51    define_constructor!(CurveEditorMessage:ZoomToFit => fn zoom_to_fit(), layout: false);
52    // Internal. Use only when you know what you're doing.
53    define_constructor!(CurveEditorMessage:RemoveSelection => fn remove_selection(), layout: false);
54    define_constructor!(CurveEditorMessage:ChangeSelectedKeysKind => fn change_selected_keys_kind(CurveKeyKind), layout: false);
55    define_constructor!(CurveEditorMessage:AddKey => fn add_key(Vector2<f32>), layout: false);
56}
57
58#[derive(Clone)]
59pub struct CurveEditor {
60    widget: Widget,
61    key_container: KeyContainer,
62    zoom: f32,
63    view_position: Vector2<f32>,
64    // Transforms a point from local to view coordinates.
65    view_matrix: Cell<Matrix3<f32>>,
66    // Transforms a point from local to screen coordinates.
67    // View and screen coordinates are different:
68    //  - view is a local viewer of curve editor
69    //  - screen is global space
70    screen_matrix: Cell<Matrix3<f32>>,
71    // Transform a point from screen space (i.e. mouse position) to the
72    // local space (the space where all keys are)
73    inv_screen_matrix: Cell<Matrix3<f32>>,
74    key_brush: Brush,
75    selected_key_brush: Brush,
76    key_size: f32,
77    grid_brush: Brush,
78    operation_context: Option<OperationContext>,
79    selection: Option<Selection>,
80    handle_radius: f32,
81    context_menu: ContextMenu,
82    text: RefCell<FormattedText>,
83}
84
85crate::define_widget_deref!(CurveEditor);
86
87#[derive(Clone)]
88struct ContextMenu {
89    widget: Handle<UiNode>,
90    add_key: Handle<UiNode>,
91    remove: Handle<UiNode>,
92    key: Handle<UiNode>,
93    make_constant: Handle<UiNode>,
94    make_linear: Handle<UiNode>,
95    make_cubic: Handle<UiNode>,
96    zoom_to_fit: Handle<UiNode>,
97}
98
99#[derive(Clone)]
100struct DragEntry {
101    key: Uuid,
102    initial_position: Vector2<f32>,
103}
104
105#[derive(Clone)]
106enum OperationContext {
107    DragKeys {
108        // In local coordinates.
109        initial_mouse_pos: Vector2<f32>,
110        entries: Vec<DragEntry>,
111    },
112    MoveView {
113        initial_mouse_pos: Vector2<f32>,
114        initial_view_pos: Vector2<f32>,
115    },
116    DragTangent {
117        key: usize,
118        left: bool,
119    },
120    BoxSelection {
121        // In local coordinates.
122        initial_mouse_pos: Vector2<f32>,
123        min: Cell<Vector2<f32>>,
124        max: Cell<Vector2<f32>>,
125    },
126}
127
128#[derive(Clone)]
129enum Selection {
130    Keys { keys: FxHashSet<Uuid> },
131    // It is ok to use index directly in case of tangents since
132    // we won't change position of keys so index will be valid.
133    LeftTangent { key: usize },
134    RightTangent { key: usize },
135}
136
137#[derive(Copy, Clone)]
138enum PickResult {
139    Key(usize),
140    LeftTangent(usize),
141    RightTangent(usize),
142}
143
144impl Selection {
145    fn single_key(key: Uuid) -> Self {
146        let mut keys = FxHashSet::default();
147        keys.insert(key);
148        Self::Keys { keys }
149    }
150}
151
152impl Control for CurveEditor {
153    fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
154        if type_id == TypeId::of::<Self>() {
155            Some(self)
156        } else {
157            None
158        }
159    }
160
161    fn draw(&self, ctx: &mut DrawingContext) {
162        self.update_matrices();
163        self.draw_background(ctx);
164        self.draw_grid(ctx);
165        self.draw_curve(ctx);
166        self.draw_keys(ctx);
167        self.draw_operation(ctx);
168    }
169
170    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
171        self.widget.handle_routed_message(ui, message);
172
173        if message.destination() == self.handle {
174            if let Some(msg) = message.data::<WidgetMessage>() {
175                match msg {
176                    WidgetMessage::KeyUp(KeyCode::Delete) => {
177                        self.remove_selection(ui);
178                    }
179                    WidgetMessage::MouseMove { pos, state } => {
180                        let local_mouse_pos = self.point_to_local_space(*pos);
181                        if let Some(operation_context) = self.operation_context.as_ref() {
182                            match operation_context {
183                                OperationContext::DragKeys {
184                                    entries,
185                                    initial_mouse_pos,
186                                } => {
187                                    let local_delta = local_mouse_pos - initial_mouse_pos;
188                                    for entry in entries {
189                                        let key = self.key_container.key_mut(entry.key).unwrap();
190                                        key.position = entry.initial_position + local_delta;
191                                    }
192                                    self.sort_keys();
193                                }
194                                OperationContext::MoveView {
195                                    initial_mouse_pos,
196                                    initial_view_pos,
197                                } => {
198                                    let delta = (pos - initial_mouse_pos).scale(1.0 / self.zoom);
199                                    self.view_position = initial_view_pos + delta;
200                                }
201                                OperationContext::DragTangent { key, left } => {
202                                    let key_pos =
203                                        self.key_container.key_index_ref(*key).unwrap().position;
204                                    let screen_key_pos = self.point_to_screen_space(key_pos);
205                                    let key = self.key_container.key_index_mut(*key).unwrap();
206                                    if let CurveKeyKind::Cubic {
207                                        left_tangent,
208                                        right_tangent,
209                                    } = &mut key.kind
210                                    {
211                                        let mut local_delta = pos - screen_key_pos;
212                                        if *left {
213                                            local_delta.x = local_delta.x.min(f32::EPSILON);
214                                        } else {
215                                            local_delta.x = local_delta.x.max(f32::EPSILON);
216                                        }
217                                        let tangent =
218                                            (local_delta.y / local_delta.x).clamp(-10e6, 10e6);
219
220                                        if *left {
221                                            *left_tangent = tangent;
222                                        } else {
223                                            *right_tangent = tangent;
224                                        }
225                                    } else {
226                                        unreachable!(
227                                            "attempt to edit tangents of non-cubic curve key!"
228                                        )
229                                    }
230                                }
231                                OperationContext::BoxSelection {
232                                    initial_mouse_pos,
233                                    min,
234                                    max,
235                                    ..
236                                } => {
237                                    min.set(local_mouse_pos.inf(initial_mouse_pos));
238                                    max.set(local_mouse_pos.sup(initial_mouse_pos));
239                                }
240                            }
241                        } else if state.left == ButtonState::Pressed {
242                            if let Some(selection) = self.selection.as_ref() {
243                                match selection {
244                                    Selection::Keys { keys } => {
245                                        self.operation_context = Some(OperationContext::DragKeys {
246                                            entries: keys
247                                                .iter()
248                                                .map(|k| DragEntry {
249                                                    key: *k,
250                                                    initial_position: self
251                                                        .key_container
252                                                        .key_ref(*k)
253                                                        .unwrap()
254                                                        .position,
255                                                })
256                                                .collect::<Vec<_>>(),
257                                            initial_mouse_pos: local_mouse_pos,
258                                        });
259                                    }
260                                    Selection::LeftTangent { key } => {
261                                        self.operation_context =
262                                            Some(OperationContext::DragTangent {
263                                                key: *key,
264                                                left: true,
265                                            })
266                                    }
267                                    Selection::RightTangent { key } => {
268                                        self.operation_context =
269                                            Some(OperationContext::DragTangent {
270                                                key: *key,
271                                                left: false,
272                                            })
273                                    }
274                                }
275
276                                if self.operation_context.is_some() {
277                                    ui.capture_mouse(self.handle);
278                                }
279                            } else {
280                                self.operation_context = Some(OperationContext::BoxSelection {
281                                    initial_mouse_pos: local_mouse_pos,
282                                    min: Default::default(),
283                                    max: Default::default(),
284                                })
285                            }
286                        }
287                    }
288
289                    WidgetMessage::MouseUp { .. } => {
290                        if let Some(context) = self.operation_context.take() {
291                            ui.release_mouse_capture();
292
293                            // Send modified curve back to user.
294                            match context {
295                                OperationContext::DragKeys { .. }
296                                | OperationContext::DragTangent { .. } => {
297                                    // Ensure that the order of keys is correct.
298                                    self.sort_keys();
299
300                                    self.send_curve(ui);
301                                }
302                                OperationContext::BoxSelection { min, max, .. } => {
303                                    let min = min.get();
304                                    let max = max.get();
305
306                                    let rect =
307                                        Rect::new(min.x, min.y, max.x - min.x, max.y - min.y);
308
309                                    let mut selection = FxHashSet::default();
310                                    for key in self.key_container.keys() {
311                                        if rect.contains(key.position) {
312                                            selection.insert(key.id);
313                                        }
314                                    }
315
316                                    if !selection.is_empty() {
317                                        self.set_selection(
318                                            Some(Selection::Keys { keys: selection }),
319                                            ui,
320                                        );
321                                    }
322                                }
323                                _ => {}
324                            }
325                        }
326                    }
327                    WidgetMessage::MouseDown { pos, button } => match button {
328                        MouseButton::Left => {
329                            let pick_result = self.pick(*pos);
330
331                            if let Some(picked) = pick_result {
332                                match picked {
333                                    PickResult::Key(picked_key) => {
334                                        let picked_key_id = self
335                                            .key_container
336                                            .key_index_ref(picked_key)
337                                            .unwrap()
338                                            .id;
339                                        if let Some(selection) = self.selection.as_mut() {
340                                            match selection {
341                                                Selection::Keys { keys } => {
342                                                    if ui.keyboard_modifiers().control {
343                                                        keys.insert(picked_key_id);
344                                                    }
345                                                    if !keys.contains(&picked_key_id) {
346                                                        self.set_selection(
347                                                            Some(Selection::single_key(
348                                                                picked_key_id,
349                                                            )),
350                                                            ui,
351                                                        );
352                                                    }
353                                                }
354                                                Selection::LeftTangent { .. }
355                                                | Selection::RightTangent { .. } => self
356                                                    .set_selection(
357                                                        Some(Selection::single_key(picked_key_id)),
358                                                        ui,
359                                                    ),
360                                            }
361                                        } else {
362                                            self.set_selection(
363                                                Some(Selection::single_key(picked_key_id)),
364                                                ui,
365                                            );
366                                        }
367                                    }
368                                    PickResult::LeftTangent(picked_key) => {
369                                        self.set_selection(
370                                            Some(Selection::LeftTangent { key: picked_key }),
371                                            ui,
372                                        );
373                                    }
374                                    PickResult::RightTangent(picked_key) => {
375                                        self.set_selection(
376                                            Some(Selection::RightTangent { key: picked_key }),
377                                            ui,
378                                        );
379                                    }
380                                }
381                            } else {
382                                self.set_selection(None, ui);
383                            }
384                        }
385                        MouseButton::Middle => {
386                            ui.capture_mouse(self.handle);
387                            self.operation_context = Some(OperationContext::MoveView {
388                                initial_mouse_pos: *pos,
389                                initial_view_pos: self.view_position,
390                            });
391                        }
392                        _ => (),
393                    },
394                    WidgetMessage::MouseWheel { amount, .. } => {
395                        let k = if *amount < 0.0 { 0.9 } else { 1.1 };
396                        ui.send_message(CurveEditorMessage::zoom(
397                            self.handle,
398                            MessageDirection::ToWidget,
399                            self.zoom * k,
400                        ));
401                    }
402                    _ => {}
403                }
404            } else if let Some(msg) = message.data::<CurveEditorMessage>() {
405                if message.destination() == self.handle
406                    && message.direction() == MessageDirection::ToWidget
407                {
408                    match msg {
409                        CurveEditorMessage::Sync(curve) => {
410                            self.key_container = KeyContainer::from(curve);
411                        }
412                        CurveEditorMessage::ViewPosition(view_position) => {
413                            self.view_position = *view_position;
414                        }
415                        CurveEditorMessage::Zoom(zoom) => {
416                            self.zoom = *zoom;
417                        }
418                        CurveEditorMessage::RemoveSelection => {
419                            self.remove_selection(ui);
420                        }
421                        CurveEditorMessage::ChangeSelectedKeysKind(kind) => {
422                            self.change_selected_keys_kind(kind.clone(), ui);
423                        }
424                        CurveEditorMessage::AddKey(screen_pos) => {
425                            let local_pos = self.point_to_local_space(*screen_pos);
426                            self.key_container.add(CurveKeyView {
427                                position: local_pos,
428                                kind: CurveKeyKind::Linear,
429                                id: Uuid::new_v4(),
430                            });
431                            self.set_selection(None, ui);
432                            self.sort_keys();
433                        }
434                        CurveEditorMessage::ZoomToFit => {
435                            let mut max_y = -f32::MAX;
436                            let mut min_y = f32::MAX;
437                            let mut max_x = -f32::MAX;
438                            let mut min_x = f32::MAX;
439
440                            let mut push = |x: f32, y: f32| {
441                                if x > max_x {
442                                    max_x = x;
443                                }
444                                if x < min_x {
445                                    min_x = x;
446                                }
447                                if y > max_y {
448                                    max_y = y;
449                                }
450                                if y < min_y {
451                                    min_y = y;
452                                }
453                            };
454
455                            for keys in self.key_container.keys().windows(2) {
456                                let left = &keys[0];
457                                let right = &keys[1];
458                                match (&left.kind, &right.kind) {
459                                    // Cubic-to-constant and cubic-to-linear is depicted as Hermite spline with right tangent == 0.0.
460                                    (
461                                        CurveKeyKind::Cubic {
462                                            right_tangent: left_tangent,
463                                            ..
464                                        },
465                                        CurveKeyKind::Constant,
466                                    )
467                                    | (
468                                        CurveKeyKind::Cubic {
469                                            right_tangent: left_tangent,
470                                            ..
471                                        },
472                                        CurveKeyKind::Linear,
473                                    ) => {
474                                        let (y0, y1) = inf_sup_cubicf(
475                                            left.position.y,
476                                            right.position.y,
477                                            *left_tangent,
478                                            0.0,
479                                        );
480                                        push(left.position.x, y0);
481                                        push(right.position.x, y1);
482                                    }
483
484                                    // Cubic-to-cubic is depicted as Hermite spline.
485                                    (
486                                        CurveKeyKind::Cubic {
487                                            right_tangent: left_tangent,
488                                            ..
489                                        },
490                                        CurveKeyKind::Cubic {
491                                            left_tangent: right_tangent,
492                                            ..
493                                        },
494                                    ) => {
495                                        let (y0, y1) = inf_sup_cubicf(
496                                            left.position.y,
497                                            right.position.y,
498                                            *left_tangent,
499                                            *right_tangent,
500                                        );
501                                        push(left.position.x, y0);
502                                        push(right.position.x, y1);
503                                    }
504                                    _ => {
505                                        push(left.position.x, left.position.y);
506                                        push(right.position.x, right.position.y);
507                                    }
508                                }
509                            }
510
511                            let min = Vector2::new(min_x, min_y);
512                            let max = Vector2::new(max_x, max_y);
513                            let center = (min + max).scale(0.5);
514
515                            let mut offset = self.actual_size().scale(0.5 * self.zoom);
516                            offset.y *= -1.0;
517                            self.view_position = center + offset;
518                        }
519                    }
520                }
521            }
522        }
523    }
524
525    fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
526        if let Some(MenuItemMessage::Click) = message.data::<MenuItemMessage>() {
527            if message.destination() == self.context_menu.remove {
528                ui.send_message(CurveEditorMessage::remove_selection(
529                    self.handle,
530                    MessageDirection::ToWidget,
531                ));
532            } else if message.destination() == self.context_menu.make_constant {
533                ui.send_message(CurveEditorMessage::change_selected_keys_kind(
534                    self.handle,
535                    MessageDirection::ToWidget,
536                    CurveKeyKind::Constant,
537                ));
538            } else if message.destination() == self.context_menu.make_linear {
539                ui.send_message(CurveEditorMessage::change_selected_keys_kind(
540                    self.handle,
541                    MessageDirection::ToWidget,
542                    CurveKeyKind::Linear,
543                ));
544            } else if message.destination() == self.context_menu.make_cubic {
545                ui.send_message(CurveEditorMessage::change_selected_keys_kind(
546                    self.handle,
547                    MessageDirection::ToWidget,
548                    CurveKeyKind::Cubic {
549                        left_tangent: 0.0,
550                        right_tangent: 0.0,
551                    },
552                ));
553            } else if message.destination() == self.context_menu.add_key {
554                let screen_pos = ui.node(self.context_menu.widget).screen_position();
555                ui.send_message(CurveEditorMessage::add_key(
556                    self.handle,
557                    MessageDirection::ToWidget,
558                    screen_pos,
559                ));
560            } else if message.destination() == self.context_menu.zoom_to_fit {
561                ui.send_message(CurveEditorMessage::zoom_to_fit(
562                    self.handle,
563                    MessageDirection::ToWidget,
564                ));
565            }
566        }
567    }
568}
569
570fn draw_cubic(
571    left_pos: Vector2<f32>,
572    left_tangent: f32,
573    right_pos: Vector2<f32>,
574    right_tangent: f32,
575    steps: usize,
576    ctx: &mut DrawingContext,
577) {
578    let mut prev = left_pos;
579    for i in 0..steps {
580        let t = i as f32 / (steps - 1) as f32;
581        let middle_x = lerpf(left_pos.x, right_pos.x, t);
582        let middle_y = cubicf(left_pos.y, right_pos.y, t, left_tangent, right_tangent);
583        let pt = Vector2::new(middle_x, middle_y);
584        ctx.push_line(prev, pt, 1.0);
585        prev = pt;
586    }
587}
588
589fn round_to_step(x: f32, step: f32) -> f32 {
590    x - x % step
591}
592
593impl CurveEditor {
594    fn update_matrices(&self) {
595        let vp = Vector2::new(self.view_position.x, -self.view_position.y);
596        self.view_matrix.set(
597            Matrix3::new_nonuniform_scaling_wrt_point(
598                &Vector2::new(self.zoom, self.zoom),
599                &Point2::from(self.actual_size().scale(0.5)),
600            ) * Matrix3::new_translation(&vp),
601        );
602
603        let screen_bounds = self.screen_bounds();
604        self.screen_matrix.set(
605            Matrix3::new_translation(&screen_bounds.position)
606                // Flip Y because in math origin is in lower left corner.
607                * Matrix3::new_translation(&Vector2::new(0.0, screen_bounds.h()))
608                * Matrix3::new_nonuniform_scaling(&Vector2::new(1.0, -1.0))
609                * self.view_matrix.get(),
610        );
611
612        self.inv_screen_matrix
613            .set(self.view_matrix.get().try_inverse().unwrap_or_default());
614    }
615
616    /// Transforms a point to view space.
617    pub fn point_to_view_space(&self, point: Vector2<f32>) -> Vector2<f32> {
618        self.view_matrix
619            .get()
620            .transform_point(&Point2::from(point))
621            .coords
622    }
623
624    /// Transforms a point to screen space.
625    pub fn point_to_screen_space(&self, point: Vector2<f32>) -> Vector2<f32> {
626        self.screen_matrix
627            .get()
628            .transform_point(&Point2::from(point))
629            .coords
630    }
631
632    /// Transforms a vector to screen space.
633    pub fn vector_to_screen_space(&self, vector: Vector2<f32>) -> Vector2<f32> {
634        (self.screen_matrix.get() * Vector3::new(vector.x, vector.y, 0.0)).xy()
635    }
636
637    /// Transforms a point to local space.
638    pub fn point_to_local_space(&self, point: Vector2<f32>) -> Vector2<f32> {
639        let mut p = point - self.screen_position();
640        p.y = self.actual_size().y - p.y;
641        self.view_matrix
642            .get()
643            .try_inverse()
644            .unwrap_or_default()
645            .transform_point(&Point2::from(p))
646            .coords
647    }
648
649    fn sort_keys(&mut self) {
650        self.key_container.sort_keys();
651    }
652
653    fn set_selection(&mut self, selection: Option<Selection>, ui: &UserInterface) {
654        self.selection = selection;
655
656        ui.send_message(WidgetMessage::enabled(
657            self.context_menu.remove,
658            MessageDirection::ToWidget,
659            self.selection.is_some(),
660        ));
661
662        ui.send_message(WidgetMessage::enabled(
663            self.context_menu.key,
664            MessageDirection::ToWidget,
665            self.selection.is_some(),
666        ));
667    }
668
669    fn remove_selection(&mut self, ui: &mut UserInterface) {
670        if let Some(Selection::Keys { keys }) = self.selection.as_ref() {
671            for &id in keys {
672                self.key_container.remove(id);
673            }
674
675            self.set_selection(None, ui);
676
677            // Send modified curve back to user.
678            self.send_curve(ui);
679        }
680    }
681
682    fn change_selected_keys_kind(&mut self, kind: CurveKeyKind, ui: &mut UserInterface) {
683        if let Some(Selection::Keys { keys }) = self.selection.as_ref() {
684            for key in keys {
685                self.key_container.key_mut(*key).unwrap().kind = kind.clone();
686            }
687
688            self.send_curve(ui);
689        }
690    }
691
692    /// `pos` must be in screen space.
693    fn pick(&self, pos: Vector2<f32>) -> Option<PickResult> {
694        // Linear search is fine here, having a curve with thousands of
695        // points is insane anyway.
696        for (i, key) in self.key_container.keys().iter().enumerate() {
697            let screen_pos = self.point_to_screen_space(key.position);
698            let bounds = Rect::new(
699                screen_pos.x - self.key_size * 0.5,
700                screen_pos.y - self.key_size * 0.5,
701                self.key_size,
702                self.key_size,
703            );
704            if bounds.contains(pos) {
705                return Some(PickResult::Key(i));
706            }
707
708            // Check tangents.
709            if let CurveKeyKind::Cubic {
710                left_tangent,
711                right_tangent,
712            } = key.kind
713            {
714                let left_handle_pos = self.tangent_screen_position(
715                    wrap_angle(left_tangent.atan()) + std::f32::consts::PI,
716                    key.position,
717                );
718
719                if (left_handle_pos - pos).norm() <= self.key_size * 0.5 {
720                    return Some(PickResult::LeftTangent(i));
721                }
722
723                let right_handle_pos =
724                    self.tangent_screen_position(wrap_angle(right_tangent.atan()), key.position);
725
726                if (right_handle_pos - pos).norm() <= self.key_size * 0.5 {
727                    return Some(PickResult::RightTangent(i));
728                }
729            }
730        }
731        None
732    }
733
734    fn tangent_screen_position(&self, angle: f32, key_position: Vector2<f32>) -> Vector2<f32> {
735        self.point_to_screen_space(key_position)
736            + Vector2::new(angle.cos(), angle.sin()).scale(self.handle_radius)
737    }
738
739    fn send_curve(&self, ui: &UserInterface) {
740        ui.send_message(CurveEditorMessage::sync(
741            self.handle,
742            MessageDirection::FromWidget,
743            self.key_container.curve(),
744        ));
745    }
746
747    fn draw_background(&self, ctx: &mut DrawingContext) {
748        let screen_bounds = self.screen_bounds();
749        // Draw background.
750        ctx.push_rect_filled(&screen_bounds, None);
751        ctx.commit(screen_bounds, self.background(), CommandTexture::None, None);
752    }
753
754    fn draw_grid(&self, ctx: &mut DrawingContext) {
755        let screen_bounds = self.screen_bounds();
756
757        let step_size = 50.0 / self.zoom.clamp(0.001, 1000.0);
758
759        let mut local_left_bottom = self.point_to_local_space(screen_bounds.left_top_corner());
760        let local_left_bottom_n = local_left_bottom;
761        local_left_bottom.x = round_to_step(local_left_bottom.x, step_size);
762        local_left_bottom.y = round_to_step(local_left_bottom.y, step_size);
763
764        let mut local_right_top = self.point_to_local_space(screen_bounds.right_bottom_corner());
765        local_right_top.x = round_to_step(local_right_top.x, step_size);
766        local_right_top.y = round_to_step(local_right_top.y, step_size);
767
768        let w = (local_right_top.x - local_left_bottom.x).abs();
769        let h = (local_right_top.y - local_left_bottom.y).abs();
770
771        let nw = ((w / step_size).ceil()) as usize;
772        let nh = ((h / step_size).ceil()) as usize;
773
774        for ny in 0..=nh {
775            let k = ny as f32 / (nh) as f32;
776            let y = local_left_bottom.y - k * h;
777            ctx.push_line(
778                self.point_to_screen_space(Vector2::new(local_left_bottom.x - step_size, y)),
779                self.point_to_screen_space(Vector2::new(local_right_top.x + step_size, y)),
780                1.0,
781            );
782        }
783
784        for nx in 0..=nw {
785            let k = nx as f32 / (nw) as f32;
786            let x = local_left_bottom.x + k * w;
787            ctx.push_line(
788                self.point_to_screen_space(Vector2::new(x, local_left_bottom.y + step_size)),
789                self.point_to_screen_space(Vector2::new(x, local_right_top.y - step_size)),
790                1.0,
791            );
792        }
793
794        // Draw main axes.
795        let vb = self.point_to_screen_space(Vector2::new(0.0, -10e6));
796        let ve = self.point_to_screen_space(Vector2::new(0.0, 10e6));
797        ctx.push_line(vb, ve, 2.0);
798
799        let hb = self.point_to_screen_space(Vector2::new(-10e6, 0.0));
800        let he = self.point_to_screen_space(Vector2::new(10e6, 0.0));
801        ctx.push_line(hb, he, 2.0);
802
803        ctx.commit(
804            screen_bounds,
805            self.grid_brush.clone(),
806            CommandTexture::None,
807            None,
808        );
809
810        // Draw values.
811        let mut text = self.text.borrow_mut();
812        for ny in 0..=nh {
813            let k = ny as f32 / (nh) as f32;
814            let y = local_left_bottom.y - k * h;
815            text.set_text(format!("{:.1}", y)).build();
816            ctx.draw_text(
817                screen_bounds,
818                self.point_to_screen_space(Vector2::new(local_left_bottom_n.x, y)),
819                &text,
820            );
821        }
822
823        for nx in 0..=nw {
824            let k = nx as f32 / (nw) as f32;
825            let x = local_left_bottom.x + k * w;
826            text.set_text(format!("{:.1}", x)).build();
827            ctx.draw_text(
828                screen_bounds,
829                self.point_to_screen_space(Vector2::new(x, 0.0)),
830                &text,
831            );
832        }
833    }
834
835    fn draw_curve(&self, ctx: &mut DrawingContext) {
836        let screen_bounds = self.screen_bounds();
837        let draw_keys = self.key_container.keys();
838
839        if let Some(first) = draw_keys.first() {
840            let screen_pos = self.point_to_screen_space(first.position);
841            ctx.push_line(Vector2::new(0.0, screen_pos.y), screen_pos, 1.0);
842        }
843        if let Some(last) = draw_keys.last() {
844            let screen_pos = self.point_to_screen_space(last.position);
845            ctx.push_line(
846                screen_pos,
847                Vector2::new(screen_bounds.x() + screen_bounds.w(), screen_pos.y),
848                1.0,
849            );
850        }
851
852        for pair in draw_keys.windows(2) {
853            let left = &pair[0];
854            let right = &pair[1];
855
856            let left_pos = self.point_to_screen_space(left.position);
857            let right_pos = self.point_to_screen_space(right.position);
858
859            let steps = ((right_pos.x - left_pos.x).abs() / 2.0) as usize;
860
861            match (&left.kind, &right.kind) {
862                // Constant-to-any is depicted as two straight lines.
863                (CurveKeyKind::Constant, CurveKeyKind::Constant)
864                | (CurveKeyKind::Constant, CurveKeyKind::Linear)
865                | (CurveKeyKind::Constant, CurveKeyKind::Cubic { .. }) => {
866                    ctx.push_line(left_pos, Vector2::new(right_pos.x, left_pos.y), 1.0);
867                    ctx.push_line(Vector2::new(right_pos.x, left_pos.y), right_pos, 1.0);
868                }
869
870                // Linear-to-any is depicted as a straight line.
871                (CurveKeyKind::Linear, CurveKeyKind::Constant)
872                | (CurveKeyKind::Linear, CurveKeyKind::Linear)
873                | (CurveKeyKind::Linear, CurveKeyKind::Cubic { .. }) => {
874                    ctx.push_line(left_pos, right_pos, 1.0)
875                }
876
877                // Cubic-to-constant and cubic-to-linear is depicted as Hermite spline with right tangent == 0.0.
878                (
879                    CurveKeyKind::Cubic {
880                        right_tangent: left_tangent,
881                        ..
882                    },
883                    CurveKeyKind::Constant,
884                )
885                | (
886                    CurveKeyKind::Cubic {
887                        right_tangent: left_tangent,
888                        ..
889                    },
890                    CurveKeyKind::Linear,
891                ) => draw_cubic(left_pos, *left_tangent, right_pos, 0.0, steps, ctx),
892
893                // Cubic-to-cubic is depicted as Hermite spline.
894                (
895                    CurveKeyKind::Cubic {
896                        right_tangent: left_tangent,
897                        ..
898                    },
899                    CurveKeyKind::Cubic {
900                        left_tangent: right_tangent,
901                        ..
902                    },
903                ) => draw_cubic(
904                    left_pos,
905                    *left_tangent,
906                    right_pos,
907                    *right_tangent,
908                    steps,
909                    ctx,
910                ),
911            }
912        }
913        ctx.commit(screen_bounds, self.foreground(), CommandTexture::None, None);
914    }
915
916    fn draw_keys(&self, ctx: &mut DrawingContext) {
917        let screen_bounds = self.screen_bounds();
918        let keys_to_draw = self.key_container.keys();
919
920        for (i, key) in keys_to_draw.iter().enumerate() {
921            let origin = self.point_to_screen_space(key.position);
922            let size = Vector2::new(self.key_size, self.key_size);
923            let half_size = size.scale(0.5);
924
925            ctx.push_rect_filled(
926                &Rect::new(
927                    origin.x - half_size.x,
928                    origin.y - half_size.x,
929                    size.x,
930                    size.y,
931                ),
932                None,
933            );
934
935            let mut selected = false;
936            if let Some(selection) = self.selection.as_ref() {
937                match selection {
938                    Selection::Keys { keys } => {
939                        selected = keys.contains(&key.id);
940                    }
941                    Selection::LeftTangent { key } | Selection::RightTangent { key } => {
942                        selected = i == *key;
943                    }
944                }
945            }
946
947            // Show tangents for Cubic keys.
948            if selected {
949                let (show_left, show_right) = match keys_to_draw.get(i.wrapping_sub(1)) {
950                    Some(left) => match (&left.kind, &key.kind) {
951                        (CurveKeyKind::Cubic { .. }, CurveKeyKind::Cubic { .. }) => (true, true),
952                        (CurveKeyKind::Linear, CurveKeyKind::Cubic { .. })
953                        | (CurveKeyKind::Constant, CurveKeyKind::Cubic { .. }) => (false, true),
954                        _ => (false, false),
955                    },
956                    None => match key.kind {
957                        CurveKeyKind::Cubic { .. } => (false, true),
958                        _ => (false, false),
959                    },
960                };
961
962                if let CurveKeyKind::Cubic {
963                    left_tangent,
964                    right_tangent,
965                } = key.kind
966                {
967                    if show_left {
968                        let left_handle_pos = self.tangent_screen_position(
969                            wrap_angle(left_tangent.atan()) + std::f32::consts::PI,
970                            key.position,
971                        );
972                        ctx.push_line(origin, left_handle_pos, 1.0);
973                        ctx.push_circle(
974                            left_handle_pos,
975                            self.key_size * 0.5,
976                            6,
977                            Default::default(),
978                        );
979                    }
980
981                    if show_right {
982                        let right_handle_pos = self.tangent_screen_position(
983                            wrap_angle(right_tangent.atan()),
984                            key.position,
985                        );
986                        ctx.push_line(origin, right_handle_pos, 1.0);
987                        ctx.push_circle(
988                            right_handle_pos,
989                            self.key_size * 0.5,
990                            6,
991                            Default::default(),
992                        );
993                    }
994                }
995            }
996
997            ctx.commit(
998                screen_bounds,
999                if selected {
1000                    self.selected_key_brush.clone()
1001                } else {
1002                    self.key_brush.clone()
1003                },
1004                CommandTexture::None,
1005                None,
1006            );
1007        }
1008    }
1009
1010    fn draw_operation(&self, ctx: &mut DrawingContext) {
1011        if let Some(OperationContext::BoxSelection { min, max, .. }) =
1012            self.operation_context.as_ref()
1013        {
1014            let min = self.point_to_screen_space(min.get());
1015            let max = self.point_to_screen_space(max.get());
1016            let rect = Rect::new(min.x, min.y, max.x - min.x, max.y - min.y);
1017
1018            ctx.push_rect(&rect, 1.0);
1019            ctx.commit(
1020                self.screen_bounds(),
1021                Brush::Solid(Color::WHITE),
1022                CommandTexture::None,
1023                None,
1024            );
1025        }
1026    }
1027}
1028
1029pub struct CurveEditorBuilder {
1030    widget_builder: WidgetBuilder,
1031    curve: Curve,
1032    view_position: Vector2<f32>,
1033    zoom: f32,
1034}
1035
1036impl CurveEditorBuilder {
1037    pub fn new(widget_builder: WidgetBuilder) -> Self {
1038        Self {
1039            widget_builder,
1040            curve: Default::default(),
1041            view_position: Default::default(),
1042            zoom: 1.0,
1043        }
1044    }
1045
1046    pub fn with_curve(mut self, curve: Curve) -> Self {
1047        self.curve = curve;
1048        self
1049    }
1050
1051    pub fn with_zoom(mut self, zoom: f32) -> Self {
1052        self.zoom = zoom;
1053        self
1054    }
1055
1056    pub fn with_view_position(mut self, view_position: Vector2<f32>) -> Self {
1057        self.view_position = view_position;
1058        self
1059    }
1060
1061    pub fn build(mut self, ctx: &mut BuildContext) -> Handle<UiNode> {
1062        let keys = KeyContainer::from(&self.curve);
1063
1064        let add_key;
1065        let remove;
1066        let make_constant;
1067        let make_linear;
1068        let make_cubic;
1069        let key;
1070        let zoom_to_fit;
1071        let context_menu = PopupBuilder::new(WidgetBuilder::new())
1072            .with_content(
1073                StackPanelBuilder::new(
1074                    WidgetBuilder::new()
1075                        .with_child({
1076                            add_key = MenuItemBuilder::new(WidgetBuilder::new())
1077                                .with_content(MenuItemContent::text("Add Key"))
1078                                .build(ctx);
1079                            add_key
1080                        })
1081                        .with_child({
1082                            remove = MenuItemBuilder::new(WidgetBuilder::new().with_enabled(false))
1083                                .with_content(MenuItemContent::text("Remove"))
1084                                .build(ctx);
1085                            remove
1086                        })
1087                        .with_child({
1088                            key = MenuItemBuilder::new(WidgetBuilder::new().with_enabled(false))
1089                                .with_content(MenuItemContent::text("Key..."))
1090                                .with_items(vec![
1091                                    {
1092                                        make_constant = MenuItemBuilder::new(WidgetBuilder::new())
1093                                            .with_content(MenuItemContent::text("Constant"))
1094                                            .build(ctx);
1095                                        make_constant
1096                                    },
1097                                    {
1098                                        make_linear = MenuItemBuilder::new(WidgetBuilder::new())
1099                                            .with_content(MenuItemContent::text("Linear"))
1100                                            .build(ctx);
1101                                        make_linear
1102                                    },
1103                                    {
1104                                        make_cubic = MenuItemBuilder::new(WidgetBuilder::new())
1105                                            .with_content(MenuItemContent::text("Cubic"))
1106                                            .build(ctx);
1107                                        make_cubic
1108                                    },
1109                                ])
1110                                .build(ctx);
1111                            key
1112                        })
1113                        .with_child({
1114                            zoom_to_fit = MenuItemBuilder::new(WidgetBuilder::new())
1115                                .with_content(MenuItemContent::text("Zoom To Fit"))
1116                                .build(ctx);
1117                            zoom_to_fit
1118                        }),
1119                )
1120                .build(ctx),
1121            )
1122            .build(ctx);
1123
1124        if self.widget_builder.foreground.is_none() {
1125            self.widget_builder.foreground = Some(Brush::Solid(Color::opaque(130, 130, 130)))
1126        }
1127
1128        let editor = CurveEditor {
1129            widget: self
1130                .widget_builder
1131                .with_context_menu(context_menu)
1132                .with_preview_messages(true)
1133                .build(),
1134            key_container: keys,
1135            zoom: 1.0,
1136            view_position: Default::default(),
1137            view_matrix: Default::default(),
1138            screen_matrix: Default::default(),
1139            inv_screen_matrix: Default::default(),
1140            key_brush: Brush::Solid(Color::opaque(140, 140, 140)),
1141            selected_key_brush: Brush::Solid(Color::opaque(220, 220, 220)),
1142            key_size: 8.0,
1143            handle_radius: 36.0,
1144            operation_context: None,
1145            grid_brush: Brush::Solid(Color::from_rgba(110, 110, 110, 50)),
1146            selection: None,
1147            text: RefCell::new(
1148                FormattedTextBuilder::new()
1149                    .with_brush(Brush::Solid(Color::opaque(100, 100, 100)))
1150                    .with_font(crate::DEFAULT_FONT.clone())
1151                    .build(),
1152            ),
1153            context_menu: ContextMenu {
1154                widget: context_menu,
1155                add_key,
1156                remove,
1157                make_constant,
1158                make_linear,
1159                make_cubic,
1160                key,
1161                zoom_to_fit,
1162            },
1163        };
1164
1165        ctx.add_node(UiNode::new(editor))
1166    }
1167}