Skip to main content

scal_core/
transform.rs

1use glam::{Vec2, Vec3};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5use crate::anim_op::{AnimOP, IntoAnimOp};
6use crate::ease::Ease;
7use crate::seconds::Time;
8
9/// The transform of an object, containing its position, rotation, and scale.
10#[derive(Clone, Debug, Serialize, Deserialize)]
11pub struct Transform {
12    /// Unique identifier for this transform
13    pub uuid: Uuid,
14    /// Optional parent transform UUID for hierarchical transforms
15    pub parent: Option<Uuid>,
16    /// 3D position of the object (z is used for draw ordering)
17    pub position: Vec3,
18    /// Rotation of the object in degrees
19    pub rotation: f32,
20    /// Uniform or nonuniform scale of the object
21    pub scale: Vec2,
22}
23
24impl Transform {
25    /// Create a new transform at the given position.
26    /// A random UUID is generated for identification.
27    #[must_use]
28    pub fn new(position: Vec3) -> Self {
29        Self {
30            uuid: Uuid::new_v4(),
31            parent: None,
32            position,
33            rotation: 0.0,
34            scale: Vec2::ONE,
35        }
36    }
37
38    /// Set the parent of this transform
39    #[must_use]
40    pub const fn with_parent(mut self, parent: Uuid) -> Self {
41        self.parent = Some(parent);
42        self
43    }
44
45    /// Returns a builder for an animation that moves this object to a new position.
46    /// ```
47    ///                transform.position()
48    ///                    .to(Vec2::new(100.0, 200.0))
49    ///                    .over(5.s())
50    ///                    .ease(Ease::InOutCubic),
51    /// ```
52    #[must_use]
53    pub const fn position(&self) -> PositionBuilder {
54        PositionBuilder {
55            uuid: self.uuid,
56            target: None,
57            object: None,
58            duration: 1.0,
59            ease: Ease::Linear,
60        }
61    }
62
63    /// Returns a builder for an animation that scales this object.
64    /// ```
65    ///                transform.scale()
66    ///                    .to(Vec2::new(2.0, 2.0))
67    ///                    .over(5.s())
68    ///                    .ease(Ease::InOutCubic),
69    /// ```
70    #[must_use]
71    pub const fn scale(&self) -> ScaleBuilder {
72        ScaleBuilder {
73            uuid: self.uuid,
74            target: None,
75            object: None,
76            duration: 1.0,
77            ease: Ease::Linear,
78        }
79    }
80
81    /// Returns a builder for an animation that rotates this object.
82    /// ```
83    ///                transform.rotation()
84    ///                    .to(360.0)
85    ///                    .over(5.s())
86    ///                    .ease(Ease::InOutCubic),
87    /// ```
88    #[must_use]
89    pub const fn rotation(&self) -> RotateBuilder {
90        RotateBuilder {
91            uuid: self.uuid,
92            target: None,
93            duration: 1.0,
94            ease: Ease::Linear,
95        }
96    }
97}
98
99/// Builder for an animation that moves an object to a target position
100pub struct PositionBuilder {
101    pub(crate) uuid: Uuid,
102    pub(crate) target: Option<Vec2>,
103    pub(crate) object: Option<Uuid>,
104    pub(crate) duration: Time,
105    pub(crate) ease: Ease,
106}
107
108#[allow(clippy::return_self_not_must_use)]
109impl PositionBuilder {
110    #[must_use]
111    /// Move to the position of another object instead of a coordinate
112    pub fn object(mut self, target: impl Into<uuid::Uuid>) -> Self {
113        self.object = Some(target.into());
114        self
115    }
116
117    #[must_use]
118    /// Set the target position to move to
119    pub const fn to(mut self, target: Vec2) -> Self {
120        self.target = Some(target);
121        self
122    }
123
124    #[must_use]
125    /// Set the duration of the movement animation
126    pub const fn over(mut self, duration: Time) -> Self {
127        self.duration = duration;
128        self
129    }
130
131    #[must_use]
132    /// Set the easing function and return the animation
133    pub fn ease(mut self, ease: Ease) -> AnimOP {
134        self.ease = ease;
135        self.into()
136    }
137}
138
139impl From<PositionBuilder> for AnimOP {
140    fn from(b: PositionBuilder) -> Self {
141        let target = b.target.unwrap_or(Vec2::ZERO);
142        b.object.map_or(
143            Self::TransformMovePos(b.uuid, target, b.duration, b.ease, None),
144            |obj| Self::TransformMoveToObj(b.uuid, obj, target, b.duration, b.ease, None),
145        )
146    }
147}
148
149impl IntoAnimOp for PositionBuilder {
150    fn into_anim_op(self) -> AnimOP {
151        self.into()
152    }
153}
154
155/// Builder for an animation that scales an object
156pub struct ScaleBuilder {
157    pub(crate) uuid: Uuid,
158    pub(crate) target: Option<Vec2>,
159    pub(crate) object: Option<Uuid>,
160    pub(crate) duration: Time,
161    pub(crate) ease: Ease,
162}
163
164#[allow(clippy::return_self_not_must_use)]
165impl ScaleBuilder {
166    #[must_use]
167    /// Match the scale of another object instead of using a coordinate
168    pub fn object(mut self, target: impl Into<uuid::Uuid>) -> Self {
169        self.object = Some(target.into());
170        self
171    }
172
173    #[must_use]
174    /// Set the target scale
175    pub const fn to(mut self, target: Vec2) -> Self {
176        self.target = Some(target);
177        self
178    }
179
180    #[must_use]
181    /// Set the duration of the scale animation
182    pub const fn over(mut self, duration: Time) -> Self {
183        self.duration = duration;
184        self
185    }
186
187    #[must_use]
188    /// Set the easing function and return the animation
189    pub fn ease(mut self, ease: Ease) -> AnimOP {
190        self.ease = ease;
191        self.into()
192    }
193}
194
195impl From<ScaleBuilder> for AnimOP {
196    fn from(b: ScaleBuilder) -> Self {
197        Self::TransformScale(
198            b.uuid,
199            b.target.unwrap_or(Vec2::ONE),
200            b.duration,
201            b.ease,
202            None,
203        )
204    }
205}
206
207impl IntoAnimOp for ScaleBuilder {
208    fn into_anim_op(self) -> AnimOP {
209        self.into()
210    }
211}
212
213/// Builder for an animation that rotates an object
214pub struct RotateBuilder {
215    pub(crate) uuid: Uuid,
216    pub(crate) target: Option<f32>,
217    pub(crate) duration: Time,
218    pub(crate) ease: Ease,
219}
220
221#[allow(clippy::return_self_not_must_use)]
222impl RotateBuilder {
223    #[must_use]
224    /// Set the target rotation in degrees
225    pub const fn to(mut self, target: f32) -> Self {
226        self.target = Some(target);
227        self
228    }
229
230    #[must_use]
231    /// Set the duration of the rotation animation
232    pub const fn over(mut self, duration: Time) -> Self {
233        self.duration = duration;
234        self
235    }
236
237    #[must_use]
238    /// Set the easing function and return the animation
239    pub fn ease(mut self, ease: Ease) -> AnimOP {
240        self.ease = ease;
241        self.into()
242    }
243}
244
245impl From<RotateBuilder> for AnimOP {
246    fn from(b: RotateBuilder) -> Self {
247        Self::TransformRotate(b.uuid, b.target.unwrap_or(0.0), b.duration, b.ease, None)
248    }
249}
250
251impl IntoAnimOp for RotateBuilder {
252    fn into_anim_op(self) -> AnimOP {
253        self.into()
254    }
255}