Skip to main content

cranpose_animation/
geometry_animation.rs

1//! Animation over Cranpose's 2D geometry types.
2//!
3//! Layers [`Lerp`] and [`SpringScalar`] onto [`Point`], [`Size`] and [`Rect`]
4//! so they plug into the same generic [`animateValueAsState`] core as
5//! [`animateFloatAsState`](crate::animateFloatAsState) and
6//! [`animateColorAsState`](crate::animateColorAsState).
7//!
8//! Note: This module uses camelCase for function names to maintain 1:1 API
9//! parity with Jetpack Compose.
10
11#![allow(non_snake_case)]
12
13use cranpose_core::State;
14use cranpose_ui_graphics::{Point, Rect, Size};
15
16use crate::animation::{AnimationType, Lerp, SpringScalar, animateValueAsState};
17
18impl Lerp for Point {
19    fn lerp(&self, target: &Self, fraction: f32) -> Self {
20        Point::new(
21            self.x.lerp(&target.x, fraction),
22            self.y.lerp(&target.y, fraction),
23        )
24    }
25}
26
27impl SpringScalar for Point {
28    const DIMENSIONS: usize = 2;
29
30    fn dimension(&self, index: usize) -> f32 {
31        match index {
32            0 => self.x,
33            _ => self.y,
34        }
35    }
36
37    fn from_dimensions(dimensions: [f32; crate::animation::SPRING_MAX_DIMENSIONS]) -> Self {
38        Point::new(dimensions[0], dimensions[1])
39    }
40}
41
42impl Lerp for Size {
43    fn lerp(&self, target: &Self, fraction: f32) -> Self {
44        Size::new(
45            self.width.lerp(&target.width, fraction),
46            self.height.lerp(&target.height, fraction),
47        )
48    }
49}
50
51impl SpringScalar for Size {
52    const DIMENSIONS: usize = 2;
53
54    fn dimension(&self, index: usize) -> f32 {
55        match index {
56            0 => self.width,
57            _ => self.height,
58        }
59    }
60
61    fn from_dimensions(dimensions: [f32; crate::animation::SPRING_MAX_DIMENSIONS]) -> Self {
62        Size::new(dimensions[0], dimensions[1])
63    }
64}
65
66impl Lerp for Rect {
67    fn lerp(&self, target: &Self, fraction: f32) -> Self {
68        Rect {
69            x: self.x.lerp(&target.x, fraction),
70            y: self.y.lerp(&target.y, fraction),
71            width: self.width.lerp(&target.width, fraction),
72            height: self.height.lerp(&target.height, fraction),
73        }
74    }
75}
76
77impl SpringScalar for Rect {
78    const DIMENSIONS: usize = 4;
79
80    fn dimension(&self, index: usize) -> f32 {
81        match index {
82            0 => self.x,
83            1 => self.y,
84            2 => self.width,
85            _ => self.height,
86        }
87    }
88
89    fn from_dimensions(dimensions: [f32; crate::animation::SPRING_MAX_DIMENSIONS]) -> Self {
90        Rect {
91            x: dimensions[0],
92            y: dimensions[1],
93            width: dimensions[2],
94            height: dimensions[3],
95        }
96    }
97}
98
99/// Fire-and-forget animation of a 2D position.
100///
101/// Mirrors Jetpack Compose: `animateOffsetAsState(targetValue, animationSpec, label)`,
102/// using Cranpose's own [`Point`] in place of Compose's `Offset`.
103#[track_caller]
104pub fn animateOffsetAsState(target: Point, animation: AnimationType, label: &str) -> State<Point> {
105    animateValueAsState(target, animation, label)
106}
107
108/// Fire-and-forget animation of a 2D extent.
109///
110/// Mirrors Jetpack Compose: `animateSizeAsState(targetValue, animationSpec, label)`.
111#[track_caller]
112pub fn animateSizeAsState(target: Size, animation: AnimationType, label: &str) -> State<Size> {
113    animateValueAsState(target, animation, label)
114}
115
116/// Fire-and-forget animation of a rectangle.
117///
118/// Mirrors Jetpack Compose: `animateRectAsState(targetValue, animationSpec, label)`.
119#[track_caller]
120pub fn animateRectAsState(target: Rect, animation: AnimationType, label: &str) -> State<Rect> {
121    animateValueAsState(target, animation, label)
122}
123
124#[cfg(test)]
125#[path = "tests/geometry_animation_tests.rs"]
126mod tests;