Skip to main content

deckcraft_model/
anim.rs

1//! Slide transitions and object animations.
2//!
3//! Animations are kept as the flat, ordered list the Animation Pane shows; the PPTX writer builds
4//! the nested timing tree from it and the reader flattens files back into it.
5
6use serde::{Deserialize, Serialize};
7
8use crate::ShapeId;
9
10#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
11#[serde(default, rename_all = "camelCase")]
12pub struct Transition {
13    /// Effect id: `none`, `fade`, `push`, `wipe`, `split`, `reveal`, `cut`, `randomBar`, `shape`,
14    /// `uncover`, `cover`, `flash`, `morph`, `dissolve`, `checker`, `blinds`, `clock`, `zoom`… (see
15    /// [`TRANSITIONS`]).
16    pub kind: String,
17    /// Effect option, e.g. direction `l`, `r`, `u`, `d`, `horz`, `vert`, `in`, `out`.
18    pub option: String,
19    pub duration_ms: u32,
20    pub advance_on_click: bool,
21    pub advance_after_ms: Option<u32>,
22    /// Embedded sound media.
23    pub sound: Option<crate::MediaId>,
24    /// Unmodelled transition XML kept for round-trip.
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub raw: Option<String>,
27}
28
29impl Default for Transition {
30    fn default() -> Self {
31        Transition {
32            kind: "none".into(),
33            option: String::new(),
34            duration_ms: 1000,
35            advance_on_click: true,
36            advance_after_ms: None,
37            sound: None,
38            raw: None,
39        }
40    }
41}
42
43/// (id, label, category, default duration ms, effect options)
44pub static TRANSITIONS: &[(&str, &str, &str, u32, &[&str])] = &[
45    ("none", "None", "Subtle", 0, &[]),
46    (
47        "morph",
48        "Morph",
49        "Subtle",
50        2000,
51        &["objects", "words", "characters"],
52    ),
53    ("fade", "Fade", "Subtle", 700, &["smoothly", "throughBlack"]),
54    ("push", "Push", "Subtle", 1000, &["u", "d", "l", "r"]),
55    (
56        "wipe",
57        "Wipe",
58        "Subtle",
59        1000,
60        &["r", "l", "u", "d", "ru", "lu", "rd", "ld"],
61    ),
62    (
63        "split",
64        "Split",
65        "Subtle",
66        1500,
67        &["vertOut", "vertIn", "horzOut", "horzIn"],
68    ),
69    (
70        "reveal",
71        "Reveal",
72        "Subtle",
73        2000,
74        &["smoothlyLeft", "smoothlyRight", "blackLeft", "blackRight"],
75    ),
76    ("cut", "Cut", "Subtle", 0, &["", "throughBlack"]),
77    (
78        "randomBar",
79        "Random Bars",
80        "Subtle",
81        1000,
82        &["vert", "horz"],
83    ),
84    (
85        "shape",
86        "Shape",
87        "Subtle",
88        2000,
89        &["circle", "diamond", "plus", "in", "out"],
90    ),
91    (
92        "uncover",
93        "Uncover",
94        "Subtle",
95        1000,
96        &["l", "r", "u", "d", "lu", "ru", "ld", "rd"],
97    ),
98    (
99        "cover",
100        "Cover",
101        "Subtle",
102        1000,
103        &["l", "r", "u", "d", "lu", "ru", "ld", "rd"],
104    ),
105    ("flash", "Flash", "Subtle", 1000, &[]),
106    ("fallOver", "Fall Over", "Exciting", 2000, &["l", "r"]),
107    ("drape", "Drape", "Exciting", 2000, &["l", "r"]),
108    ("curtains", "Curtains", "Exciting", 2000, &[]),
109    ("wind", "Wind", "Exciting", 2000, &["l", "r"]),
110    ("prestige", "Prestige", "Exciting", 2000, &[]),
111    ("fracture", "Fracture", "Exciting", 2000, &[]),
112    ("crush", "Crush", "Exciting", 2000, &[]),
113    ("peelOff", "Peel Off", "Exciting", 1500, &["l", "r"]),
114    ("pageCurlDouble", "Page Curl", "Exciting", 1500, &["l", "r"]),
115    ("airplane", "Airplane", "Exciting", 2000, &["l", "r"]),
116    ("origami", "Origami", "Exciting", 2000, &["l", "r"]),
117    ("dissolve", "Dissolve", "Exciting", 1200, &[]),
118    (
119        "checker",
120        "Checkerboard",
121        "Exciting",
122        1500,
123        &["horz", "vert"],
124    ),
125    ("blinds", "Blinds", "Exciting", 1600, &["vert", "horz"]),
126    (
127        "clock",
128        "Clock",
129        "Exciting",
130        2000,
131        &["clockwise", "counterClockwise", "wedge"],
132    ),
133    (
134        "ripple",
135        "Ripple",
136        "Exciting",
137        1400,
138        &["center", "lu", "ru", "ld", "rd"],
139    ),
140    ("honeycomb", "Honeycomb", "Exciting", 3000, &[]),
141    (
142        "glitter",
143        "Glitter",
144        "Exciting",
145        2500,
146        &["l", "r", "u", "d"],
147    ),
148    ("vortex", "Vortex", "Exciting", 3000, &["l", "r", "u", "d"]),
149    ("shred", "Shred", "Exciting", 2000, &["strips", "particles"]),
150    ("switch", "Switch", "Exciting", 1250, &["l", "r"]),
151    ("flip", "Flip", "Exciting", 1250, &["l", "r"]),
152    ("gallery", "Gallery", "Exciting", 1500, &["l", "r"]),
153    ("cube", "Cube", "Exciting", 1250, &["l", "r", "u", "d"]),
154    ("doors", "Doors", "Exciting", 1400, &["vert", "horz"]),
155    ("box", "Box", "Exciting", 1250, &["l", "r", "u", "d"]),
156    ("comb", "Comb", "Exciting", 1000, &["horz", "vert"]),
157    ("zoom", "Zoom", "Exciting", 1000, &["in", "out"]),
158    ("random", "Random", "Exciting", 2000, &[]),
159    ("pan", "Pan", "Dynamic Content", 1000, &["u", "d", "l", "r"]),
160    (
161        "ferris",
162        "Ferris Wheel",
163        "Dynamic Content",
164        2000,
165        &["l", "r"],
166    ),
167    ("conveyor", "Conveyor", "Dynamic Content", 2000, &["l", "r"]),
168    (
169        "rotate",
170        "Rotate",
171        "Dynamic Content",
172        2000,
173        &["l", "r", "u", "d"],
174    ),
175    (
176        "window",
177        "Window",
178        "Dynamic Content",
179        1250,
180        &["vert", "horz"],
181    ),
182    (
183        "orbit",
184        "Orbit",
185        "Dynamic Content",
186        2000,
187        &["l", "r", "u", "d"],
188    ),
189    (
190        "flythrough",
191        "Fly Through",
192        "Dynamic Content",
193        1000,
194        &["in", "out", "inBounce", "outBounce"],
195    ),
196];
197
198#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
199#[serde(rename_all = "camelCase")]
200pub enum AnimClass {
201    #[default]
202    Entrance,
203    Emphasis,
204    Exit,
205    Path,
206    Media,
207}
208
209impl AnimClass {
210    pub fn xml(self) -> &'static str {
211        match self {
212            AnimClass::Entrance => "entr",
213            AnimClass::Emphasis => "emph",
214            AnimClass::Exit => "exit",
215            AnimClass::Path => "path",
216            AnimClass::Media => "mediacall",
217        }
218    }
219    pub fn from_xml(s: &str) -> Self {
220        match s {
221            "emph" => AnimClass::Emphasis,
222            "exit" => AnimClass::Exit,
223            "path" => AnimClass::Path,
224            "mediacall" => AnimClass::Media,
225            _ => AnimClass::Entrance,
226        }
227    }
228}
229
230#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
231#[serde(rename_all = "camelCase")]
232pub enum AnimStart {
233    #[default]
234    OnClick,
235    WithPrevious,
236    AfterPrevious,
237}
238
239/// Text build: as one object, all at once by paragraph, or by paragraph level.
240#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
241#[serde(rename_all = "camelCase")]
242pub enum TextBuild {
243    #[default]
244    AsOne,
245    ByParagraph,
246    ByWord,
247    ByLetter,
248}
249
250#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
251#[serde(default, rename_all = "camelCase")]
252pub struct Animation {
253    pub shape: ShapeId,
254    pub class: AnimClass,
255    /// Effect id from [`ANIMATIONS`] (e.g. `fade`, `fly`, `zoom`, `spin`, `pulse`, `lines`…).
256    pub effect: String,
257    /// Effect option (direction etc.).
258    pub option: String,
259    pub start: AnimStart,
260    pub duration_ms: u32,
261    pub delay_ms: u32,
262    /// Repeat count (0/1 = once); `u32::MAX` = until next click.
263    pub repeat: u32,
264    pub rewind: bool,
265    pub text_build: TextBuild,
266    /// Paragraph index when the effect animates one paragraph of a text body.
267    pub paragraph: Option<u32>,
268    /// Motion path in slide-relative units (SVG-like `M 0 0 L 0.25 0 E`).
269    pub path: Option<String>,
270    /// Triggered by a click on another shape.
271    pub trigger: Option<ShapeId>,
272    /// Emphasis amount: spin degrees, grow/shrink scale (1.5 = 150%), transparency…
273    pub amount: Option<f64>,
274    /// Emphasis colour (fill/line/font colour change).
275    pub color: Option<crate::style::ColorRef>,
276    /// PowerPoint preset id/subtype read from or written to files.
277    pub preset_id: Option<u32>,
278    pub preset_subtype: Option<u32>,
279    /// After animation: dim to colour, hide, hide on next click.
280    pub after: Option<String>,
281    pub sound: Option<crate::MediaId>,
282    pub smooth_start: f64,
283    pub smooth_end: f64,
284    pub bounce_end: f64,
285    pub auto_reverse: bool,
286}
287
288impl Default for Animation {
289    fn default() -> Self {
290        Animation {
291            shape: ShapeId(0),
292            class: AnimClass::Entrance,
293            effect: "fade".into(),
294            option: String::new(),
295            start: AnimStart::OnClick,
296            duration_ms: 500,
297            delay_ms: 0,
298            repeat: 1,
299            rewind: false,
300            text_build: TextBuild::AsOne,
301            paragraph: None,
302            path: None,
303            trigger: None,
304            amount: None,
305            color: None,
306            preset_id: None,
307            preset_subtype: None,
308            after: None,
309            sound: None,
310            smooth_start: 0.0,
311            smooth_end: 0.0,
312            bounce_end: 0.0,
313            auto_reverse: false,
314        }
315    }
316}
317
318/// (id, label, class, PowerPoint preset id, default duration ms, options)
319pub static ANIMATIONS: &[(&str, &str, AnimClass, u32, u32, &[&str])] = &[
320    ("appear", "Appear", AnimClass::Entrance, 1, 0, &[]),
321    ("fade", "Fade", AnimClass::Entrance, 10, 500, &[]),
322    (
323        "fly",
324        "Fly In",
325        AnimClass::Entrance,
326        2,
327        500,
328        &["b", "lb", "l", "lt", "t", "rt", "r", "rb"],
329    ),
330    (
331        "float",
332        "Float In",
333        AnimClass::Entrance,
334        42,
335        1000,
336        &["u", "d"],
337    ),
338    (
339        "split",
340        "Split",
341        AnimClass::Entrance,
342        16,
343        500,
344        &["horzIn", "horzOut", "vertIn", "vertOut"],
345    ),
346    (
347        "wipe",
348        "Wipe",
349        AnimClass::Entrance,
350        22,
351        500,
352        &["b", "l", "t", "r"],
353    ),
354    (
355        "shape",
356        "Shape",
357        AnimClass::Entrance,
358        6,
359        2000,
360        &[
361            "circleIn",
362            "circleOut",
363            "boxIn",
364            "boxOut",
365            "diamondIn",
366            "diamondOut",
367            "plusIn",
368            "plusOut",
369        ],
370    ),
371    (
372        "wheel",
373        "Wheel",
374        AnimClass::Entrance,
375        21,
376        2000,
377        &["1", "2", "3", "4", "8"],
378    ),
379    (
380        "randomBars",
381        "Random Bars",
382        AnimClass::Entrance,
383        14,
384        500,
385        &["horz", "vert"],
386    ),
387    ("grow", "Grow & Turn", AnimClass::Entrance, 31, 500, &[]),
388    ("zoom", "Zoom", AnimClass::Entrance, 53, 500, &["in", "out"]),
389    (
390        "swivel",
391        "Swivel",
392        AnimClass::Entrance,
393        45,
394        2000,
395        &["horz", "vert"],
396    ),
397    ("bounce", "Bounce", AnimClass::Entrance, 26, 2000, &[]),
398    (
399        "blinds",
400        "Blinds",
401        AnimClass::Entrance,
402        3,
403        500,
404        &["horz", "vert"],
405    ),
406    ("box", "Box", AnimClass::Entrance, 4, 500, &["in", "out"]),
407    (
408        "checkerboard",
409        "Checkerboard",
410        AnimClass::Entrance,
411        5,
412        500,
413        &["across", "down"],
414    ),
415    (
416        "circle",
417        "Circle",
418        AnimClass::Entrance,
419        6,
420        500,
421        &["in", "out"],
422    ),
423    (
424        "diamond",
425        "Diamond",
426        AnimClass::Entrance,
427        8,
428        500,
429        &["in", "out"],
430    ),
431    ("dissolve", "Dissolve In", AnimClass::Entrance, 9, 500, &[]),
432    (
433        "peek",
434        "Peek In",
435        AnimClass::Entrance,
436        12,
437        500,
438        &["b", "l", "t", "r"],
439    ),
440    (
441        "strips",
442        "Strips",
443        AnimClass::Entrance,
444        18,
445        500,
446        &["lu", "ru", "ld", "rd"],
447    ),
448    ("expand", "Expand", AnimClass::Entrance, 55, 500, &[]),
449    ("rise", "Rise Up", AnimClass::Entrance, 37, 1000, &[]),
450    ("spinner", "Spinner", AnimClass::Entrance, 49, 1000, &[]),
451    ("pulse", "Pulse", AnimClass::Emphasis, 26, 500, &[]),
452    (
453        "colorPulse",
454        "Color Pulse",
455        AnimClass::Emphasis,
456        27,
457        500,
458        &[],
459    ),
460    ("teeter", "Teeter", AnimClass::Emphasis, 32, 1000, &[]),
461    (
462        "spin",
463        "Spin",
464        AnimClass::Emphasis,
465        8,
466        2000,
467        &["clockwise", "counterClockwise"],
468    ),
469    (
470        "growShrink",
471        "Grow/Shrink",
472        AnimClass::Emphasis,
473        6,
474        2000,
475        &["both", "horz", "vert"],
476    ),
477    (
478        "desaturate",
479        "Desaturate",
480        AnimClass::Emphasis,
481        35,
482        500,
483        &[],
484    ),
485    ("darken", "Darken", AnimClass::Emphasis, 25, 500, &[]),
486    ("lighten", "Lighten", AnimClass::Emphasis, 30, 500, &[]),
487    (
488        "transparency",
489        "Transparency",
490        AnimClass::Emphasis,
491        9,
492        500,
493        &[],
494    ),
495    (
496        "objectColor",
497        "Object Color",
498        AnimClass::Emphasis,
499        19,
500        2000,
501        &[],
502    ),
503    (
504        "complementaryColor",
505        "Complementary Color",
506        AnimClass::Emphasis,
507        21,
508        500,
509        &[],
510    ),
511    ("lineColor", "Line Color", AnimClass::Emphasis, 7, 2000, &[]),
512    ("fillColor", "Fill Color", AnimClass::Emphasis, 1, 2000, &[]),
513    ("fontColor", "Font Color", AnimClass::Emphasis, 3, 2000, &[]),
514    ("underline", "Underline", AnimClass::Emphasis, 18, 500, &[]),
515    ("boldFlash", "Bold Flash", AnimClass::Emphasis, 10, 500, &[]),
516    ("wave", "Wave", AnimClass::Emphasis, 34, 500, &[]),
517    ("disappear", "Disappear", AnimClass::Exit, 1, 0, &[]),
518    ("fadeOut", "Fade", AnimClass::Exit, 10, 500, &[]),
519    (
520        "flyOut",
521        "Fly Out",
522        AnimClass::Exit,
523        2,
524        500,
525        &["b", "lb", "l", "lt", "t", "rt", "r", "rb"],
526    ),
527    (
528        "floatOut",
529        "Float Out",
530        AnimClass::Exit,
531        42,
532        1000,
533        &["u", "d"],
534    ),
535    (
536        "splitOut",
537        "Split",
538        AnimClass::Exit,
539        16,
540        500,
541        &["horzIn", "horzOut", "vertIn", "vertOut"],
542    ),
543    (
544        "wipeOut",
545        "Wipe",
546        AnimClass::Exit,
547        22,
548        500,
549        &["b", "l", "t", "r"],
550    ),
551    (
552        "shapeOut",
553        "Shape",
554        AnimClass::Exit,
555        6,
556        2000,
557        &["circleIn", "circleOut"],
558    ),
559    (
560        "wheelOut",
561        "Wheel",
562        AnimClass::Exit,
563        21,
564        2000,
565        &["1", "2", "3", "4", "8"],
566    ),
567    (
568        "randomBarsOut",
569        "Random Bars",
570        AnimClass::Exit,
571        14,
572        500,
573        &["horz", "vert"],
574    ),
575    ("shrinkTurn", "Shrink & Turn", AnimClass::Exit, 31, 500, &[]),
576    ("zoomOut", "Zoom", AnimClass::Exit, 53, 500, &["in", "out"]),
577    (
578        "swivelOut",
579        "Swivel",
580        AnimClass::Exit,
581        45,
582        2000,
583        &["horz", "vert"],
584    ),
585    ("bounceOut", "Bounce", AnimClass::Exit, 26, 2000, &[]),
586    ("dissolveOut", "Dissolve Out", AnimClass::Exit, 9, 500, &[]),
587    (
588        "lines",
589        "Lines",
590        AnimClass::Path,
591        64,
592        2000,
593        &["down", "up", "right", "left"],
594    ),
595    (
596        "arcs",
597        "Arcs",
598        AnimClass::Path,
599        37,
600        2000,
601        &["down", "up", "right", "left"],
602    ),
603    (
604        "turns",
605        "Turns",
606        AnimClass::Path,
607        43,
608        2000,
609        &["down", "up", "right", "left"],
610    ),
611    (
612        "shapes",
613        "Shapes",
614        AnimClass::Path,
615        1,
616        2000,
617        &[
618            "circle",
619            "diamond",
620            "hexagon",
621            "triangle",
622            "square",
623            "trapezoid",
624            "octagon",
625            "parallelogram",
626            "pentagon",
627        ],
628    ),
629    ("loops", "Loops", AnimClass::Path, 61, 2000, &[]),
630    ("customPath", "Custom Path", AnimClass::Path, 0, 2000, &[]),
631    ("play", "Play", AnimClass::Media, 1, 0, &[]),
632    ("pause", "Pause", AnimClass::Media, 2, 0, &[]),
633    ("stop", "Stop", AnimClass::Media, 3, 0, &[]),
634];
635
636pub fn animation_info(
637    effect: &str,
638    class: AnimClass,
639) -> Option<&'static (
640    &'static str,
641    &'static str,
642    AnimClass,
643    u32,
644    u32,
645    &'static [&'static str],
646)> {
647    ANIMATIONS
648        .iter()
649        .find(|a| a.0 == effect && a.2 == class)
650        .or_else(|| ANIMATIONS.iter().find(|a| a.0 == effect))
651}