jellyflow_runtime/runtime/viewport/
animation.rs1use serde::{Deserialize, Serialize};
2
3use jellyflow_core::core::CanvasPoint;
4
5use super::transform::ViewportTransform;
6
7#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum ViewportAnimationEasing {
11 #[default]
12 CubicInOut,
13 Linear,
14}
15
16impl ViewportAnimationEasing {
17 fn sample(self, progress: f32) -> f32 {
18 match self {
19 Self::CubicInOut => cubic_in_out(progress),
20 Self::Linear => progress.clamp(0.0, 1.0),
21 }
22 }
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
27pub struct ViewportAnimationOptions {
28 pub duration_seconds: f32,
30 pub easing: ViewportAnimationEasing,
32}
33
34impl ViewportAnimationOptions {
35 pub fn new(duration_seconds: f32) -> Self {
36 Self {
37 duration_seconds,
38 easing: ViewportAnimationEasing::default(),
39 }
40 }
41
42 pub fn with_easing(mut self, easing: ViewportAnimationEasing) -> Self {
43 self.easing = easing;
44 self
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
50pub struct ViewportAnimationRequest {
51 pub from: ViewportTransform,
52 pub to: ViewportTransform,
53 pub options: ViewportAnimationOptions,
54}
55
56impl ViewportAnimationRequest {
57 pub fn new(
58 from: ViewportTransform,
59 to: ViewportTransform,
60 options: ViewportAnimationOptions,
61 ) -> Self {
62 Self { from, to, options }
63 }
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
68pub struct ViewportAnimationPlan {
69 pub from: ViewportTransform,
70 pub to: ViewportTransform,
71 pub duration_seconds: f32,
72 pub easing: ViewportAnimationEasing,
73}
74
75impl ViewportAnimationPlan {
76 pub fn frame_at(self, elapsed_seconds: f32) -> Option<ViewportAnimationFrame> {
78 if !elapsed_seconds.is_finite() {
79 return None;
80 }
81
82 let elapsed_seconds = elapsed_seconds.max(0.0);
83 let progress = if self.duration_seconds <= 0.0 {
84 1.0
85 } else {
86 (elapsed_seconds / self.duration_seconds).clamp(0.0, 1.0)
87 };
88 let eased_progress = self.easing.sample(progress);
89 let transform = interpolate_transform(self.from, self.to, eased_progress)?;
90
91 Some(ViewportAnimationFrame {
92 elapsed_seconds,
93 progress,
94 eased_progress,
95 transform,
96 done: progress >= 1.0,
97 })
98 }
99
100 pub fn is_immediate(self) -> bool {
101 self.duration_seconds <= 0.0
102 }
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
107pub struct ViewportAnimationFrame {
108 pub elapsed_seconds: f32,
109 pub progress: f32,
110 pub eased_progress: f32,
111 pub transform: ViewportTransform,
112 pub done: bool,
113}
114
115pub fn plan_viewport_animation(
116 from: ViewportTransform,
117 to: ViewportTransform,
118 duration_seconds: f32,
119) -> Option<ViewportAnimationPlan> {
120 plan_viewport_animation_with_options(ViewportAnimationRequest::new(
121 from,
122 to,
123 ViewportAnimationOptions::new(duration_seconds),
124 ))
125}
126
127pub fn plan_viewport_animation_with_options(
128 request: ViewportAnimationRequest,
129) -> Option<ViewportAnimationPlan> {
130 if !request.from.is_valid()
131 || !request.to.is_valid()
132 || !request.options.duration_seconds.is_finite()
133 {
134 return None;
135 }
136
137 Some(ViewportAnimationPlan {
138 from: request.from,
139 to: request.to,
140 duration_seconds: request.options.duration_seconds.max(0.0),
141 easing: request.options.easing,
142 })
143}
144
145fn interpolate_transform(
146 from: ViewportTransform,
147 to: ViewportTransform,
148 progress: f32,
149) -> Option<ViewportTransform> {
150 ViewportTransform::new(
151 CanvasPoint {
152 x: lerp(from.pan.x, to.pan.x, progress),
153 y: lerp(from.pan.y, to.pan.y, progress),
154 },
155 lerp(from.zoom, to.zoom, progress),
156 )
157}
158
159fn lerp(from: f32, to: f32, progress: f32) -> f32 {
160 from + (to - from) * progress
161}
162
163fn cubic_in_out(t: f32) -> f32 {
164 let t = t.clamp(0.0, 1.0);
165 let doubled = t * 2.0;
166 if doubled <= 1.0 {
167 doubled * doubled * doubled / 2.0
168 } else {
169 let shifted = doubled - 2.0;
170 (shifted * shifted * shifted + 2.0) / 2.0
171 }
172}