1use core::fmt;
4
5use crate::css::Css;
6use crate::data_type::{Angle, Length, LengthPercentage};
7use crate::to_css::{write_number, ToCss};
8
9#[derive(Clone, Debug, PartialEq)]
12pub enum TransformFn {
13 Translate(LengthPercentage, LengthPercentage),
15 TranslateX(LengthPercentage),
17 TranslateY(LengthPercentage),
19 TranslateZ(Length),
21 Translate3d(LengthPercentage, LengthPercentage, Length),
23 Rotate(Angle),
25 RotateX(Angle),
27 RotateY(Angle),
29 RotateZ(Angle),
31 Scale(f32, f32),
33 ScaleX(f32),
35 ScaleY(f32),
37 Skew(Angle, Angle),
39 SkewX(Angle),
41 SkewY(Angle),
43 Matrix([f32; 6]),
45 Matrix3d([f32; 16]),
47}
48
49impl ToCss for TransformFn {
50 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
51 match self {
52 TransformFn::Translate(x, y) => {
53 dest.write_str("translate(")?;
54 x.to_css(dest)?;
55 dest.write_str(", ")?;
56 y.to_css(dest)?;
57 dest.write_char(')')
58 }
59 TransformFn::TranslateX(x) => {
60 dest.write_str("translateX(")?;
61 x.to_css(dest)?;
62 dest.write_char(')')
63 }
64 TransformFn::TranslateY(y) => {
65 dest.write_str("translateY(")?;
66 y.to_css(dest)?;
67 dest.write_char(')')
68 }
69 TransformFn::TranslateZ(z) => {
70 dest.write_str("translateZ(")?;
71 z.to_css(dest)?;
72 dest.write_char(')')
73 }
74 TransformFn::Translate3d(x, y, z) => {
75 dest.write_str("translate3d(")?;
76 x.to_css(dest)?;
77 dest.write_str(", ")?;
78 y.to_css(dest)?;
79 dest.write_str(", ")?;
80 z.to_css(dest)?;
81 dest.write_char(')')
82 }
83 TransformFn::Rotate(a) => fn_one(dest, "rotate", a),
84 TransformFn::RotateX(a) => fn_one(dest, "rotateX", a),
85 TransformFn::RotateY(a) => fn_one(dest, "rotateY", a),
86 TransformFn::RotateZ(a) => fn_one(dest, "rotateZ", a),
87 TransformFn::Scale(x, y) => {
88 dest.write_str("scale(")?;
89 write_number(dest, *x)?;
90 dest.write_str(", ")?;
91 write_number(dest, *y)?;
92 dest.write_char(')')
93 }
94 TransformFn::ScaleX(x) => {
95 dest.write_str("scaleX(")?;
96 write_number(dest, *x)?;
97 dest.write_char(')')
98 }
99 TransformFn::ScaleY(y) => {
100 dest.write_str("scaleY(")?;
101 write_number(dest, *y)?;
102 dest.write_char(')')
103 }
104 TransformFn::Skew(x, y) => {
105 dest.write_str("skew(")?;
106 x.to_css(dest)?;
107 dest.write_str(", ")?;
108 y.to_css(dest)?;
109 dest.write_char(')')
110 }
111 TransformFn::SkewX(a) => fn_one(dest, "skewX", a),
112 TransformFn::SkewY(a) => fn_one(dest, "skewY", a),
113 TransformFn::Matrix(m) => {
114 dest.write_str("matrix(")?;
115 for (i, v) in m.iter().enumerate() {
116 if i > 0 {
117 dest.write_str(", ")?;
118 }
119 write_number(dest, *v)?;
120 }
121 dest.write_char(')')
122 }
123 TransformFn::Matrix3d(m) => {
124 dest.write_str("matrix3d(")?;
125 for (i, v) in m.iter().enumerate() {
126 if i > 0 {
127 dest.write_str(", ")?;
128 }
129 write_number(dest, *v)?;
130 }
131 dest.write_char(')')
132 }
133 }
134 }
135}
136
137fn fn_one(dest: &mut dyn fmt::Write, name: &str, a: &Angle) -> fmt::Result {
138 dest.write_str(name)?;
139 dest.write_char('(')?;
140 a.to_css(dest)?;
141 dest.write_char(')')
142}
143
144#[derive(Clone, Debug, Default, PartialEq)]
147pub struct Transform(pub Vec<TransformFn>);
148
149impl Transform {
150 pub fn new() -> Self {
152 Self::default()
153 }
154
155 pub fn push(mut self, f: TransformFn) -> Self {
157 self.0.push(f);
158 self
159 }
160}
161
162impl ToCss for Transform {
163 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
164 for (i, f) in self.0.iter().enumerate() {
165 if i > 0 {
166 dest.write_char(' ')?;
167 }
168 f.to_css(dest)?;
169 }
170 Ok(())
171 }
172}
173
174impl From<TransformFn> for Transform {
175 fn from(f: TransformFn) -> Self {
176 Self(vec![f])
177 }
178}
179
180impl<const N: usize> From<[TransformFn; N]> for Transform {
181 fn from(arr: [TransformFn; N]) -> Self {
182 Self(arr.into_iter().collect())
183 }
184}
185
186impl From<Vec<TransformFn>> for Transform {
187 fn from(v: Vec<TransformFn>) -> Self {
188 Self(v)
189 }
190}
191
192impl Css {
193 pub fn transform(self, t: impl Into<Transform>) -> Self {
197 self.push("transform", t.into())
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use crate::ext::*;
204 use crate::Css;
205
206 use super::*;
207
208 #[test]
209 fn single_transform_function() {
210 let s = Css::new().transform(TransformFn::TranslateX(px(10).into()));
211 assert_eq!(s.to_string(), "transform: translateX(10px);");
212 }
213
214 #[test]
215 fn chain_of_transforms() {
216 let s = Css::new().transform([
217 TransformFn::TranslateX(px(10).into()),
218 TransformFn::Rotate(45.deg()),
219 TransformFn::Scale(1.5, 2.0),
220 ]);
221 assert_eq!(
222 s.to_string(),
223 "transform: translateX(10px) rotate(45deg) scale(1.5, 2);"
224 );
225 }
226
227 #[test]
228 fn translate_variants() {
229 assert_eq!(
230 Css::new()
231 .transform(TransformFn::Translate(px(1).into(), px(2).into()))
232 .to_string(),
233 "transform: translate(1px, 2px);"
234 );
235 assert_eq!(
236 Css::new()
237 .transform(TransformFn::TranslateY(px(3).into()))
238 .to_string(),
239 "transform: translateY(3px);"
240 );
241 assert_eq!(
242 Css::new()
243 .transform(TransformFn::TranslateZ(px(4)))
244 .to_string(),
245 "transform: translateZ(4px);"
246 );
247 assert_eq!(
248 Css::new()
249 .transform(TransformFn::Translate3d(px(1).into(), px(2).into(), px(3),))
250 .to_string(),
251 "transform: translate3d(1px, 2px, 3px);"
252 );
253 }
254
255 #[test]
256 fn rotate_axes() {
257 let s = Css::new().transform([
258 TransformFn::RotateX(10.deg()),
259 TransformFn::RotateY(20.deg()),
260 TransformFn::RotateZ(30.deg()),
261 ]);
262 assert_eq!(
263 s.to_string(),
264 "transform: rotateX(10deg) rotateY(20deg) rotateZ(30deg);"
265 );
266 }
267
268 #[test]
269 fn scale_axes() {
270 let s = Css::new().transform([TransformFn::ScaleX(2.0), TransformFn::ScaleY(0.5)]);
271 assert_eq!(s.to_string(), "transform: scaleX(2) scaleY(0.5);");
272 }
273
274 #[test]
275 fn skew_variants() {
276 let s = Css::new().transform([
277 TransformFn::Skew(10.deg(), 20.deg()),
278 TransformFn::SkewX(5.deg()),
279 TransformFn::SkewY(15.deg()),
280 ]);
281 assert_eq!(
282 s.to_string(),
283 "transform: skew(10deg, 20deg) skewX(5deg) skewY(15deg);"
284 );
285 }
286
287 #[test]
288 fn matrix_six_values() {
289 let s = Css::new().transform(TransformFn::Matrix([1.0, 0.0, 0.0, 1.0, 10.0, 20.0]));
290 assert_eq!(s.to_string(), "transform: matrix(1, 0, 0, 1, 10, 20);");
291 }
292
293 #[test]
294 fn matrix3d_sixteen_values() {
295 let mut m = [0.0_f32; 16];
296 m[0] = 1.0;
297 m[5] = 1.0;
298 m[10] = 1.0;
299 m[15] = 1.0;
300 let s = Css::new().transform(TransformFn::Matrix3d(m));
301 assert_eq!(
302 s.to_string(),
303 "transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);"
304 );
305 }
306
307 #[test]
308 fn transform_from_vec_and_array() {
309 let from_vec = Css::new().transform(vec![TransformFn::Rotate(10.deg())]);
310 let from_arr = Css::new().transform([TransformFn::Rotate(10.deg())]);
311 assert_eq!(from_vec.to_string(), from_arr.to_string());
312 }
313
314 #[test]
315 fn transform_builder_pattern() {
316 let t = Transform::new()
317 .push(TransformFn::TranslateX(px(10).into()))
318 .push(TransformFn::Rotate(45.deg()));
319 let s = Css::new().transform(t);
320 assert_eq!(s.to_string(), "transform: translateX(10px) rotate(45deg);");
321 }
322}