1use ravenrs_core::{Color, Effect, Point, Rect};
2
3use crate::oklab::{premultiplied, unpremultiplied};
4use crate::{Easing, Spring};
5
6pub trait Animatable: Copy {
8 const REST: f32;
10 fn to_components(self) -> [f32; 4];
12 fn from_components(c: [f32; 4]) -> Self;
14}
15
16impl Animatable for f32 {
17 const REST: f32 = 0.001;
18 fn to_components(self) -> [f32; 4] {
19 [self, 0.0, 0.0, 0.0]
20 }
21 fn from_components(c: [f32; 4]) -> Self {
22 c[0]
23 }
24}
25
26impl Animatable for Point {
27 const REST: f32 = 0.02;
28 fn to_components(self) -> [f32; 4] {
29 [self.x, self.y, 0.0, 0.0]
30 }
31 fn from_components(c: [f32; 4]) -> Self {
32 Point::new(c[0], c[1])
33 }
34}
35
36impl Animatable for Rect {
37 const REST: f32 = 0.02;
38 fn to_components(self) -> [f32; 4] {
39 [self.x(), self.y(), self.width(), self.height()]
40 }
41 fn from_components(c: [f32; 4]) -> Self {
42 Rect::new(c[0], c[1], c[2].max(0.0), c[3].max(0.0))
43 }
44}
45
46impl Animatable for Color {
47 const REST: f32 = 0.002;
48 fn to_components(self) -> [f32; 4] {
49 premultiplied(self)
50 }
51 fn from_components(c: [f32; 4]) -> Self {
52 unpremultiplied(c)
53 }
54}
55
56impl Animatable for Effect {
57 const REST: f32 = 0.002;
58 fn to_components(self) -> [f32; 4] {
59 [self.opacity, self.offset.x, self.offset.y, self.scale]
60 }
61 fn from_components(c: [f32; 4]) -> Self {
62 Effect { opacity: c[0].clamp(0.0, 1.0), offset: Point::new(c[1], c[2]), scale: c[3].max(0.0) }
63 }
64}
65
66#[derive(Clone, Copy, Debug, PartialEq)]
68#[non_exhaustive]
69pub enum Transition {
70 Instant,
72 Tween {
74 duration: f32,
76 easing: Easing,
78 },
79 Spring(Spring),
81}
82
83impl Transition {
84 pub const fn tween(duration: f32, easing: Easing) -> Self {
86 Self::Tween { duration, easing }
87 }
88
89 pub const fn fast() -> Self {
91 Self::tween(0.12, Easing::EaseOut)
92 }
93
94 pub const fn base() -> Self {
96 Self::tween(0.2, Easing::EaseInOut)
97 }
98
99 pub const fn slow() -> Self {
101 Self::tween(0.32, Easing::Emphasized)
102 }
103
104 pub const fn draw_in() -> Self {
107 Self::tween(1.0, Easing::Bezier(crate::CubicBezier::new(0.25, 1.0, 0.5, 1.0)))
108 }
109
110 pub const fn snappy() -> Self {
112 Self::Spring(Spring::snappy())
113 }
114
115 pub const fn gentle() -> Self {
117 Self::Spring(Spring::gentle())
118 }
119
120 pub const fn without_bounce(self) -> Self {
123 match self {
124 Self::Spring(_) => Self::fast(),
125 other => other,
126 }
127 }
128}
129
130const ZERO: [f32; 4] = [0.0; 4];
131
132#[derive(Clone, Copy, Debug, PartialEq)]
133struct Segment {
134 delta: [f32; 4],
136 start: f64,
137 duration: f32,
138 easing: Easing,
139}
140
141impl Segment {
142 fn remaining(&self, now: f64) -> f32 {
143 let p = ((now - self.start) as f32 / self.duration.max(1e-4)).clamp(0.0, 1.0);
144 1.0 - self.easing.sample(p)
145 }
146}
147
148#[derive(Clone, Copy, Debug, PartialEq)]
149struct SpringRun {
150 spring: Spring,
151 start: f64,
152 x0: [f32; 4],
154 v0: [f32; 4],
155}
156
157#[derive(Clone, Debug)]
164pub struct Animated<T> {
165 exact: T,
167 target: [f32; 4],
168 segments: Vec<Segment>,
169 spring: Option<SpringRun>,
170}
171
172impl<T: Animatable> Animated<T> {
173 pub fn new(value: T) -> Self {
175 Self { exact: value, target: value.to_components(), segments: Vec::new(), spring: None }
176 }
177
178 pub fn target(&self) -> T {
180 self.exact
181 }
182
183 pub fn set_target(&mut self, target: T, transition: Transition, now: f64) {
186 let new = target.to_components();
187 if new == self.target {
188 return;
189 }
190 match transition {
191 Transition::Instant => {
192 self.segments.clear();
193 self.spring = None;
194 }
195 Transition::Tween { duration, easing } if duration > 0.0 => {
196 let delta = std::array::from_fn(|i| new[i] - self.target[i]);
197 self.segments.push(Segment { delta, start: now, duration, easing });
198 }
199 Transition::Tween { .. } => {
200 self.segments.clear();
201 self.spring = None;
202 }
203 Transition::Spring(spring) => {
204 let (x, v) = (self.components(now), self.velocity(now));
205 self.segments.clear();
206 let x0 = std::array::from_fn(|i| x[i] - new[i]);
207 self.spring = Some(SpringRun { spring, start: now, x0, v0: v });
208 }
209 }
210 self.target = new;
211 self.exact = target;
212 }
213
214 pub fn value(&self, now: f64) -> T {
216 if self.is_moving() { T::from_components(self.components(now)) } else { self.exact }
217 }
218
219 fn components(&self, now: f64) -> [f32; 4] {
220 let mut out = self.target;
221 if let Some(run) = &self.spring {
222 let t = (now - run.start) as f32;
223 for (i, v) in out.iter_mut().enumerate() {
224 *v += run.spring.sample(run.x0[i], run.v0[i], t).0;
225 }
226 }
227 for s in &self.segments {
228 let remaining = s.remaining(now);
229 for (i, v) in out.iter_mut().enumerate() {
230 *v -= s.delta[i] * remaining;
231 }
232 }
233 out
234 }
235
236 pub fn velocity(&self, now: f64) -> [f32; 4] {
238 let mut out = ZERO;
239 if let Some(run) = &self.spring {
240 let t = (now - run.start) as f32;
241 for (i, v) in out.iter_mut().enumerate() {
242 *v += run.spring.sample(run.x0[i], run.v0[i], t).1;
243 }
244 }
245 const H: f64 = 1.0 / 480.0;
246 for s in &self.segments {
247 let slope = (s.remaining(now - H) - s.remaining(now + H)) / (2.0 * H as f32);
248 for (i, v) in out.iter_mut().enumerate() {
249 *v += s.delta[i] * slope;
250 }
251 }
252 out
253 }
254
255 pub fn step(&mut self, now: f64) -> bool {
257 self.segments.retain(|s| now - s.start < f64::from(s.duration));
258 if let Some(run) = &self.spring {
259 let t = (now - run.start) as f32;
260 let rest = (0..4).all(|i| {
261 let (x, v) = run.spring.sample(run.x0[i], run.v0[i], t);
262 x.abs() < T::REST && v.abs() < T::REST * 10.0
263 });
264 if rest {
265 self.spring = None;
266 }
267 }
268 !self.segments.is_empty() || self.spring.is_some()
269 }
270
271 pub fn is_moving(&self) -> bool {
273 !self.segments.is_empty() || self.spring.is_some()
274 }
275}