ElyDoc, ElyTable, ElyShow (LibreOfficeKit), ElyPDF (MuPDF), ElyTree (Git), ElyMark (Milkdown and CodeMirror in CEF). ElyMail and ElyAgent follow.
172 lines
5.3 KiB
Rust
172 lines
5.3 KiB
Rust
//! Where a show stands, and where each key takes it.
|
|
|
|
use ely_lok::show::{Effect, ShowSlide};
|
|
|
|
use crate::preview::timeline::{Run, length, schedule};
|
|
|
|
/// Effects in click groups; group 0 runs on entry.
|
|
pub fn groups(effects: &[Effect]) -> Vec<Vec<Effect>> {
|
|
let mut groups = vec![Vec::new()];
|
|
for effect in effects {
|
|
if effect.start == "click" {
|
|
groups.push(Vec::new());
|
|
}
|
|
groups.last_mut().expect("one group").push(effect.clone());
|
|
}
|
|
groups
|
|
}
|
|
|
|
/// A slide and its clicks done, or the end.
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
pub enum Place {
|
|
Slide { part: usize, clicks: usize },
|
|
End,
|
|
}
|
|
|
|
/// The next place on a click.
|
|
pub fn next(slides: &[ShowSlide], place: Place) -> Place {
|
|
match place {
|
|
Place::Slide { part, clicks } if clicks + 1 < groups(&slides[part].effects).len() => {
|
|
Place::Slide {
|
|
part,
|
|
clicks: clicks + 1,
|
|
}
|
|
}
|
|
Place::Slide { part, .. } => (part + 1..slides.len())
|
|
.find(|at| !slides[*at].hidden)
|
|
.map_or(Place::End, |part| Place::Slide { part, clicks: 0 }),
|
|
Place::End => Place::End,
|
|
}
|
|
}
|
|
|
|
/// The previous slide, fully built; the first stays.
|
|
pub fn previous(slides: &[ShowSlide], place: Place) -> Place {
|
|
let from = match place {
|
|
Place::Slide { part, .. } => part,
|
|
Place::End => slides.len(),
|
|
};
|
|
let built = |part: usize| Place::Slide {
|
|
part,
|
|
clicks: groups(&slides[part].effects).len() - 1,
|
|
};
|
|
match (0..from).rev().find(|at| !slides[*at].hidden) {
|
|
Some(part) => built(part),
|
|
None => place,
|
|
}
|
|
}
|
|
|
|
/// Runs from entry: group `n` starts at `started[n]` seconds.
|
|
pub fn runs(effects: &[Effect], started: &[f64]) -> Vec<Run> {
|
|
(groups(effects).iter().zip(started))
|
|
.flat_map(|(group, at)| {
|
|
schedule(group).into_iter().map(move |run| Run {
|
|
begin: run.begin + at,
|
|
end: run.end + at,
|
|
..run
|
|
})
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
/// A click group starts now or once all before end.
|
|
pub fn click_at(effects: &[Effect], clicks: &[f64], now: f64) -> f64 {
|
|
let before = clicks.iter().copied().fold(now, f64::max);
|
|
length(&runs(effects, clicks)).max(before)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use ely_lok::show::Transition;
|
|
|
|
fn effect(start: &str) -> Effect {
|
|
Effect {
|
|
preset: "ooo-entrance-fade-in".into(),
|
|
label: String::new(),
|
|
class: "entrance".into(),
|
|
target: Some(0),
|
|
hash: start.into(),
|
|
start: start.into(),
|
|
duration: 1.0,
|
|
delay: 0.0,
|
|
}
|
|
}
|
|
|
|
fn slide(hidden: bool, effects: Vec<Effect>) -> ShowSlide {
|
|
ShowSlide {
|
|
hidden,
|
|
notes: String::new(),
|
|
transition: Transition {
|
|
preset: String::new(),
|
|
duration: 0.0,
|
|
auto: false,
|
|
time: 0.0,
|
|
},
|
|
effects,
|
|
media: Vec::new(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn with_and_after_join_the_click_before() {
|
|
let sizes = |effects: &[Effect]| groups(effects).iter().map(Vec::len).collect::<Vec<_>>();
|
|
assert_eq!(sizes(&[]), [0]);
|
|
assert_eq!(sizes(&[effect("with"), effect("click")]), [1, 1]);
|
|
assert_eq!(sizes(&[effect("click"), effect("after"), effect("click")]), [0, 2, 1]);
|
|
}
|
|
|
|
#[test]
|
|
fn clicks_play_effects_then_skip_hidden_slides_to_the_end() {
|
|
let deck = [
|
|
slide(false, vec![effect("click")]),
|
|
slide(true, Vec::new()),
|
|
slide(false, Vec::new()),
|
|
];
|
|
let mut place = Place::Slide { part: 0, clicks: 0 };
|
|
let mut seen = Vec::new();
|
|
for _ in 0..4 {
|
|
place = next(&deck, place);
|
|
seen.push(place);
|
|
}
|
|
assert_eq!(
|
|
seen,
|
|
[
|
|
Place::Slide { part: 0, clicks: 1 },
|
|
Place::Slide { part: 2, clicks: 0 },
|
|
Place::End,
|
|
Place::End
|
|
]
|
|
);
|
|
assert_eq!(previous(&deck, Place::End), Place::Slide { part: 2, clicks: 0 });
|
|
assert_eq!(
|
|
previous(&deck, Place::Slide { part: 2, clicks: 0 }),
|
|
Place::Slide { part: 0, clicks: 1 },
|
|
"back to a built slide, past the hidden one"
|
|
);
|
|
let first = Place::Slide { part: 0, clicks: 0 };
|
|
assert_eq!(previous(&deck, first), first);
|
|
}
|
|
|
|
#[test]
|
|
fn a_quick_click_waits_for_the_groups_and_transition_before() {
|
|
let long = |start| Effect {
|
|
duration: 10.0,
|
|
..effect(start)
|
|
};
|
|
let effects = [long("click"), long("click")];
|
|
assert_eq!(click_at(&effects, &[0.0], 0.1), 0.1, "nothing runs yet");
|
|
assert_eq!(click_at(&effects, &[0.0, 0.1], 0.2), 10.1, "after the first");
|
|
assert_eq!(click_at(&effects, &[2.0], 0.5), 2.0, "after the transition");
|
|
}
|
|
|
|
#[test]
|
|
fn each_group_runs_from_its_own_click() {
|
|
let effects = [effect("with"), effect("click"), effect("after")];
|
|
let times: Vec<(f64, f64)> = runs(&effects, &[0.0, 5.0])
|
|
.iter()
|
|
.map(|r| (r.begin, r.end))
|
|
.collect();
|
|
assert_eq!(times, [(0.0, 1.0), (5.0, 6.0), (6.0, 7.0)]);
|
|
}
|
|
}
|