tawasuyu

tawasuyu/llimphi

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

Rust 484Markdown 258TOML 131RON 38118 Definiciones

⎇ main shared/foreign-lottie/src/runtime/model/value.rs Rust · 269 líneas · 7.8 KB Historia Quién tocó qué crudo

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
4use kurbo::{
5    ParamCurve, Point,
6    common::{solve_cubic, solve_itp},
7};
8
9/// Fixed or animated value.
10#[derive(Clone, Debug)]
11pub enum Value<T: Tween> {
12    /// Fixed value.
13    Fixed(T),
14    /// Animated value.
15    Animated(Animated<T>),
16}
17
18impl<T: Tween + Default> Value<T> {
19    /// Returns the value at a specified frame.
20    pub fn evaluate(&self, frame: f64) -> T {
21        match self {
22            Self::Fixed(fixed) => fixed.clone(),
23            Self::Animated(animated) => animated.evaluate_or(frame, T::default()),
24        }
25    }
26}
27
28impl<T: Tween> Value<T> {
29    /// Returns true if the value is fixed.
30    pub fn is_fixed(&self) -> bool {
31        matches!(self, Self::Fixed(_))
32    }
33
34    /// Returns the value at a specified frame, with a fallback default.
35    pub fn evaluate_or(&self, frame: f64, default: T) -> T {
36        match self {
37            Self::Fixed(fixed) => fixed.clone(),
38            Self::Animated(animated) => animated.evaluate_or(frame, default),
39        }
40    }
41}
42
43impl<T: Tween + Default> Default for Value<T> {
44    fn default() -> Self {
45        Self::Fixed(T::default())
46    }
47}
48
49/// Borrowed or owned value.
50#[derive(Clone, Debug)]
51pub enum ValueRef<'a, T> {
52    Borrowed(&'a T),
53    Owned(T),
54}
55
56impl<T> AsRef<T> for ValueRef<'_, T> {
57    fn as_ref(&self) -> &T {
58        match self {
59            Self::Borrowed(value) => value,
60            Self::Owned(value) => value,
61        }
62    }
63}
64
65impl<T: Clone> ValueRef<'_, T> {
66    pub fn into_owned(self) -> T {
67        match self {
68            Self::Borrowed(value) => value.clone(),
69            Self::Owned(value) => value,
70        }
71    }
72}
73
74#[derive(Copy, Clone, Debug)]
75pub struct Easing {
76    pub o: EasingHandle,
77    pub i: EasingHandle,
78}
79
80impl Easing {
81    pub const LERP: Easing = Easing {
82        o: EasingHandle { x: 0.0, y: 0.0 },
83        i: EasingHandle { x: 1.0, y: 1.0 },
84    };
85}
86
87#[derive(Copy, Clone, Debug)]
88pub struct EasingHandle {
89    pub x: f64,
90    pub y: f64,
91}
92
93/// Time for a particular keyframe, represented as a frame number.
94#[derive(Copy, Clone, Debug)]
95pub struct Time {
96    /// Frame number.
97    pub frame: f64,
98    /// Easing tangent going into the next keyframe
99    pub in_tangent: Option<EasingHandle>,
100    /// Easing tangent leaving the current keyframe
101    pub out_tangent: Option<EasingHandle>,
102    /// Whether it's a hold frame.
103    pub hold: bool,
104}
105
106impl Time {
107    /// Returns the frame indices and interpolation weight for the given frame,
108    /// and whether to hold the frame
109    pub(crate) fn frames_and_weight(
110        times: &[Time],
111        frame: f64,
112    ) -> Option<([usize; 2], f64, Easing, bool)> {
113        if times.is_empty() {
114            return None;
115        }
116        use core::cmp::Ordering::*;
117        let ix = match times.binary_search_by(|x| {
118            if x.frame < frame {
119                Less
120            } else if x.frame > frame {
121                Greater
122            } else {
123                Equal
124            }
125        }) {
126            Ok(ix) => ix,
127            Err(ix) => ix.saturating_sub(1),
128        };
129        let ix0 = ix.min(times.len() - 1);
130        let ix1 = (ix0 + 1).min(times.len() - 1);
131
132        let t0 = times[ix0];
133        let t1 = times[ix1];
134        let (t0_ox, t0_oy) = t0.out_tangent.map(|o| (o.x, o.y)).unwrap_or((0.0, 0.0));
135        let (t0_ix, t0_iy) = t0.in_tangent.map(|i| (i.x, i.y)).unwrap_or((1.0, 1.0));
136        let easing = Easing {
137            o: EasingHandle { x: t0_ox, y: t0_oy },
138            i: EasingHandle { x: t0_ix, y: t0_iy },
139        };
140        let hold = t0.hold;
141        let t = (frame - t0.frame) / (t1.frame - t0.frame);
142        Some(([ix0, ix1], t.clamp(0.0, 1.0), easing, hold))
143    }
144}
145
146#[derive(Clone, Debug)]
147pub struct Animated<T: Tween> {
148    pub times: Vec<Time>,
149    pub values: Vec<T>,
150}
151
152impl<T: Tween> Animated<T> {
153    /// Returns the value at the specified frame.
154    pub fn evaluate_or(&self, frame: f64, default: T) -> T {
155        self.evaluate_inner(frame).unwrap_or(default)
156    }
157
158    fn evaluate_inner(&self, frame: f64) -> Option<T> {
159        let ([ix0, ix1], t, easing, hold) = Time::frames_and_weight(&self.times, frame)?;
160        let t = if hold { 0.0 } else { t };
161
162        let v1 = self.values.get(ix0)?;
163        let v2 = self.values.get(ix1)?;
164
165        Some(v1.tween(v2, t, &easing))
166    }
167}
168
169/// Something that can be interpolated with an easing function.
170pub trait Tween: Clone {
171    fn tween(&self, other: &Self, t: f64, easing: &Easing) -> Self;
172}
173
174impl Tween for f64 {
175    /// Tween between two f64 values using a cubic bezier easing curve.
176    ///
177    /// Unfortunately, kurbo doesn't expose a way to get the eased value of t directly, so we have
178    /// to re-implement it here.
179    ///
180    /// t - normalized time 0.0 to 1.0
181    fn tween(&self, other: &Self, t: f64, easing: &Easing) -> Self {
182        // avoid unnecessary calculations
183        if t <= 0.0 {
184            return *self;
185        }
186        if t >= 1.0 {
187            return *other;
188        }
189
190        // Created for [kurbo::ParamCurve::eval]
191        let curve = kurbo::CubicBez::new(
192            Point::new(0.0, 0.0),
193            Point::new(easing.o.x, easing.o.y),
194            Point::new(easing.i.x, easing.i.y),
195            Point::new(1.0, 1.0),
196        );
197
198        // Manual copy-paste of [kurbo::bezpath::cubic_bez_coefs]
199        // in preparation for [kurbo::common::solve_cubic]
200        let (x0, x1, x2, x3) = (curve.p0.x, curve.p1.x, curve.p2.x, curve.p3.x);
201        let c0 = x0;
202        let c1 = 3.0 * (x1 - x0);
203        let c2 = 3.0 * (x2 - 2.0 * x1 + x0);
204        let c3 = x3 - 3.0 * x2 + 3.0 * x1 - x0;
205
206        // Find where along the curve we are at normalized time `t`
207        // See: https://github.com/jneem/linesweeper/blob/7d620ed004605141519037c681edac93e22f5855/src/curve/mod.rs#L355
208        let max_coeff = c0.abs().max(c1.abs()).max(c2.abs()).max(c3.abs());
209        let eps = 1e-12 * max_coeff.max(1.0);
210
211        let roots = kurbo::common::solve_cubic(c0 - t, c1, c2, c3);
212        let u = roots
213            .iter()
214            .copied()
215            .find(|&r| (-eps..=1.0 + eps).contains(&r))
216            .unwrap_or_else(|| {
217                // Fallback when no valid root found due to precision issues
218                // The cubic evaluates to (c0 - t) at u=0 and (c0 + c1 + c2 + c3 - t) at u=1
219                let val_at_0 = (c0 - t).abs();
220                let val_at_1 = (c0 + c1 + c2 + c3 - t).abs();
221
222                if val_at_0 <= val_at_1 { 0.0 } else { 1.0 }
223            });
224
225        // Evaluate the curve at that point to get the y value
226        let eased_y = curve.eval(u).y;
227
228        self + (other - self) * eased_y
229    }
230}
231
232impl Tween for kurbo::Point {
233    fn tween(&self, other: &Self, t: f64, easing: &Easing) -> Self {
234        Self::new(
235            self.x.tween(&other.x, t, easing),
236            self.y.tween(&other.y, t, easing),
237        )
238    }
239}
240
241impl Tween for kurbo::Vec2 {
242    fn tween(&self, other: &Self, t: f64, easing: &Easing) -> Self {
243        Self::new(
244            self.x.tween(&other.x, t, easing),
245            self.y.tween(&other.y, t, easing),
246        )
247    }
248}
249
250impl Tween for kurbo::Size {
251    fn tween(&self, other: &Self, t: f64, easing: &Easing) -> Self {
252        Self::new(
253            self.width.tween(&other.width, t, easing),
254            self.height.tween(&other.height, t, easing),
255        )
256    }
257}
258
259impl Tween for peniko::Color {
260    fn tween(&self, other: &Self, t: f64, easing: &Easing) -> Self {
261        let [r1, g1, b1, a1] = self.components;
262        let [r2, g2, b2, a2] = other.components;
263        let r = (r1 as f64).tween(&(r2 as f64), t, easing);
264        let g = (g1 as f64).tween(&(g2 as f64), t, easing);
265        let b = (b1 as f64).tween(&(b2 as f64), t, easing);
266        let a = (a1 as f64).tween(&(a2 as f64), t, easing);
267        peniko::Color::new([r as f32, g as f32, b as f32, a as f32])
268    }
269}