Skip to main content

ferroui_base/media/transformation/
transform_operation.rs

1use crate::media::transformation::InterpolationUtilities;
2use crate::Matrix;
3
4/// The kind of a [`TransformOperation`].
5#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
6#[repr(i32)]
7pub enum OperationType {
8    #[default]
9    Translate = 0,
10    Rotate = 1,
11    Scale = 2,
12    Skew = 3,
13    Matrix = 4,
14    Identity = 5,
15}
16
17/// The skew view of [`DataLayout`].
18#[derive(Clone, Copy, Debug, Default, PartialEq)]
19pub struct SkewLayout {
20    pub x: f64,
21    pub y: f64,
22}
23
24/// The scale view of [`DataLayout`].
25#[derive(Clone, Copy, Debug, Default, PartialEq)]
26pub struct ScaleLayout {
27    pub x: f64,
28    pub y: f64,
29}
30
31/// The translation view of [`DataLayout`].
32#[derive(Clone, Copy, Debug, Default, PartialEq)]
33pub struct TranslateLayout {
34    pub x: f64,
35    pub y: f64,
36}
37
38/// The rotation view of [`DataLayout`].
39#[derive(Clone, Copy, Debug, Default, PartialEq)]
40pub struct RotateLayout {
41    pub angle: f64,
42}
43
44/// The parameters of a [`TransformOperation`].
45///
46/// The skew, scale, translation and rotation views share the same storage:
47/// the first component holds `x` (or the rotation angle) and the second `y`.
48#[derive(Clone, Copy, Debug, Default, PartialEq)]
49pub struct DataLayout {
50    first: f64,
51    second: f64,
52}
53
54impl DataLayout {
55    #[inline]
56    pub fn skew(&self) -> SkewLayout {
57        SkewLayout { x: self.first, y: self.second }
58    }
59
60    #[inline]
61    pub fn set_skew(&mut self, x: f64, y: f64) {
62        self.first = x;
63        self.second = y;
64    }
65
66    #[inline]
67    pub fn scale(&self) -> ScaleLayout {
68        ScaleLayout { x: self.first, y: self.second }
69    }
70
71    #[inline]
72    pub fn set_scale(&mut self, x: f64, y: f64) {
73        self.first = x;
74        self.second = y;
75    }
76
77    #[inline]
78    pub fn translate(&self) -> TranslateLayout {
79        TranslateLayout { x: self.first, y: self.second }
80    }
81
82    #[inline]
83    pub fn set_translate(&mut self, x: f64, y: f64) {
84        self.first = x;
85        self.second = y;
86    }
87
88    #[inline]
89    pub fn rotate(&self) -> RotateLayout {
90        RotateLayout { angle: self.first }
91    }
92
93    #[inline]
94    pub fn set_rotate(&mut self, angle: f64) {
95        self.first = angle;
96    }
97}
98
99/// Represents a single primitive transform (like translation, rotation,
100/// scale, etc.).
101///
102/// The default value is a translation by zero whose matrix is all zeros; call
103/// [`bake`](Self::bake) after setting the type and data.
104#[derive(Clone, Copy, Debug, Default, PartialEq)]
105pub struct TransformOperation {
106    pub type_: OperationType,
107    pub matrix: Matrix,
108    pub data: DataLayout,
109}
110
111impl TransformOperation {
112    /// Returns whether the operation produces the identity matrix.
113    #[inline]
114    pub fn is_identity(&self) -> bool {
115        self.matrix.is_identity()
116    }
117
118    /// Bakes this operation to a transform matrix.
119    pub fn bake(&mut self) {
120        self.matrix = Matrix::IDENTITY;
121
122        match self.type_ {
123            OperationType::Translate => {
124                self.matrix = Matrix::create_translation(self.data.translate().x, self.data.translate().y);
125            }
126            OperationType::Rotate => {
127                self.matrix = Matrix::create_rotation(self.data.rotate().angle);
128            }
129            OperationType::Scale => {
130                self.matrix = Matrix::create_scale(self.data.scale().x, self.data.scale().y);
131            }
132            OperationType::Skew => {
133                self.matrix = Matrix::create_skew(self.data.skew().x, self.data.skew().y);
134            }
135            OperationType::Matrix | OperationType::Identity => {}
136        }
137    }
138
139    /// Returns a new identity operation.
140    pub fn identity() -> TransformOperation {
141        TransformOperation { matrix: Matrix::IDENTITY, type_: OperationType::Identity, data: DataLayout::default() }
142    }
143
144    /// Attempts to interpolate between two transform operations. Returns
145    /// false if the operations cannot be interpolated.
146    pub fn try_interpolate(
147        from: Option<TransformOperation>,
148        to: Option<TransformOperation>,
149        progress: f64,
150        result: &mut TransformOperation,
151    ) -> bool {
152        let from_identity = Self::is_operation_identity(&from);
153        let to_identity = Self::is_operation_identity(&to);
154
155        if from_identity && to_identity {
156            result.matrix = Matrix::IDENTITY;
157            return true;
158        }
159
160        let from_value = match from {
161            Some(from) if !from_identity => from,
162            _ => Self::identity(),
163        };
164        let to_value = match to {
165            Some(to) if !to_identity => to,
166            _ => Self::identity(),
167        };
168
169        let interpolation_type = if to_identity { from_value.type_ } else { to_value.type_ };
170        result.type_ = interpolation_type;
171
172        match interpolation_type {
173            OperationType::Translate => {
174                let from_x = if from_identity { 0.0 } else { from_value.data.translate().x };
175                let from_y = if from_identity { 0.0 } else { from_value.data.translate().y };
176                let to_x = if to_identity { 0.0 } else { to_value.data.translate().x };
177                let to_y = if to_identity { 0.0 } else { to_value.data.translate().y };
178
179                result.data.set_translate(
180                    InterpolationUtilities::interpolate_scalars(from_x, to_x, progress),
181                    InterpolationUtilities::interpolate_scalars(from_y, to_y, progress),
182                );
183                result.bake();
184            }
185            OperationType::Rotate => {
186                let from_angle = if from_identity { 0.0 } else { from_value.data.rotate().angle };
187                let to_angle = if to_identity { 0.0 } else { to_value.data.rotate().angle };
188
189                result.data.set_rotate(InterpolationUtilities::interpolate_scalars(from_angle, to_angle, progress));
190                result.bake();
191            }
192            OperationType::Scale => {
193                let from_x = if from_identity { 1.0 } else { from_value.data.scale().x };
194                let from_y = if from_identity { 1.0 } else { from_value.data.scale().y };
195                let to_x = if to_identity { 1.0 } else { to_value.data.scale().x };
196                let to_y = if to_identity { 1.0 } else { to_value.data.scale().y };
197
198                result.data.set_scale(
199                    InterpolationUtilities::interpolate_scalars(from_x, to_x, progress),
200                    InterpolationUtilities::interpolate_scalars(from_y, to_y, progress),
201                );
202                result.bake();
203            }
204            OperationType::Skew => {
205                let from_x = if from_identity { 0.0 } else { from_value.data.skew().x };
206                let from_y = if from_identity { 0.0 } else { from_value.data.skew().y };
207                let to_x = if to_identity { 0.0 } else { to_value.data.skew().x };
208                let to_y = if to_identity { 0.0 } else { to_value.data.skew().y };
209
210                result.data.set_skew(
211                    InterpolationUtilities::interpolate_scalars(from_x, to_x, progress),
212                    InterpolationUtilities::interpolate_scalars(from_y, to_y, progress),
213                );
214                result.bake();
215            }
216            OperationType::Matrix => {
217                let from_matrix = if from_identity { Matrix::IDENTITY } else { from_value.matrix };
218                let to_matrix = if to_identity { Matrix::IDENTITY } else { to_value.matrix };
219
220                let (Some(from_decomposed), Some(to_decomposed)) =
221                    (Matrix::try_decompose_transform(from_matrix), Matrix::try_decompose_transform(to_matrix))
222                else {
223                    return false;
224                };
225
226                let interpolated = InterpolationUtilities::interpolate_decomposed_transforms(
227                    &from_decomposed,
228                    &to_decomposed,
229                    progress,
230                );
231                result.matrix = InterpolationUtilities::compose_transform(interpolated);
232            }
233            OperationType::Identity => {
234                result.matrix = Matrix::IDENTITY;
235            }
236        }
237
238        true
239    }
240
241    fn is_operation_identity(operation: &Option<TransformOperation>) -> bool {
242        match operation {
243            Some(operation) => operation.is_identity(),
244            None => true,
245        }
246    }
247}