dioxus_motion/animations/
transform.rs

1//! Transform module for 2D transformations
2//!
3//! Provides a Transform type that can be animated, supporting:
4//! - Translation (x, y)
5//! - Scale
6//! - Rotation
7//!
8//! Uses radians for rotation and supports smooth interpolation.
9
10use crate::Animatable;
11use wide::f32x4;
12
13/// Represents a 2D transformation with translation, scale, and rotation
14///
15/// # Examples
16/// ```rust
17/// use dioxus_motion::prelude::Transform;
18/// use std::f32::consts::PI;
19/// let transform = Transform::new(100.0, 50.0, 1.5, PI/4.0);
20/// ```
21#[derive(Debug, Copy, Clone, PartialEq)]
22pub struct Transform {
23    /// X translation component
24    pub x: f32,
25    /// Y translation component
26    pub y: f32,
27    /// Uniform scale factor
28    pub scale: f32,
29    /// Rotation in radians
30    pub rotation: f32,
31}
32
33impl Transform {
34    /// Creates a new transform with specified parameters
35    pub fn new(x: f32, y: f32, scale: f32, rotation: f32) -> Self {
36        Self {
37            x,
38            y,
39            scale,
40            rotation,
41        }
42    }
43
44    /// Creates an identity transform (no transformation)
45    pub fn identity() -> Self {
46        Self {
47            x: 0.0,
48            y: 0.0,
49            scale: 1.0,
50            rotation: 0.0,
51        }
52    }
53}
54
55impl Default for Transform {
56    fn default() -> Self {
57        Transform::identity()
58    }
59}
60
61impl std::ops::Add for Transform {
62    type Output = Self;
63
64    fn add(self, other: Self) -> Self {
65        Transform::new(
66            self.x + other.x,
67            self.y + other.y,
68            self.scale + other.scale,
69            self.rotation + other.rotation,
70        )
71    }
72}
73
74impl std::ops::Sub for Transform {
75    type Output = Self;
76
77    fn sub(self, other: Self) -> Self {
78        Transform::new(
79            self.x - other.x,
80            self.y - other.y,
81            self.scale - other.scale,
82            self.rotation - other.rotation,
83        )
84    }
85}
86
87impl std::ops::Mul<f32> for Transform {
88    type Output = Self;
89
90    fn mul(self, factor: f32) -> Self {
91        Transform::new(
92            self.x * factor,
93            self.y * factor,
94            self.scale * factor,
95            self.rotation * factor,
96        )
97    }
98}
99
100/// Implementation of Animatable for f32 primitive type
101/// Much simpler with the new trait design - leverages standard Rust operators
102impl Animatable for f32 {
103    fn interpolate(&self, target: &Self, t: f32) -> Self {
104        self + (target - self) * t
105    }
106
107    fn magnitude(&self) -> f32 {
108        self.abs()
109    }
110
111    // Uses default epsilon of 0.01 from the trait
112}
113
114/// Implementation of Animatable for Transform
115/// Much simpler with the new trait design - uses standard operators
116impl Animatable for Transform {
117    fn interpolate(&self, target: &Self, t: f32) -> Self {
118        // SIMD for x, y, scale; handle rotation separately for shortest path
119        let a = [self.x, self.y, self.scale, 0.0];
120        let b = [target.x, target.y, target.scale, 0.0];
121        let va = f32x4::new(a);
122        let vb = f32x4::new(b);
123        let vt = f32x4::splat(t.clamp(0.0, 1.0));
124        let result = va + (vb - va) * vt;
125        let out = result.to_array();
126
127        // Rotation: shortest path
128        let mut rotation_diff = target.rotation - self.rotation;
129        if rotation_diff > std::f32::consts::PI {
130            rotation_diff -= 2.0 * std::f32::consts::PI;
131        } else if rotation_diff < -std::f32::consts::PI {
132            rotation_diff += 2.0 * std::f32::consts::PI;
133        }
134        let rotation = self.rotation + rotation_diff * t;
135
136        Transform::new(out[0], out[1], out[2], rotation)
137    }
138
139    fn magnitude(&self) -> f32 {
140        (self.x * self.x
141            + self.y * self.y
142            + self.scale * self.scale
143            + self.rotation * self.rotation)
144            .sqrt()
145    }
146
147    // Uses default epsilon of 0.01 from the trait - no need for TRANSFORM_EPSILON
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153    use std::f32::consts::PI;
154
155    #[test]
156    fn test_transform_new() {
157        let transform = Transform::new(100.0, 50.0, 1.5, PI / 4.0);
158        assert_eq!(transform.x, 100.0);
159        assert_eq!(transform.y, 50.0);
160        assert_eq!(transform.scale, 1.5);
161        assert!((transform.rotation - PI / 4.0).abs() < f32::EPSILON);
162    }
163
164    #[test]
165    fn test_transform_default() {
166        let transform = Transform::identity();
167        assert_eq!(transform.x, 0.0);
168        assert_eq!(transform.y, 0.0);
169        assert_eq!(transform.scale, 1.0);
170        assert_eq!(transform.rotation, 0.0);
171    }
172
173    #[test]
174    fn test_transform_lerp() {
175        let start = Transform::new(0.0, 0.0, 1.0, 0.0);
176        let end = Transform::new(100.0, 100.0, 2.0, PI);
177        let mid = start.interpolate(&end, 0.5);
178
179        assert_eq!(mid.x, 50.0);
180        assert_eq!(mid.y, 50.0);
181        assert_eq!(mid.scale, 1.5);
182        assert!((mid.rotation - PI / 2.0).abs() < f32::EPSILON);
183    }
184}