Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-widgets/src/animation.rs
StratoSDK / crates / strato-widgets / src / animation.rs
1//! Animation system for widgets
2use std::time::{Duration, Instant};
3use strato_core::types::Color;
4 
5/// Animation curve
6#[derive(Debug, Clone, Copy, PartialEq)]
7pub enum Curve {
8 Linear,
9 EaseIn,
10 EaseOut,
11 EaseInOut,
12}
13 
14impl Curve {
15 /// Calculate progress value (0.0 to 1.0) based on curve
16 pub fn transform(&self, t: f32) -> f32 {
17 let t = t.clamp(0.0, 1.0);
18 match self {
19 Curve::Linear => t,
20 Curve::EaseIn => t * t,
21 Curve::EaseOut => t * (2.0 - t),
22 Curve::EaseInOut => {
23 if t < 0.5 {
24 2.0 * t * t
25 } else {
26 -1.0 + (4.0 - 2.0 * t) * t
27 }
28 }
29 }
30 }
31}
32 
33/// Controls an animation's state and progress
34#[derive(Debug, Clone)]
35pub struct AnimationController {
36 duration: Duration,
37 start_time: Option<Instant>,
38 curve: Curve,
39 is_repeating: bool,
40 is_reversed: bool,
41}
42 
43impl AnimationController {
44 /// Create a new controller
45 pub fn new(duration: Duration) -> Self {
46 Self {
47 duration,
48 start_time: None,
49 curve: Curve::Linear,
50 is_repeating: false,
51 is_reversed: false,
52 }
53 }
54 
55 /// Set animation curve
56 pub fn with_curve(mut self, curve: Curve) -> Self {
57 self.curve = curve;
58 self
59 }
60 
61 /// Set repeating
62 pub fn loop_forever(mut self) -> Self {
63 self.is_repeating = true;
64 self
65 }
66 
67 /// Start the animation
68 pub fn start(&mut self) {
69 self.start_time = Some(Instant::now());
70 }
71 
72 /// Reset the animation
73 pub fn reset(&mut self) {
74 self.start_time = None;
75 }
76 
77 /// Get current progress value (0.0 to 1.0)
78 pub fn value(&self) -> f32 {
79 let Some(start) = self.start_time else {
80 return 0.0;
81 };
82 
83 let elapsed = start.elapsed().as_secs_f32();
84 let duration = self.duration.as_secs_f32();
85 
86 if duration == 0.0 {
87 return 1.0;
88 }
89 
90 let raw_t = elapsed / duration;
91 
92 let t = if self.is_repeating {
93 let cycle = raw_t % 2.0;
94 if cycle > 1.0 {
95 2.0 - cycle
96 } else {
97 cycle
98 }
99 } else {
100 raw_t.clamp(0.0, 1.0)
101 };
102 
103 self.curve.transform(t)
104 }
105 
106 /// Check if animation is finished
107 pub fn is_completed(&self) -> bool {
108 if self.is_repeating {
109 return false;
110 }
111 if let Some(start) = self.start_time {
112 start.elapsed() >= self.duration
113 } else {
114 false
115 }
116 }
117}
118 
119/// Tween interface for interpolating values
120pub trait Tweenable: Copy {
121 fn lerp(start: Self, end: Self, t: f32) -> Self;
122}
123 
124impl Tweenable for f32 {
125 fn lerp(start: Self, end: Self, t: f32) -> Self {
126 start + (end - start) * t
127 }
128}
129 
130impl Tweenable for Color {
131 fn lerp(start: Self, end: Self, t: f32) -> Self {
132 Color::rgba(
133 f32::lerp(start.r, end.r, t),
134 f32::lerp(start.g, end.g, t),
135 f32::lerp(start.b, end.b, t),
136 f32::lerp(start.a, end.a, t),
137 )
138 }
139}
140 
141/// Simple tween object
142#[derive(Debug, Clone, Copy)]
143pub struct Tween<T: Tweenable> {
144 pub begin: T,
145 pub end: T,
146}
147 
148impl<T: Tweenable> Tween<T> {
149 pub fn new(begin: T, end: T) -> Self {
150 Self { begin, end }
151 }
152 
153 pub fn transform(&self, t: f32) -> T {
154 T::lerp(self.begin, self.end, t)
155 }
156}
157 
158/// A handle to an animation task
159pub type AnimationId = u64;
160 
161/// Animation status
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163pub enum AnimationStatus {
164 Playing,
165 Paused,
166 Completed,
167}
168 
169/// Advanced Timeline for managing complex animations
170pub struct Timeline {
171 animations: Vec<Box<dyn Animation>>,
172 status: AnimationStatus,
173 start_time: Option<Instant>,
174 elapsed: Duration,
175 speed: f32,
176}
177 
178impl Timeline {
179 pub fn new() -> Self {
180 Self {
181 animations: Vec::new(),
182 status: AnimationStatus::Paused,
183 start_time: None,
184 elapsed: Duration::ZERO,
185 speed: 1.0,
186 }
187 }
188 
189 pub fn add(&mut self, anim: impl Animation + 'static) {
190 self.animations.push(Box::new(anim));
191 }
192 
193 pub fn play(&mut self) {
194 if self.status != AnimationStatus::Playing {
195 self.status = AnimationStatus::Playing;
196 self.start_time = Some(Instant::now());
197 }
198 }
199 
200 pub fn pause(&mut self) {
201 if self.status == AnimationStatus::Playing {
202 self.status = AnimationStatus::Paused;
203 if let Some(start) = self.start_time {
204 self.elapsed += start.elapsed().mul_f32(self.speed);
205 self.start_time = None;
206 }
207 }
208 }
209 
210 pub fn update(&mut self) {
211 if self.status == AnimationStatus::Playing {
212 if let Some(start) = self.start_time {
213 let current_elapsed = self.elapsed + start.elapsed().mul_f32(self.speed);
214 
215 let mut all_finished = true;
216 for anim in &mut self.animations {
217 anim.update(current_elapsed);
218 if !anim.is_finished() {
219 all_finished = false;
220 }
221 }
222 
223 if all_finished {
224 self.status = AnimationStatus::Completed;
225 }
226 }
227 }
228 }
229 
230 pub fn reset(&mut self) {
231 self.status = AnimationStatus::Paused;
232 self.start_time = None;
233 self.elapsed = Duration::ZERO;
234 for anim in &mut self.animations {
235 anim.reset();
236 }
237 }
238}
239 
240/// Trait for any animation
241pub trait Animation: std::fmt::Debug {
242 fn update(&mut self, elapsed: Duration);
243 fn is_finished(&self) -> bool;
244 fn reset(&mut self);
245 fn duration(&self) -> Duration;
246}
247 
248// Re-implementing KeyframeAnimation properly with state for is_finished
249/// Animation that interpolates a value over time using a Signal target
250#[derive(Debug)]
251pub struct KeyframeAnimation<T: Tweenable + 'static + Send + Sync> {
252 controller: AnimationController,
253 tween: Tween<T>,
254 target: strato_core::state::Signal<T>,
255 finished: bool,
256}
257 
258impl<T: Tweenable + std::fmt::Debug + Send + Sync> KeyframeAnimation<T> {
259 pub fn new(duration: Duration, tween: Tween<T>, target: strato_core::state::Signal<T>) -> Self {
260 Self {
261 controller: AnimationController::new(duration),
262 tween,
263 target,
264 finished: false,
265 }
266 }
267 
268 pub fn with_curve(mut self, curve: Curve) -> Self {
269 self.controller = self.controller.with_curve(curve);
270 self
271 }
272}
273 
274impl<T: Tweenable + std::fmt::Debug + Send + Sync> Animation for KeyframeAnimation<T> {
275 fn update(&mut self, elapsed: Duration) {
276 let d_secs = self.controller.duration.as_secs_f32();
277 if d_secs == 0.0 {
278 self.finished = true;
279 return;
280 }
281 
282 let t_secs = elapsed.as_secs_f32();
283 let raw_t = t_secs / d_secs;
284 
285 self.finished = raw_t >= 1.0;
286 
287 let t = raw_t.clamp(0.0, 1.0);
288 let curved_t = self.controller.curve.transform(t);
289 let value = self.tween.transform(curved_t);
290 
291 self.target.set(value);
292 }
293 
294 fn is_finished(&self) -> bool {
295 self.finished
296 }
297 
298 fn reset(&mut self) {
299 self.finished = false;
300 // Optionally reset value to start?
301 // let value = self.tween.transform(0.0);
302 // self.target.set(value);
303 }
304 
305 fn duration(&self) -> Duration {
306 self.controller.duration
307 }
308}
309 
310/// Run animations in sequence
311#[derive(Debug)]
312pub struct Sequence {
313 animations: Vec<Box<dyn Animation>>,
314}
315 
316impl Sequence {
317 pub fn new(animations: Vec<Box<dyn Animation>>) -> Self {
318 Self { animations }
319 }
320}
321 
322impl Animation for Sequence {
323 fn update(&mut self, elapsed: Duration) {
324 let mut time_so_far = Duration::ZERO;
325 
326 for anim in &mut self.animations {
327 let duration = anim.duration();
328 let anim_end_time = time_so_far + duration;
329 
330 if elapsed >= anim_end_time {
331 // Ensure this animation is in its final state
332 anim.update(duration);
333 } else if elapsed >= time_so_far {
334 // Currently active
335 anim.update(elapsed - time_so_far);
336 } else {
337 // Future
338 anim.update(Duration::ZERO);
339 }
340 
341 time_so_far += duration;
342 }
343 }
344 
345 fn is_finished(&self) -> bool {
346 if let Some(last) = self.animations.last() {
347 last.is_finished()
348 } else {
349 true
350 }
351 }
352 
353 fn reset(&mut self) {
354 for anim in &mut self.animations {
355 anim.reset();
356 }
357 }
358 
359 fn duration(&self) -> Duration {
360 self.animations.iter().map(|a| a.duration()).sum()
361 }
362}
363 
364/// Run animations in parallel
365#[derive(Debug)]
366pub struct Parallel {
367 animations: Vec<Box<dyn Animation>>,
368}
369 
370impl Parallel {
371 pub fn new(animations: Vec<Box<dyn Animation>>) -> Self {
372 Self { animations }
373 }
374}
375 
376impl Animation for Parallel {
377 fn update(&mut self, elapsed: Duration) {
378 for anim in &mut self.animations {
379 anim.update(elapsed);
380 }
381 }
382 
383 fn is_finished(&self) -> bool {
384 self.animations.iter().all(|a| a.is_finished())
385 }
386 
387 fn reset(&mut self) {
388 for anim in &mut self.animations {
389 anim.reset();
390 }
391 }
392 
393 fn duration(&self) -> Duration {
394 self.animations
395 .iter()
396 .map(|a| a.duration())
397 .max()
398 .unwrap_or(Duration::ZERO)
399 }
400}
401