tawasuyu

tawasuyu/llimphi

Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.

Rust 124Markdown 82TOML 78Shell 12149 Definitions

⎇ main shared/foreign-lottie/src/runtime/model/animated.rs Rust · 474 lines · 14.2 KB History Who touched what raw

chore: refresco desde el monorepo — 27 crates nuevos (anim, lottie, svg, mesh, image, layer, test, wire-view, widgets rag-sidebar/router/lazy-list/rive-button) y sync de los 95 existentes 93253cc22 · Sergio ·

1// Copyright 2024 the Velato Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4/*!
5Representations of animated values.
6*/
7
8use super::*;
9
10use kurbo::PathEl;
11
12#[derive(Clone, Debug)]
13pub enum Position {
14    Value(Value<Point>),
15    SplitValues((Value<f64>, Value<f64>)),
16}
17
18/// Animated affine transformation.
19#[derive(Clone, Debug)]
20pub struct Transform {
21    /// Anchor point.
22    pub anchor: Value<Point>,
23    /// Translation.
24    pub position: Position,
25    /// Rotation angle.
26    pub rotation: Value<f64>,
27    /// Scale factor.
28    pub scale: Value<Vec2>,
29    /// Skew factor.
30    pub skew: Value<f64>,
31    /// Skew angle, 0 skews along the x axis, 90 along the y axis.
32    pub skew_angle: Value<f64>,
33}
34
35impl Transform {
36    /// Returns true if the transform is fixed.
37    pub fn is_fixed(&self) -> bool {
38        self.anchor.is_fixed()
39            && match &self.position {
40                Position::Value(value) => value.is_fixed(),
41                Position::SplitValues((x_value, y_value)) => {
42                    x_value.is_fixed() && y_value.is_fixed()
43                }
44            }
45            && self.rotation.is_fixed()
46            && self.scale.is_fixed()
47            && self.skew.is_fixed()
48            && self.skew_angle.is_fixed()
49    }
50
51    /// Evaluates the transform at the specified frame.
52    pub fn evaluate(&self, frame: f64) -> Affine {
53        let anchor = self.anchor.evaluate(frame);
54        let position = match &self.position {
55            Position::Value(value) => value.evaluate(frame),
56            Position::SplitValues((x_value, y_value)) => kurbo::Point {
57                x: x_value.evaluate(frame),
58                y: y_value.evaluate(frame),
59            },
60        };
61        let rotation = self.rotation.evaluate(frame);
62        let scale = self.scale.evaluate(frame);
63        let skew = self.skew.evaluate(frame);
64        let skew_angle = self.skew_angle.evaluate(frame);
65        let skew_matrix = if skew != 0.0 {
66            let skew = skew.to_radians();
67            let angle = skew_angle.to_radians();
68            Affine::rotate(-angle) * Affine::skew(skew.tan(), 0.0) * Affine::rotate(angle)
69        } else {
70            Affine::IDENTITY
71        };
72        Affine::translate((position.x, position.y))
73            * Affine::rotate(rotation.to_radians())
74            * skew_matrix
75            * Affine::scale_non_uniform(scale.x / 100.0, scale.y / 100.0)
76            * Affine::translate((-anchor.x, -anchor.y))
77    }
78
79    /// Converts the animated value to its model representation.
80    pub fn into_model(self) -> super::Transform {
81        if self.is_fixed() {
82            super::Transform::Fixed(self.evaluate(0.0))
83        } else {
84            super::Transform::Animated(self)
85        }
86    }
87}
88
89/// Animated ellipse.
90#[derive(Clone, Debug)]
91pub struct Ellipse {
92    /// True if the ellipse should be drawn in CCW order.
93    pub is_ccw: bool,
94    /// Position of the ellipse.
95    pub position: Value<Point>,
96    /// Size of the ellipse.
97    pub size: Value<Size>,
98}
99
100impl Ellipse {
101    pub fn is_fixed(&self) -> bool {
102        self.position.is_fixed() && self.size.is_fixed()
103    }
104
105    pub fn evaluate(&self, frame: f64) -> kurbo::Ellipse {
106        let position = self.position.evaluate(frame);
107        let size = self.size.evaluate(frame);
108        let radii = (size.width * 0.5, size.height * 0.5);
109        kurbo::Ellipse::new(position, radii, 0.0)
110    }
111}
112
113/// Animated rounded rectangle.
114#[derive(Clone, Debug)]
115pub struct Rect {
116    /// True if the rect should be drawn in CCW order.
117    pub is_ccw: bool,
118    /// Position of the rectangle.
119    pub position: Value<Point>,
120    /// Size of the rectangle.
121    pub size: Value<Size>,
122    /// Radius of the rectangle corners.
123    pub corner_radius: Value<f64>,
124}
125
126impl Rect {
127    /// Returns true if the rectangle is fixed.
128    pub fn is_fixed(&self) -> bool {
129        self.position.is_fixed() && self.size.is_fixed() && self.corner_radius.is_fixed()
130    }
131
132    /// Evaluates the rectangle at the specified frame.
133    pub fn evaluate(&self, frame: f64) -> kurbo::RoundedRect {
134        let position = self.position.evaluate(frame);
135        let size = self.size.evaluate(frame);
136        let position = Point::new(
137            position.x - size.width * 0.5,
138            position.y - size.height * 0.5,
139        );
140        let radius = self.corner_radius.evaluate(frame);
141        kurbo::RoundedRect::new(
142            position.x,
143            position.y,
144            position.x + size.width,
145            position.y + size.height,
146            radius,
147        )
148    }
149}
150
151/// Animated star or polygon.
152#[derive(Clone, Debug)]
153pub struct Star {
154    pub is_polygon: bool,
155    pub direction: f64,
156    pub position: Value<Point>,
157    pub inner_radius: Value<f64>,
158    pub inner_roundness: Value<f64>,
159    pub outer_radius: Value<f64>,
160    pub outer_roundness: Value<f64>,
161    pub rotation: Value<f64>,
162    pub points: Value<f64>,
163}
164
165// TODO: Use this.
166//impl Star {
167//    pub fn is_fixed(&self) -> bool {
168//        self.position.is_fixed()
169//            && self.inner_radius.is_fixed()
170//            && self.inner_roundness.is_fixed()
171//            && self.outer_radius.is_fixed()
172//            && self.outer_roundness.is_fixed()
173//            && self.rotation.is_fixed()
174//            && self.points.is_fixed()
175//    }
176//}
177
178/// Animated cubic spline.
179#[derive(Clone, Debug)]
180pub struct Spline {
181    /// True if the spline is closed.
182    pub is_closed: bool,
183    /// Collection of times.
184    pub times: Vec<Time>,
185    /// Collection of splines for each time. The splines at each point must have a start, but may also have an
186    /// end defined. When the end is not defined, then the next keyframe's start is used.
187    pub values: Vec<SplineKeyframeValues>,
188}
189
190/// The values of an animated Spline at a single keyframe. When end is None, the next frame's start is used.
191#[derive(Clone, Debug)]
192pub struct SplineKeyframeValues {
193    pub start: Vec<Point>,
194    pub end: Option<Vec<Point>>,
195}
196
197impl Spline {
198    /// Evaluates the spline at the given frame and emits the elements
199    /// to the specified path.
200    pub fn evaluate(&self, frame: f64, path: &mut Vec<PathEl>) -> bool {
201        let Some(([ix0, ix1], t, _easing, hold)) = Time::frames_and_weight(&self.times, frame)
202        else {
203            return false;
204        };
205        let t = if hold { 0.0 } else { t };
206        let (Some(from), Some(to)) = (self.values.get(ix0), self.values.get(ix1)) else {
207            return false;
208        };
209        let to_slice = {
210            if let Some(end) = &from.end {
211                end.as_slice()
212            } else {
213                to.start.as_slice()
214            }
215        };
216        (from.start.as_slice(), to_slice, t).to_path(self.is_closed, path);
217        true
218    }
219}
220
221/// Animated repeater effect.
222#[derive(Clone, Debug)]
223pub struct Repeater {
224    /// Number of times elements should be repeated.
225    pub copies: Value<f64>,
226    /// Offset applied to each element.
227    pub offset: Value<f64>,
228    /// Anchor point.
229    pub anchor_point: Value<Point>,
230    /// Translation.
231    pub position: Value<Point>,
232    /// Rotation in degrees.
233    pub rotation: Value<f64>,
234    /// Scale.
235    pub scale: Value<Vec2>,
236    /// Opacity of the first element.
237    pub start_opacity: Value<f64>,
238    /// Opacity of the last element.
239    pub end_opacity: Value<f64>,
240}
241
242impl Repeater {
243    /// Returns true if the repeater contains no animated properties.
244    pub fn is_fixed(&self) -> bool {
245        self.copies.is_fixed()
246            && self.offset.is_fixed()
247            && self.anchor_point.is_fixed()
248            && self.position.is_fixed()
249            && self.rotation.is_fixed()
250            && self.scale.is_fixed()
251            && self.start_opacity.is_fixed()
252            && self.end_opacity.is_fixed()
253    }
254
255    /// Evaluates the repeater at the specified frame.
256    pub fn evaluate(&self, frame: f64) -> fixed::Repeater {
257        let copies = self.copies.evaluate(frame).round() as usize;
258        let offset = self.offset.evaluate(frame);
259        let anchor_point = self.anchor_point.evaluate(frame);
260        let position = self.position.evaluate(frame);
261        let rotation = self.rotation.evaluate(frame);
262        let scale = self.scale.evaluate(frame);
263        let start_opacity = self.start_opacity.evaluate(frame);
264        let end_opacity = self.end_opacity.evaluate(frame);
265        fixed::Repeater {
266            copies,
267            offset,
268            anchor_point,
269            position,
270            rotation,
271            scale,
272            start_opacity,
273            end_opacity,
274        }
275    }
276
277    /// Converts the animated value to its model representation.
278    pub fn into_model(self) -> super::Repeater {
279        if self.is_fixed() {
280            super::Repeater::Fixed(self.evaluate(0.0))
281        } else {
282            super::Repeater::Animated(self)
283        }
284    }
285}
286
287/// Animated stroke properties.
288#[derive(Clone, Debug)]
289pub struct Stroke {
290    /// Width of the stroke.
291    pub width: Value<f64>,
292    /// Join style.
293    pub join: kurbo::Join,
294    /// Limit for miter joins.
295    pub miter_limit: Option<f64>,
296    /// Cap style.
297    pub cap: kurbo::Cap,
298}
299
300impl Stroke {
301    /// Returns true if the stroke is fixed.
302    pub fn is_fixed(&self) -> bool {
303        self.width.is_fixed()
304    }
305
306    /// Evaluates the stroke at the specified frame.
307    pub fn evaluate(&self, frame: f64) -> kurbo::Stroke {
308        let width = self.width.evaluate(frame);
309        let mut stroke = kurbo::Stroke::new(width)
310            .with_caps(self.cap)
311            .with_join(self.join);
312        if let Some(miter_limit) = self.miter_limit {
313            stroke.miter_limit = miter_limit;
314        }
315        stroke
316    }
317
318    /// Converts the animated value to its model representation.
319    pub fn into_model(self) -> super::Stroke {
320        if self.is_fixed() {
321            super::Stroke::Fixed(self.evaluate(0.0))
322        } else {
323            super::Stroke::Animated(self)
324        }
325    }
326}
327
328/// Animated linear or radial gradient.
329#[derive(Clone, Debug)]
330pub struct Gradient {
331    /// True if the gradient is radial.
332    pub is_radial: bool,
333    /// Starting point.
334    pub start_point: Value<Point>,
335    /// Ending point.
336    pub end_point: Value<Point>,
337    /// Stop offsets and color values.
338    pub stops: super::ColorStops,
339}
340
341impl Gradient {
342    /// Returns true if the value contains no animated properties.
343    pub fn is_fixed(&self) -> bool {
344        self.start_point.is_fixed() && self.end_point.is_fixed() && self.stops.is_fixed()
345    }
346
347    /// Evaluates the animated value at the given frame.
348    pub fn evaluate(&self, frame: f64) -> peniko::Brush {
349        let start = self.start_point.evaluate(frame);
350        let end = self.end_point.evaluate(frame);
351        let stops = self.stops.evaluate(frame).into_owned();
352        if self.is_radial {
353            let radius = (end.to_vec2() - start.to_vec2()).hypot();
354            let mut grad = peniko::Gradient::new_radial(start, radius as f32);
355            grad.stops = stops;
356            grad.into()
357        } else {
358            let mut grad = peniko::Gradient::new_linear(start, end);
359            grad.stops = stops;
360            grad.into()
361        }
362    }
363}
364
365#[derive(Clone, Debug)]
366pub struct ColorStops {
367    pub frames: Vec<Time>,
368    pub values: Vec<Vec<f64>>,
369    pub count: usize,
370}
371
372impl ColorStops {
373    pub fn evaluate(&self, frame: f64) -> fixed::ColorStops {
374        self.evaluate_inner(frame).unwrap_or_default()
375    }
376
377    fn evaluate_inner(&self, frame: f64) -> Option<fixed::ColorStops> {
378        let ([ix0, ix1], t, easing, hold) = Time::frames_and_weight(&self.frames, frame)?;
379
380        let v0 = self.values.get(ix0)?;
381        let v1 = self.values.get(ix1)?;
382
383        let mut stops: fixed::ColorStops = Default::default();
384        for i in 0..self.count {
385            let j = i * 5;
386            let offset = v0.get(j)?.tween(v1.get(j)?, t, &easing);
387            let t = if hold { 0.0 } else { t };
388            let r = v0.get(j + 1)?.tween(v1.get(j + 1)?, t, &easing);
389            let g = v0.get(j + 2)?.tween(v1.get(j + 2)?, t, &easing);
390            let b = v0.get(j + 3)?.tween(v1.get(j + 3)?, t, &easing);
391            let a = v0.get(j + 4)?.tween(v1.get(j + 4)?, t, &easing);
392            let stop = peniko::ColorStop::from((
393                offset as f32,
394                peniko::Color::new([r as f32, g as f32, b as f32, a as f32]),
395            ));
396            stops.push(stop);
397        }
398        Some(stops)
399    }
400}
401
402/// Animated brush.
403#[derive(Clone, Debug)]
404pub enum Brush {
405    /// Solid color.
406    Solid(Value<Color>),
407    /// Gradient color.
408    Gradient(Gradient),
409}
410
411impl Brush {
412    /// Returns true if the value contains no animated properties.
413    pub fn is_fixed(&self) -> bool {
414        match self {
415            Self::Solid(value) => value.is_fixed(),
416            Self::Gradient(value) => value.is_fixed(),
417        }
418    }
419
420    /// Evaluates the animation at the specified time.
421    pub fn evaluate(&self, alpha: f64, frame: f64) -> fixed::Brush {
422        match self {
423            Self::Solid(value) => value
424                .evaluate_or(frame, peniko::Color::TRANSPARENT)
425                .multiply_alpha(alpha as f32)
426                .into(),
427            Self::Gradient(value) => value.evaluate(frame),
428        }
429    }
430
431    /// Converts the animated value to its model representation.
432    pub fn into_model(self) -> super::Brush {
433        if self.is_fixed() {
434            super::Brush::Fixed(self.evaluate(1.0, 0.0))
435        } else {
436            super::Brush::Animated(self)
437        }
438    }
439}
440
441#[derive(Clone, Debug)]
442pub struct Trim {
443    /// Start of the visible segment (0.0 to 100.0).
444    pub start: Value<f64>,
445    /// End of the visible segment (0.0 to 100.0).
446    pub end: Value<f64>,
447    /// Offset rotation in degrees.
448    pub offset: Value<f64>,
449}
450
451impl Trim {
452    /// Returns true if the trim is fixed.
453    pub fn is_fixed(&self) -> bool {
454        self.start.is_fixed() && self.end.is_fixed() && self.offset.is_fixed()
455    }
456
457    /// Evaluates the trim at the specified frame.
458    pub fn evaluate(&self, frame: f64) -> fixed::Trim {
459        fixed::Trim {
460            start: self.start.evaluate(frame),
461            end: self.end.evaluate(frame),
462            offset: self.offset.evaluate(frame),
463        }
464    }
465
466    /// Converts the animated value to its model representation.
467    pub fn into_model(self) -> super::Trim {
468        if self.is_fixed() {
469            super::Trim::Fixed(self.evaluate(0.0))
470        } else {
471            super::Trim::Animated(self)
472        }
473    }
474}