1#[macro_export]
70macro_rules! motion {
71 ( enter : $kind:ident $distance:expr ; $($rest:tt)* ) => {
72 $crate::__motion!(
73 @m $crate::anim::Motion::enter_from($crate::__kind!($kind), $distance as f32)
74 ; $($rest)*
75 )
76 };
77 ( enter : $kind:ident ; $($rest:tt)* ) => {
78 $crate::__motion!(@m $crate::anim::Motion::enter($crate::__kind!($kind)) ; $($rest)*)
79 };
80 ( exit : $kind:ident $distance:expr ; $($rest:tt)* ) => {
81 $crate::__motion!(
82 @m $crate::anim::Motion::exit_to($crate::__kind!($kind), $distance as f32)
83 ; $($rest)*
84 )
85 };
86 ( exit : $kind:ident ; $($rest:tt)* ) => {
87 $crate::__motion!(@m $crate::anim::Motion::exit($crate::__kind!($kind)) ; $($rest)*)
88 };
89 ( $($decls:tt)* ) => {
90 $crate::__motion!(@m $crate::anim::Motion::new() ; $($decls)*)
91 };
92}
93
94#[macro_export]
114macro_rules! sequence {
115 ( $($items:tt)* ) => {
116 $crate::__sequence!(@s $crate::anim::Sequence::new() ; $($items)* ,)
119 };
120}
121
122#[macro_export]
123#[doc(hidden)]
124macro_rules! __motion {
125 (@m $m:expr ;) => { $m };
126
127 (@m $m:expr ; duration : $v:expr ; $($r:tt)*) => {
129 $crate::__motion!(@m $m.duration($v as f32) ; $($r)*)
130 };
131 (@m $m:expr ; delay : $v:expr ; $($r:tt)*) => {
132 $crate::__motion!(@m $m.delay($v as f32) ; $($r)*)
133 };
134 (@m $m:expr ; end_delay : $v:expr ; $($r:tt)*) => {
135 $crate::__motion!(@m $m.end_delay($v as f32) ; $($r)*)
136 };
137
138 (@m $m:expr ; ease : linear ; $($r:tt)*) => {
141 $crate::__motion!(@m $m.ease($crate::Easing::Linear) ; $($r)*)
142 };
143 (@m $m:expr ; ease : spring ; $($r:tt)*) => {
144 $crate::__motion!(@m $m.ease($crate::Easing::Spring($crate::Spring::default())) ; $($r)*)
145 };
146 (@m $m:expr ; ease : steps($n:expr) ; $($r:tt)*) => {
147 $crate::__motion!(@m $m.ease($crate::Easing::Steps($n as u32)) ; $($r)*)
148 };
149 (@m $m:expr ; ease : in_out $c:ident ; $($r:tt)*) => {
150 $crate::__motion!(@m $m.ease($crate::Easing::InOut($crate::__curve!($c))) ; $($r)*)
151 };
152 (@m $m:expr ; ease : in $c:ident ; $($r:tt)*) => {
153 $crate::__motion!(@m $m.ease($crate::Easing::In($crate::__curve!($c))) ; $($r)*)
154 };
155 (@m $m:expr ; ease : out $c:ident ; $($r:tt)*) => {
156 $crate::__motion!(@m $m.ease($crate::Easing::Out($crate::__curve!($c))) ; $($r)*)
157 };
158 (@m $m:expr ; ease : $e:expr ; $($r:tt)*) => {
159 $crate::__motion!(@m $m.ease($e) ; $($r)*)
160 };
161
162 (@m $m:expr ; repeat : forever ; $($r:tt)*) => {
164 $crate::__motion!(@m $m.repeat_forever() ; $($r)*)
165 };
166 (@m $m:expr ; repeat : once ; $($r:tt)*) => {
167 $crate::__motion!(@m $m.repeat(1) ; $($r)*)
168 };
169 (@m $m:expr ; repeat : $n:expr ; $($r:tt)*) => {
170 $crate::__motion!(@m $m.repeat($n as u32) ; $($r)*)
171 };
172 (@m $m:expr ; alternate ; $($r:tt)*) => {
173 $crate::__motion!(@m $m.alternate(true) ; $($r)*)
174 };
175 (@m $m:expr ; reversed ; $($r:tt)*) => {
176 $crate::__motion!(@m $m.reversed(true) ; $($r)*)
177 };
178 (@m $m:expr ; margins ; $($r:tt)*) => {
179 $crate::__motion!(@m $m.as_margins() ; $($r)*)
180 };
181
182 (@m $m:expr ; custom($n:literal) : $from:expr => [ $($k:expr),* $(,)? ] ; $($r:tt)*) => {
185 $crate::__motion!(@m $m.keyframes($crate::Prop::Custom($n), $from, [$($k),*]) ; $($r)*)
186 };
187 (@m $m:expr ; custom($n:literal) : $from:expr => $to:expr ; $($r:tt)*) => {
188 $crate::__motion!(@m $m.tween($crate::Prop::Custom($n), $from, $to) ; $($r)*)
189 };
190 (@m $m:expr ; $p:ident : $from:expr => [ $($k:expr),* $(,)? ] ; $($r:tt)*) => {
191 $crate::__motion!(@m $m.keyframes($crate::__prop!($p), $from, [$($k),*]) ; $($r)*)
192 };
193 (@m $m:expr ; $p:ident : $from:expr => $to:expr ; $($r:tt)*) => {
194 $crate::__motion!(@m $m.tween($crate::__prop!($p), $from, $to) ; $($r)*)
195 };
196}
197
198#[macro_export]
199#[doc(hidden)]
200macro_rules! __sequence {
201 (@s $s:expr ;) => { $s };
202 (@s $s:expr ; ,) => { $s };
203
204 (@s $s:expr ; rel($v:expr) => $m:expr , $($r:tt)*) => {
205 $crate::__sequence!(@s $s.add_at($m, $crate::At::Rel($v as f32)) ; $($r)*)
206 };
207 (@s $s:expr ; abs($v:expr) => $m:expr , $($r:tt)*) => {
208 $crate::__sequence!(@s $s.add_at($m, $crate::At::Abs($v as f32)) ; $($r)*)
209 };
210 (@s $s:expr ; with($v:expr) => $m:expr , $($r:tt)*) => {
211 $crate::__sequence!(@s $s.add_at($m, $crate::At::With($v as f32)) ; $($r)*)
212 };
213 (@s $s:expr ; label($n:expr, $v:expr) => $m:expr , $($r:tt)*) => {
214 $crate::__sequence!(@s $s.add_at($m, $crate::At::Label($n.into(), $v as f32)) ; $($r)*)
215 };
216 (@s $s:expr ; $m:expr , $($r:tt)*) => {
217 $crate::__sequence!(@s $s.add($m) ; $($r)*)
218 };
219}
220
221#[macro_export]
223#[doc(hidden)]
224macro_rules! __kind {
225 (fade) => {
226 $crate::TransitionKind::Fade
227 };
228 (slide_up) => {
229 $crate::TransitionKind::SlideUp
230 };
231 (slide_down) => {
232 $crate::TransitionKind::SlideDown
233 };
234 (slide_left) => {
235 $crate::TransitionKind::SlideLeft
236 };
237 (slide_right) => {
238 $crate::TransitionKind::SlideRight
239 };
240}
241
242#[macro_export]
244#[doc(hidden)]
245macro_rules! __curve {
246 (quad) => {
247 $crate::Curve::Quad
248 };
249 (cubic) => {
250 $crate::Curve::Cubic
251 };
252 (quart) => {
253 $crate::Curve::Quart
254 };
255 (quint) => {
256 $crate::Curve::Quint
257 };
258 (sine) => {
259 $crate::Curve::Sine
260 };
261 (expo) => {
262 $crate::Curve::Expo
263 };
264 (circ) => {
265 $crate::Curve::Circ
266 };
267 (back) => {
268 $crate::Curve::Back
269 };
270 (elastic) => {
271 $crate::Curve::Elastic
272 };
273 (bounce) => {
274 $crate::Curve::Bounce
275 };
276}
277
278#[macro_export]
280#[doc(hidden)]
281macro_rules! __prop {
282 (opacity) => {
283 $crate::Prop::Opacity
284 };
285 (x) => {
286 $crate::Prop::X
287 };
288 (y) => {
289 $crate::Prop::Y
290 };
291 (w) => {
292 $crate::Prop::Width
293 };
294 (width) => {
295 $crate::Prop::Width
296 };
297 (h) => {
298 $crate::Prop::Height
299 };
300 (height) => {
301 $crate::Prop::Height
302 };
303 (mt) => {
304 $crate::Prop::MarginTop
305 };
306 (mr) => {
307 $crate::Prop::MarginRight
308 };
309 (mb) => {
310 $crate::Prop::MarginBottom
311 };
312 (ml) => {
313 $crate::Prop::MarginLeft
314 };
315 (pt) => {
316 $crate::Prop::PadTop
317 };
318 (pr) => {
319 $crate::Prop::PadRight
320 };
321 (pb) => {
322 $crate::Prop::PadBottom
323 };
324 (pl) => {
325 $crate::Prop::PadLeft
326 };
327 (radius) => {
328 $crate::Prop::Radius
329 };
330 (border_width) => {
331 $crate::Prop::BorderWidth
332 };
333 (gap) => {
334 $crate::Prop::Gap
335 };
336 (font_size) => {
337 $crate::Prop::FontSize
338 };
339 (bg) => {
340 $crate::Prop::Background
341 };
342 (background) => {
343 $crate::Prop::Background
344 };
345 (border_color) => {
346 $crate::Prop::BorderColor
347 };
348 (color) => {
349 $crate::Prop::TextColor
350 };
351 (rotate) => {
352 $crate::Prop::Rotate
353 };
354 (scale) => {
355 $crate::Prop::Scale
356 };
357 (custom($n:literal)) => {
358 $crate::Prop::Custom($n)
359 };
360}
361
362#[cfg(test)]
363mod tests {
364 use crate::anim::{Loop, Motion, Prop, Sequence};
365 use crate::Easing;
366
367 #[test]
370 fn every_declaration_expands() {
371 let m = motion! {
372 duration: 420;
373 delay: 80;
374 end_delay: 120;
375 ease: out back;
376 repeat: 3;
377 alternate;
378 reversed;
379 margins;
380 opacity: 0 => 1;
381 radius: 6 => 24;
382 };
383 assert_eq!(m.duration, 420.0);
384 assert_eq!(m.delay, 80.0);
385 assert_eq!(m.end_delay, 120.0);
386 assert_eq!(m.ease, Easing::Out(crate::Curve::Back));
387 assert_eq!(m.loops, Loop::Times(3));
388 assert!(m.alternate && m.reversed);
389 }
390
391 #[test]
392 fn every_easing_spelling_expands() {
393 let curves = [
394 motion! { ease: linear; },
395 motion! { ease: spring; },
396 motion! { ease: steps(4); },
397 motion! { ease: in quad; },
398 motion! { ease: out cubic; },
399 motion! { ease: in_out sine; },
400 motion! { ease: Easing::CubicBezier(0.25, 0.1, 0.25, 1.0); },
401 motion! { ease: in quart; },
402 motion! { ease: out quint; },
403 motion! { ease: in expo; },
404 motion! { ease: out circ; },
405 motion! { ease: out elastic; },
406 motion! { ease: out bounce; },
407 ];
408 assert_eq!(curves[0].ease, Easing::Linear);
409 assert_eq!(curves[2].ease, Easing::Steps(4));
410 assert_eq!(curves[4].ease, Easing::Out(crate::Curve::Cubic));
411 }
412
413 #[test]
414 fn presets_pick_the_constructor() {
415 let m = motion! { enter: slide_up; duration: 300; };
416 assert_eq!(m.duration, 300.0);
417 assert_eq!(m.sample(0.0).number(Prop::Opacity), Some(0.0));
418 assert_eq!(m.sample(300.0).number(Prop::Y), Some(0.0));
419
420 let far = motion! { enter: slide_left 24; };
422 assert_eq!(far.sample(0.0).number(Prop::X), Some(24.0));
423 let out = motion! { exit: fade; };
424 assert_eq!(out.sample(0.0).number(Prop::Opacity), Some(1.0));
425 let _ = motion! { exit: slide_down 12; };
426 let _ = motion! { enter: fade; };
427 let _ = motion! { enter: slide_right; };
428 }
429
430 #[test]
431 fn a_leg_list_takes_values_or_keyframes() {
432 let plain = motion! {
433 duration: 300;
434 ease: linear;
435 y: 0 => [-30, 0];
436 };
437 assert_eq!(plain.iteration_ms(), 300.0);
438 assert_eq!(plain.sample(150.0).number(Prop::Y), Some(-30.0));
439
440 let timed = motion! {
442 duration: 300;
443 ease: linear;
444 y: 0 => [crate::Keyframe::to(-30.0).duration(100.0), crate::Keyframe::to(0.0)];
445 };
446 assert_eq!(timed.sample(100.0).number(Prop::Y), Some(-30.0));
447 }
448
449 #[test]
450 fn colours_tween_through_the_macro() {
451 let m = motion! {
452 duration: 200;
453 bg: color!("#111111") => color!("#333333");
454 color: color!(teal) => color!(orchid);
455 border_color: color!("#000000") => color!("#ffffff");
456 };
457 assert!(m.sample(0.0).color(Prop::Background).is_some());
458 assert!(m.sample(200.0).color(Prop::TextColor).is_some());
459 }
460
461 #[test]
462 fn every_prop_word_maps() {
463 let m = motion! {
464 opacity: 0 => 1;
465 x: 0 => 1;
466 y: 0 => 1;
467 w: 0 => 1;
468 width: 0 => 1;
469 h: 0 => 1;
470 height: 0 => 1;
471 mt: 0 => 1;
472 mr: 0 => 1;
473 mb: 0 => 1;
474 ml: 0 => 1;
475 pt: 0 => 1;
476 pr: 0 => 1;
477 pb: 0 => 1;
478 pl: 0 => 1;
479 radius: 0 => 1;
480 border_width: 0 => 1;
481 gap: 0 => 1;
482 font_size: 0 => 1;
483 rotate: 0 => 1;
484 scale: 0 => 1;
485 custom("progress"): 0 => 1;
486 custom("legs"): 0 => [2, 4];
487 };
488 assert_eq!(m.tracks.len(), 23);
490 assert_eq!(m.sample(m.total_ms()).number(Prop::Rotate), Some(1.0));
491 assert_eq!(
492 m.sample(m.total_ms()).number(Prop::Custom("progress")),
493 Some(1.0)
494 );
495 assert_eq!(
496 m.sample(m.total_ms()).number(Prop::Custom("legs")),
497 Some(4.0)
498 );
499 }
500
501 #[test]
504 fn the_spellings_tailor_generates_all_parse() {
505 let m = motion! {
506 enter: slide_up 16.;
507 duration: 400.;
508 delay: 60.;
509 ease: in_out sine;
510 repeat: forever;
511 alternate;
512 margins;
513 };
514 assert_eq!(m.duration, 400.0);
515 assert_eq!(m.delay, 60.0);
516 assert_eq!(m.loops, Loop::Forever);
517 assert_eq!(m.sample(0.0).number(Prop::MarginTop), Some(16.0));
519 let _ = motion! { enter: fade; duration: 260.; ease: out cubic; };
520 }
521
522 #[test]
523 fn repeat_words_and_counts() {
524 assert_eq!(motion! { repeat: once; }.loops, Loop::Times(1));
525 assert_eq!(motion! { repeat: forever; }.loops, Loop::Forever);
526 assert_eq!(motion! { repeat: 5; }.loops, Loop::Times(5));
527 }
528
529 #[test]
530 fn an_empty_block_is_a_default_motion() {
531 assert_eq!(motion! {}, Motion::new());
532 }
533
534 #[test]
535 fn the_block_still_chains() {
536 let m = motion! { duration: 100; opacity: 0 => 1; }
539 .repeat(2)
540 .alternate(true);
541 assert_eq!(m.total_ms(), 200.0);
542 }
543
544 fn leg(from: f32, to: f32) -> Motion {
545 motion! { duration: 100; ease: linear; x: from => to; }
546 }
547
548 #[test]
549 fn sequences_queue_and_place() {
550 let s = sequence![leg(0.0, 10.0), leg(10.0, 20.0)];
551 assert_eq!(s.len(), 2);
552 assert_eq!(s.iteration_ms(), 200.0);
553
554 let overlapped = sequence![
555 leg(0.0, 10.0),
556 rel(-50) => leg(10.0, 20.0),
557 with(0) => leg(20.0, 30.0),
558 abs(400) => leg(30.0, 40.0),
559 ];
560 assert_eq!(overlapped.len(), 4);
561 assert_eq!(overlapped.iteration_ms(), 500.0);
562 }
563
564 #[test]
565 fn a_label_anchors_a_sequence_entry() {
566 let placed = Sequence::new()
567 .add(leg(0.0, 10.0))
568 .label("settled", crate::At::End);
569 let s = sequence![label("settled", 50) => leg(10.0, 20.0)];
570 assert_eq!(s.len(), 1);
573 assert_eq!(
574 placed.resolve(&crate::At::Label("settled".into(), 0.0)),
575 100.0
576 );
577 }
578
579 #[test]
580 fn a_trailing_comma_is_fine_either_way() {
581 assert_eq!(sequence![leg(0.0, 1.0)].len(), 1);
582 assert_eq!(sequence![leg(0.0, 1.0),].len(), 1);
583 assert_eq!(sequence![].len(), 0);
584 }
585}