//! Slide transitions and object animations. //! //! Animations are kept as the flat, ordered list the Animation Pane shows; the PPTX writer builds //! the nested timing tree from it and the reader flattens files back into it. use serde::{Deserialize, Serialize}; use crate::ShapeId; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(default, rename_all = "camelCase")] pub struct Transition { /// Effect id: `none`, `fade`, `push`, `wipe`, `split`, `reveal`, `cut`, `randomBar`, `shape`, /// `uncover`, `cover`, `flash`, `morph`, `dissolve`, `checker`, `blinds`, `clock`, `zoom`… (see /// [`TRANSITIONS`]). pub kind: String, /// Effect option, e.g. direction `l`, `r`, `u`, `d`, `horz`, `vert`, `in`, `out`. pub option: String, pub duration_ms: u32, pub advance_on_click: bool, pub advance_after_ms: Option, /// Embedded sound media. pub sound: Option, /// Unmodelled transition XML kept for round-trip. #[serde(skip_serializing_if = "Option::is_none")] pub raw: Option, } impl Default for Transition { fn default() -> Self { Transition { kind: "none".into(), option: String::new(), duration_ms: 1000, advance_on_click: true, advance_after_ms: None, sound: None, raw: None, } } } /// (id, label, category, default duration ms, effect options) pub static TRANSITIONS: &[(&str, &str, &str, u32, &[&str])] = &[ ("none", "None", "Subtle", 0, &[]), ("morph", "Morph", "Subtle", 2000, &["objects", "words", "characters"]), ("fade", "Fade", "Subtle", 700, &["smoothly", "throughBlack"]), ("push", "Push", "Subtle", 1000, &["u", "d", "l", "r"]), ("wipe", "Wipe", "Subtle", 1000, &["r", "l", "u", "d", "ru", "lu", "rd", "ld"]), ("split", "Split", "Subtle", 1500, &["vertOut", "vertIn", "horzOut", "horzIn"]), ("reveal", "Reveal", "Subtle", 2000, &["smoothlyLeft", "smoothlyRight", "blackLeft", "blackRight"]), ("cut", "Cut", "Subtle", 0, &["", "throughBlack"]), ("randomBar", "Random Bars", "Subtle", 1000, &["vert", "horz"]), ("shape", "Shape", "Subtle", 2000, &["circle", "diamond", "plus", "in", "out"]), ("uncover", "Uncover", "Subtle", 1000, &["l", "r", "u", "d", "lu", "ru", "ld", "rd"]), ("cover", "Cover", "Subtle", 1000, &["l", "r", "u", "d", "lu", "ru", "ld", "rd"]), ("flash", "Flash", "Subtle", 1000, &[]), ("fallOver", "Fall Over", "Exciting", 2000, &["l", "r"]), ("drape", "Drape", "Exciting", 2000, &["l", "r"]), ("curtains", "Curtains", "Exciting", 2000, &[]), ("wind", "Wind", "Exciting", 2000, &["l", "r"]), ("prestige", "Prestige", "Exciting", 2000, &[]), ("fracture", "Fracture", "Exciting", 2000, &[]), ("crush", "Crush", "Exciting", 2000, &[]), ("peelOff", "Peel Off", "Exciting", 1500, &["l", "r"]), ("pageCurlDouble", "Page Curl", "Exciting", 1500, &["l", "r"]), ("airplane", "Airplane", "Exciting", 2000, &["l", "r"]), ("origami", "Origami", "Exciting", 2000, &["l", "r"]), ("dissolve", "Dissolve", "Exciting", 1200, &[]), ("checker", "Checkerboard", "Exciting", 1500, &["horz", "vert"]), ("blinds", "Blinds", "Exciting", 1600, &["vert", "horz"]), ("clock", "Clock", "Exciting", 2000, &["clockwise", "counterClockwise", "wedge"]), ("ripple", "Ripple", "Exciting", 1400, &["center", "lu", "ru", "ld", "rd"]), ("honeycomb", "Honeycomb", "Exciting", 3000, &[]), ("glitter", "Glitter", "Exciting", 2500, &["l", "r", "u", "d"]), ("vortex", "Vortex", "Exciting", 3000, &["l", "r", "u", "d"]), ("shred", "Shred", "Exciting", 2000, &["strips", "particles"]), ("switch", "Switch", "Exciting", 1250, &["l", "r"]), ("flip", "Flip", "Exciting", 1250, &["l", "r"]), ("gallery", "Gallery", "Exciting", 1500, &["l", "r"]), ("cube", "Cube", "Exciting", 1250, &["l", "r", "u", "d"]), ("doors", "Doors", "Exciting", 1400, &["vert", "horz"]), ("box", "Box", "Exciting", 1250, &["l", "r", "u", "d"]), ("comb", "Comb", "Exciting", 1000, &["horz", "vert"]), ("zoom", "Zoom", "Exciting", 1000, &["in", "out"]), ("random", "Random", "Exciting", 2000, &[]), ("pan", "Pan", "Dynamic Content", 1000, &["u", "d", "l", "r"]), ("ferris", "Ferris Wheel", "Dynamic Content", 2000, &["l", "r"]), ("conveyor", "Conveyor", "Dynamic Content", 2000, &["l", "r"]), ("rotate", "Rotate", "Dynamic Content", 2000, &["l", "r", "u", "d"]), ("window", "Window", "Dynamic Content", 1250, &["vert", "horz"]), ("orbit", "Orbit", "Dynamic Content", 2000, &["l", "r", "u", "d"]), ("flythrough", "Fly Through", "Dynamic Content", 1000, &["in", "out", "inBounce", "outBounce"]), ]; #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum AnimClass { #[default] Entrance, Emphasis, Exit, Path, Media, } impl AnimClass { pub fn xml(self) -> &'static str { match self { AnimClass::Entrance => "entr", AnimClass::Emphasis => "emph", AnimClass::Exit => "exit", AnimClass::Path => "path", AnimClass::Media => "mediacall", } } pub fn from_xml(s: &str) -> Self { match s { "emph" => AnimClass::Emphasis, "exit" => AnimClass::Exit, "path" => AnimClass::Path, "mediacall" => AnimClass::Media, _ => AnimClass::Entrance, } } } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum AnimStart { #[default] OnClick, WithPrevious, AfterPrevious, } /// Text build: as one object, all at once by paragraph, or by paragraph level. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum TextBuild { #[default] AsOne, ByParagraph, ByWord, ByLetter, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(default, rename_all = "camelCase")] pub struct Animation { pub shape: ShapeId, pub class: AnimClass, /// Effect id from [`ANIMATIONS`] (e.g. `fade`, `fly`, `zoom`, `spin`, `pulse`, `lines`…). pub effect: String, /// Effect option (direction etc.). pub option: String, pub start: AnimStart, pub duration_ms: u32, pub delay_ms: u32, /// Repeat count (0/1 = once); `u32::MAX` = until next click. pub repeat: u32, pub rewind: bool, pub text_build: TextBuild, /// Paragraph index when the effect animates one paragraph of a text body. pub paragraph: Option, /// Motion path in slide-relative units (SVG-like `M 0 0 L 0.25 0 E`). pub path: Option, /// Triggered by a click on another shape. pub trigger: Option, /// Emphasis amount: spin degrees, grow/shrink scale (1.5 = 150%), transparency… pub amount: Option, /// Emphasis colour (fill/line/font colour change). pub color: Option, /// PowerPoint preset id/subtype read from or written to files. pub preset_id: Option, pub preset_subtype: Option, /// After animation: dim to colour, hide, hide on next click. pub after: Option, pub sound: Option, pub smooth_start: f64, pub smooth_end: f64, pub bounce_end: f64, pub auto_reverse: bool, } impl Default for Animation { fn default() -> Self { Animation { shape: ShapeId(0), class: AnimClass::Entrance, effect: "fade".into(), option: String::new(), start: AnimStart::OnClick, duration_ms: 500, delay_ms: 0, repeat: 1, rewind: false, text_build: TextBuild::AsOne, paragraph: None, path: None, trigger: None, amount: None, color: None, preset_id: None, preset_subtype: None, after: None, sound: None, smooth_start: 0.0, smooth_end: 0.0, bounce_end: 0.0, auto_reverse: false, } } } /// (id, label, class, PowerPoint preset id, default duration ms, options) pub static ANIMATIONS: &[(&str, &str, AnimClass, u32, u32, &[&str])] = &[ ("appear", "Appear", AnimClass::Entrance, 1, 0, &[]), ("fade", "Fade", AnimClass::Entrance, 10, 500, &[]), ("fly", "Fly In", AnimClass::Entrance, 2, 500, &["b", "lb", "l", "lt", "t", "rt", "r", "rb"]), ("float", "Float In", AnimClass::Entrance, 42, 1000, &["u", "d"]), ("split", "Split", AnimClass::Entrance, 16, 500, &["horzIn", "horzOut", "vertIn", "vertOut"]), ("wipe", "Wipe", AnimClass::Entrance, 22, 500, &["b", "l", "t", "r"]), ("shape", "Shape", AnimClass::Entrance, 6, 2000, &["circleIn", "circleOut", "boxIn", "boxOut", "diamondIn", "diamondOut", "plusIn", "plusOut"]), ("wheel", "Wheel", AnimClass::Entrance, 21, 2000, &["1", "2", "3", "4", "8"]), ("randomBars", "Random Bars", AnimClass::Entrance, 14, 500, &["horz", "vert"]), ("grow", "Grow & Turn", AnimClass::Entrance, 31, 500, &[]), ("zoom", "Zoom", AnimClass::Entrance, 53, 500, &["in", "out"]), ("swivel", "Swivel", AnimClass::Entrance, 45, 2000, &["horz", "vert"]), ("bounce", "Bounce", AnimClass::Entrance, 26, 2000, &[]), ("blinds", "Blinds", AnimClass::Entrance, 3, 500, &["horz", "vert"]), ("box", "Box", AnimClass::Entrance, 4, 500, &["in", "out"]), ("checkerboard", "Checkerboard", AnimClass::Entrance, 5, 500, &["across", "down"]), ("circle", "Circle", AnimClass::Entrance, 6, 500, &["in", "out"]), ("diamond", "Diamond", AnimClass::Entrance, 8, 500, &["in", "out"]), ("dissolve", "Dissolve In", AnimClass::Entrance, 9, 500, &[]), ("peek", "Peek In", AnimClass::Entrance, 12, 500, &["b", "l", "t", "r"]), ("strips", "Strips", AnimClass::Entrance, 18, 500, &["lu", "ru", "ld", "rd"]), ("expand", "Expand", AnimClass::Entrance, 55, 500, &[]), ("rise", "Rise Up", AnimClass::Entrance, 37, 1000, &[]), ("spinner", "Spinner", AnimClass::Entrance, 49, 1000, &[]), ("pulse", "Pulse", AnimClass::Emphasis, 26, 500, &[]), ("colorPulse", "Color Pulse", AnimClass::Emphasis, 27, 500, &[]), ("teeter", "Teeter", AnimClass::Emphasis, 32, 1000, &[]), ("spin", "Spin", AnimClass::Emphasis, 8, 2000, &["clockwise", "counterClockwise"]), ("growShrink", "Grow/Shrink", AnimClass::Emphasis, 6, 2000, &["both", "horz", "vert"]), ("desaturate", "Desaturate", AnimClass::Emphasis, 35, 500, &[]), ("darken", "Darken", AnimClass::Emphasis, 25, 500, &[]), ("lighten", "Lighten", AnimClass::Emphasis, 30, 500, &[]), ("transparency", "Transparency", AnimClass::Emphasis, 9, 500, &[]), ("objectColor", "Object Color", AnimClass::Emphasis, 19, 2000, &[]), ("complementaryColor", "Complementary Color", AnimClass::Emphasis, 21, 500, &[]), ("lineColor", "Line Color", AnimClass::Emphasis, 7, 2000, &[]), ("fillColor", "Fill Color", AnimClass::Emphasis, 1, 2000, &[]), ("fontColor", "Font Color", AnimClass::Emphasis, 3, 2000, &[]), ("underline", "Underline", AnimClass::Emphasis, 18, 500, &[]), ("boldFlash", "Bold Flash", AnimClass::Emphasis, 10, 500, &[]), ("wave", "Wave", AnimClass::Emphasis, 34, 500, &[]), ("disappear", "Disappear", AnimClass::Exit, 1, 0, &[]), ("fadeOut", "Fade", AnimClass::Exit, 10, 500, &[]), ("flyOut", "Fly Out", AnimClass::Exit, 2, 500, &["b", "lb", "l", "lt", "t", "rt", "r", "rb"]), ("floatOut", "Float Out", AnimClass::Exit, 42, 1000, &["u", "d"]), ("splitOut", "Split", AnimClass::Exit, 16, 500, &["horzIn", "horzOut", "vertIn", "vertOut"]), ("wipeOut", "Wipe", AnimClass::Exit, 22, 500, &["b", "l", "t", "r"]), ("shapeOut", "Shape", AnimClass::Exit, 6, 2000, &["circleIn", "circleOut"]), ("wheelOut", "Wheel", AnimClass::Exit, 21, 2000, &["1", "2", "3", "4", "8"]), ("randomBarsOut", "Random Bars", AnimClass::Exit, 14, 500, &["horz", "vert"]), ("shrinkTurn", "Shrink & Turn", AnimClass::Exit, 31, 500, &[]), ("zoomOut", "Zoom", AnimClass::Exit, 53, 500, &["in", "out"]), ("swivelOut", "Swivel", AnimClass::Exit, 45, 2000, &["horz", "vert"]), ("bounceOut", "Bounce", AnimClass::Exit, 26, 2000, &[]), ("dissolveOut", "Dissolve Out", AnimClass::Exit, 9, 500, &[]), ("lines", "Lines", AnimClass::Path, 64, 2000, &["down", "up", "right", "left"]), ("arcs", "Arcs", AnimClass::Path, 37, 2000, &["down", "up", "right", "left"]), ("turns", "Turns", AnimClass::Path, 43, 2000, &["down", "up", "right", "left"]), ( "shapes", "Shapes", AnimClass::Path, 1, 2000, &["circle", "diamond", "hexagon", "triangle", "square", "trapezoid", "octagon", "parallelogram", "pentagon"], ), ("loops", "Loops", AnimClass::Path, 61, 2000, &[]), ("customPath", "Custom Path", AnimClass::Path, 0, 2000, &[]), ("play", "Play", AnimClass::Media, 1, 0, &[]), ("pause", "Pause", AnimClass::Media, 2, 0, &[]), ("stop", "Stop", AnimClass::Media, 3, 0, &[]), ]; pub fn animation_info(effect: &str, class: AnimClass) -> Option<&'static (&'static str, &'static str, AnimClass, u32, u32, &'static [&'static str])> { ANIMATIONS.iter().find(|a| a.0 == effect && a.2 == class).or_else(|| ANIMATIONS.iter().find(|a| a.0 == effect)) }