1use crate::protocol::Mutation;
16use crate::schema::{
17 AnimatedProperty, Animation, AnimationControl, AnimationEventKind, ReducedMotion,
18};
19use std::cell::{Cell, RefCell};
20use std::rc::Rc;
21use std::sync::atomic::{AtomicBool, Ordering};
22use std::sync::{Arc, Mutex, MutexGuard};
23
24#[derive(Clone, Copy, Debug, PartialEq)]
26pub struct AnimationMoment {
27 pub node_id: u32,
29 pub animation_id: u32,
30 pub kind: AnimationEventKind,
31 pub property: AnimatedProperty,
32 pub slot: u8,
33 pub iteration: u32,
34 pub elapsed_ms: f32,
36 pub time_nanos: u64,
39}
40
41enum Command {
42 Start(Animation<'static>),
43 Control {
44 node_id: u32,
45 animation_id: u32,
46 property: AnimatedProperty,
47 slot: u8,
48 op: AnimationControl,
49 at_time_nanos: u64,
50 },
51}
52
53static QUEUE: Mutex<Vec<Command>> = Mutex::new(Vec::new());
56
57static PENDING: AtomicBool = AtomicBool::new(false);
60
61fn queue() -> MutexGuard<'static, Vec<Command>> {
62 QUEUE.lock().unwrap_or_else(|poison| poison.into_inner())
63}
64
65fn submit(command: Command) {
66 queue().push(command);
67 PENDING.store(true, Ordering::Release);
68 if !crate::boundary::in_host_call() {
70 crate::boundary::request_frame_from_worker();
71 }
72}
73
74pub fn start_animation(animation: Animation<'static>) {
76 submit(Command::Start(animation));
77}
78
79pub fn control_animation(
82 node_id: u32,
83 animation_id: u32,
84 property: AnimatedProperty,
85 slot: u8,
86 op: AnimationControl,
87 at_time_nanos: u64,
88) {
89 submit(Command::Control {
90 node_id,
91 animation_id,
92 property,
93 slot,
94 op,
95 at_time_nanos,
96 });
97}
98
99pub(crate) fn drain(mut emit: impl FnMut(Mutation<'_>)) {
101 if !PENDING.swap(false, Ordering::AcqRel) {
102 return;
103 }
104 let mut taken = std::mem::take(&mut *queue());
105 for command in taken.drain(..) {
106 emit(match command {
107 Command::Start(animation) => Mutation::StartAnimation(animation),
108 Command::Control {
109 node_id,
110 animation_id,
111 property,
112 slot,
113 op,
114 at_time_nanos,
115 } => Mutation::ControlAnimation {
116 node_id,
117 animation_id,
118 property,
119 slot,
120 op,
121 at_time_nanos,
122 },
123 });
124 }
125 let mut guard = queue();
126 if guard.is_empty() {
127 *guard = taken;
128 }
129}
130
131struct Subscriber {
132 id: u64,
133 notify: Arc<dyn Fn() + Send + Sync>,
134}
135
136type MomentHandler = Rc<dyn Fn(AnimationMoment)>;
137
138thread_local! {
139 static REDUCED_MOTION: Cell<ReducedMotion> = const { Cell::new(ReducedMotion::Unknown) };
141 static SUBSCRIBERS: RefCell<Vec<Subscriber>> = const { RefCell::new(Vec::new()) };
142 static MOMENT_HANDLERS: RefCell<Vec<(u64, MomentHandler)>> = const { RefCell::new(Vec::new()) };
143 static NEXT_ID: Cell<u64> = const { Cell::new(1) };
144}
145
146fn next_id() -> u64 {
147 NEXT_ID.with(|next| {
148 let id = next.get();
149 next.set(id + 1);
150 id
151 })
152}
153
154pub fn reduced_motion() -> ReducedMotion {
159 REDUCED_MOTION.with(Cell::get)
160}
161
162pub(crate) fn publish_reduced_motion(state: ReducedMotion) -> bool {
164 if REDUCED_MOTION.with(Cell::get) == state {
165 return false;
166 }
167 REDUCED_MOTION.with(|current| current.set(state));
168 SUBSCRIBERS.with_borrow(|subscribers| {
169 for subscriber in subscribers {
170 (subscriber.notify)();
171 }
172 !subscribers.is_empty()
173 })
174}
175
176pub struct ReducedMotionSubscription {
178 id: u64,
179}
180
181pub fn subscribe_reduced_motion(notify: Arc<dyn Fn() + Send + Sync>) -> ReducedMotionSubscription {
184 let id = next_id();
185 SUBSCRIBERS.with_borrow_mut(|subscribers| subscribers.push(Subscriber { id, notify }));
186 ReducedMotionSubscription { id }
187}
188
189impl Drop for ReducedMotionSubscription {
190 fn drop(&mut self) {
191 SUBSCRIBERS.with_borrow_mut(|subscribers| subscribers.retain(|s| s.id != self.id));
192 }
193}
194
195pub struct AnimationEventSubscription {
197 id: u64,
198}
199
200pub fn on_animation_event(handler: Rc<dyn Fn(AnimationMoment)>) -> AnimationEventSubscription {
203 let id = next_id();
204 MOMENT_HANDLERS.with_borrow_mut(|handlers| handlers.push((id, handler)));
205 AnimationEventSubscription { id }
206}
207
208impl Drop for AnimationEventSubscription {
209 fn drop(&mut self) {
210 MOMENT_HANDLERS.with_borrow_mut(|handlers| handlers.retain(|(id, _)| *id != self.id));
211 }
212}
213
214pub(crate) fn deliver(moment: AnimationMoment) -> bool {
216 let handlers: Vec<MomentHandler> =
218 MOMENT_HANDLERS.with_borrow(|handlers| handlers.iter().map(|(_, h)| h.clone()).collect());
219 for handler in &handlers {
220 handler(moment);
221 }
222 !handlers.is_empty()
223}
224
225#[doc(hidden)]
228pub fn reset_animations() {
229 queue().clear();
230 PENDING.store(false, Ordering::Release);
231 REDUCED_MOTION.with(|current| current.set(ReducedMotion::Unknown));
232 SUBSCRIBERS.with_borrow_mut(Vec::clear);
233 MOMENT_HANDLERS.with_borrow_mut(Vec::clear);
234}
235
236#[derive(Clone, Copy, Debug, PartialEq)]
243pub struct AnimationSpec {
244 pub duration_ms: f32,
245 pub delay_ms: f32,
246 pub timing: crate::schema::Timing,
247}
248
249impl AnimationSpec {
250 pub const STANDARD: Self = Self {
252 duration_ms: 300.0,
253 delay_ms: 0.0,
254 timing: crate::schema::Timing::CubicBezier {
255 x1: 0.2,
256 y1: 0.0,
257 x2: 0.0,
258 y2: 1.0,
259 },
260 };
261
262 pub const fn tween(duration_ms: f32, timing: crate::schema::Timing) -> Self {
264 Self {
265 duration_ms,
266 delay_ms: 0.0,
267 timing,
268 }
269 }
270}
271
272impl Default for AnimationSpec {
273 fn default() -> Self {
274 Self::STANDARD
275 }
276}
277
278pub fn transition(
282 node_id: u32,
283 animation_id: u32,
284 property: AnimatedProperty,
285 to: crate::schema::KeyframeValue,
286 spec: AnimationSpec,
287) -> Animation<'static> {
288 use crate::schema::{
289 AnimationEvents, ColorInterpolation, FillMode, Keyframe, PlayState, PlaybackDirection,
290 Timing,
291 };
292 let colour = matches!(
293 property,
294 AnimatedProperty::Color | AnimatedProperty::Background | AnimatedProperty::BorderColor
295 );
296 let transform = property == AnimatedProperty::Transform;
297 Animation {
298 node_id,
299 animation_id,
300 property,
301 slot: 0,
302 direction: PlaybackDirection::Normal,
303 fill: FillMode::None,
304 play_state: PlayState::Running,
305 interpolation: colour.then_some(ColorInterpolation::SrgbPremultiplied),
306 start_time_nanos: 0,
307 delay_ms: spec.delay_ms,
308 duration_ms: spec.duration_ms,
309 iterations: 1.0,
310 origin_x: if transform { 0.5 } else { 0.0 },
311 origin_y: if transform { 0.5 } else { 0.0 },
312 events: AnimationEvents::NONE,
313 keyframes: std::borrow::Cow::Owned(vec![
314 Keyframe {
315 offset: 0.0,
316 timing: spec.timing,
317 from_presented: true,
318 value: to.clone(),
319 },
320 Keyframe {
321 offset: 1.0,
322 timing: Timing::Linear,
323 from_presented: false,
324 value: to,
325 },
326 ]),
327 }
328}
329
330#[derive(Clone, Copy, Debug, PartialEq)]
334pub struct GraphicsLayer {
335 pub rotation_z: f32,
336 pub scale_x: f32,
337 pub scale_y: f32,
338 pub translation_x: f32,
339 pub translation_y: f32,
340 pub transform_origin: (f32, f32),
341 pub alpha: f32,
342}
343
344impl Default for GraphicsLayer {
345 fn default() -> Self {
346 Self {
347 rotation_z: 0.0,
348 scale_x: 1.0,
349 scale_y: 1.0,
350 translation_x: 0.0,
351 translation_y: 0.0,
352 transform_origin: (0.5, 0.5),
353 alpha: 1.0,
354 }
355 }
356}
357
358impl GraphicsLayer {
359 pub fn transform(&self) -> crate::schema::Modifier {
362 let radians = self.rotation_z.to_radians();
363 let (sin, cos) = radians.sin_cos();
364 crate::schema::Modifier::Transform {
365 a: self.scale_x * cos,
366 b: self.scale_x * sin,
367 c: -self.scale_y * sin,
368 d: self.scale_y * cos,
369 e: self.translation_x,
370 f: self.translation_y,
371 origin_x: self.transform_origin.0,
372 origin_y: self.transform_origin.1,
373 }
374 }
375
376 pub fn alpha(&self) -> crate::schema::Modifier {
378 crate::schema::Modifier::Alpha(self.alpha)
379 }
380}
381
382#[derive(Clone, Copy, Debug, PartialEq)]
391pub struct Animated<T> {
392 pub target: T,
393 pub spec: AnimationSpec,
394}
395
396impl<T: Copy> Animated<T> {
397 pub fn value(&self) -> T {
399 self.target
400 }
401}
402
403pub fn animate_float_as_state(target: f32, spec: AnimationSpec) -> Animated<f32> {
406 Animated { target, spec }
407}
408
409pub fn animate_color_as_state(
411 target: crate::schema::Paint,
412 spec: AnimationSpec,
413) -> Animated<crate::schema::Paint> {
414 Animated { target, spec }
415}
416
417pub fn animate_offset_as_state(target: (f32, f32), spec: AnimationSpec) -> Animated<(f32, f32)> {
420 Animated { target, spec }
421}
422
423pub(crate) fn animated_bytes(target: &[u32], spec: &AnimationSpec) -> Vec<u8> {
426 use crate::schema::Timing;
427 let mut out = Vec::with_capacity(4 * target.len() + 28);
428 out.push(target.len() as u8);
429 for word in target {
430 out.extend_from_slice(&word.to_le_bytes());
431 }
432 out.extend_from_slice(&spec.duration_ms.to_le_bytes());
433 out.extend_from_slice(&spec.delay_ms.to_le_bytes());
434 match spec.timing {
435 Timing::Linear => out.push(1),
436 Timing::CubicBezier { x1, y1, x2, y2 } => {
437 out.push(2);
438 for value in [x1, y1, x2, y2] {
439 out.extend_from_slice(&value.to_le_bytes());
440 }
441 }
442 Timing::Steps { count, position } => {
443 out.push(3);
444 out.extend_from_slice(&count.to_le_bytes());
445 out.extend_from_slice(&u16::from(position).to_le_bytes());
446 }
447 }
448 out
449}
450
451pub(crate) fn read_animated(bytes: &[u8]) -> Option<(Vec<u32>, AnimationSpec)> {
454 use crate::schema::{StepPosition, Timing};
455 let word = |at: usize| -> Option<u32> {
456 Some(u32::from_le_bytes(bytes.get(at..at + 4)?.try_into().ok()?))
457 };
458 let float = |at: usize| word(at).map(f32::from_bits);
459 let count = usize::from(*bytes.first()?);
460 let target = (0..count)
461 .map(|index| word(1 + 4 * index))
462 .collect::<Option<Vec<_>>>()?;
463 let at = 1 + 4 * count;
464 let duration_ms = float(at)?;
465 let delay_ms = float(at + 4)?;
466 let timing = match *bytes.get(at + 8)? {
467 1 => Timing::Linear,
468 2 => Timing::CubicBezier {
469 x1: float(at + 9)?,
470 y1: float(at + 13)?,
471 x2: float(at + 17)?,
472 y2: float(at + 21)?,
473 },
474 3 => Timing::Steps {
475 count: word(at + 9)?,
476 position: StepPosition::try_from(u16::from_le_bytes(
477 bytes.get(at + 13..at + 15)?.try_into().ok()?,
478 ))
479 .ok()?,
480 },
481 _ => return None,
482 };
483 Some((
484 target,
485 AnimationSpec {
486 duration_ms,
487 delay_ms,
488 timing,
489 },
490 ))
491}