mirror of
https://github.com/storytold/deckcraft.git
synced 2026-11-03 00:41:28 -06:00
Transitions: play transition preview from previous slide when Preview is clicked (#1654)
- When Preview is clicked on the Transitions ribbon, initialize Show with start_transition_preview from the prior slide into the selected slide (or from black for slide 0). - If tr.old is unset for a non-part transition with tr.from, render the previous slide texture on the first frame so the transition blends cleanly. - Indicate active playback on the Preview button while previewing. - Regression tests for transitions from previous slide and from black on slide 0.
This commit is contained in:
@@ -12,11 +12,14 @@ pub(super) fn transitions(app: &mut SlideApp, ui: &mut Ui) {
|
||||
let cur = app.session.active().and_then(|d| d.current_slide().and_then(|s| s.transition.clone()));
|
||||
let kind = cur.as_ref().map(|t| t.kind.clone()).unwrap_or_else(|| "none".into());
|
||||
group(ui, |ui| {
|
||||
if big_button(ui, Icon::Preview, "Preview", true).clicked() {
|
||||
let from = app.session.active().map(|d| d.selection.slide).unwrap_or(0);
|
||||
app.start_show(from, true);
|
||||
if let Some(s) = app.show.as_mut() {
|
||||
s.preview_only = true;
|
||||
let playing = app.show.as_ref().is_some_and(|s| s.preview_only);
|
||||
let (resp, _) = widgets::big_button_ex(ui, Icon::Preview, "Preview", true, false, playing);
|
||||
if resp.on_hover_text("Play this slide's transition").clicked() {
|
||||
let to = app.session.active().map(|d| d.selection.slide).unwrap_or(0);
|
||||
app.start_show(to, true);
|
||||
let now = ui.input(|i| i.time);
|
||||
if let (Some(s), Some(d)) = (app.show.as_mut(), app.session.active()) {
|
||||
s.start_transition_preview(&d.doc, now);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -261,6 +261,27 @@ impl Show {
|
||||
}
|
||||
}
|
||||
|
||||
/// Play this slide's transition from the previous slide (or black for slide 0) as a preview (#1654).
|
||||
pub fn start_transition_preview(&mut self, doc: &Presentation, now: f64) {
|
||||
let i = self.state.slide;
|
||||
let from = i.checked_sub(1);
|
||||
let (kind, option, dur) = doc
|
||||
.slides
|
||||
.get(i)
|
||||
.and_then(|s| s.transition.as_ref())
|
||||
.map(|t| (t.kind.clone(), t.option.clone(), t.duration_ms as f64 / 1000.0))
|
||||
.unwrap_or(("none".into(), String::new(), 0.0));
|
||||
self.trans = if kind != "none" && dur > 0.0 && !doc.show.without_animation {
|
||||
Some(Transition { old: None, from, morph: None, parts: None, start: now, kind, option, dur, reverse: false })
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.preview_only = true;
|
||||
self.slide_start = now;
|
||||
self.step_start = now;
|
||||
self.step_end = now;
|
||||
}
|
||||
|
||||
/// Identity of the show state a queued window-point event belongs to.
|
||||
pub(crate) fn input_state(&self) -> ShowInputState {
|
||||
let bits = |v: f32| if v.is_finite() { v.to_bits() } else { 0 };
|
||||
|
||||
@@ -1238,3 +1238,37 @@ fn a_preview_of_an_edited_presentation_is_rendered_again() {
|
||||
let tex = harness.state().1.next_tex.as_ref().map(|(d, k, _)| (std::sync::Arc::ptr_eq(d, &edited), *k));
|
||||
assert_eq!(tex, Some((true, 1)), "the preview comes from the edited presentation");
|
||||
}
|
||||
|
||||
/// #1654: Preview on the Transitions ribbon plays the current slide's transition from the
|
||||
/// previous slide into the current slide, and exits when finished.
|
||||
#[test]
|
||||
fn transition_preview_plays_from_the_previous_slide() {
|
||||
let mut s = deckcraft_engine::Session::with_new();
|
||||
s.execute("slide.new", &json!({})).unwrap();
|
||||
s.execute("transition.set", &json!({"kind": "push", "index": 1, "duration": 1.0})).unwrap();
|
||||
let doc = s.active().unwrap().doc.clone();
|
||||
let mut show = Show::new(&doc, 1, true, false);
|
||||
show.start_transition_preview(&doc, 10.0);
|
||||
assert!(show.preview_only);
|
||||
let tr = show.trans.as_ref().expect("transition preview is playing");
|
||||
assert_eq!(tr.kind, "push");
|
||||
assert_eq!(tr.from, Some(0), "enters from slide 0 into slide 1");
|
||||
assert_eq!(tr.dur, 1.0);
|
||||
assert!(!tr.reverse);
|
||||
assert_eq!(tr.start, 10.0);
|
||||
}
|
||||
|
||||
/// #1654: Transition preview for slide 0 plays with from = None (transitioning from black).
|
||||
#[test]
|
||||
fn transition_preview_for_first_slide_transitions_from_black() {
|
||||
let mut s = deckcraft_engine::Session::with_new();
|
||||
s.execute("transition.set", &json!({"kind": "fade", "index": 0, "duration": 500})).unwrap();
|
||||
let doc = s.active().unwrap().doc.clone();
|
||||
let mut show = Show::new(&doc, 0, true, false);
|
||||
show.start_transition_preview(&doc, 5.0);
|
||||
assert!(show.preview_only);
|
||||
let tr = show.trans.as_ref().expect("fade transition preview is playing");
|
||||
assert_eq!(tr.kind, "fade");
|
||||
assert_eq!(tr.from, None, "slide 0 transitions from black (None)");
|
||||
assert_eq!(tr.dur, 0.5);
|
||||
}
|
||||
|
||||
@@ -388,6 +388,16 @@ fn draw_transition(show: &mut Show, f: &Fr, sl: &Sl, painter: &egui::Painter, te
|
||||
if tr.parts.is_none() && deckcraft_anim::uses_slide_parts(&tr.kind, &tr.option) {
|
||||
tr.parts = Some(part_textures(ctx, doc, tr.from, idx, size));
|
||||
}
|
||||
if tr.old.is_none()
|
||||
&& let Some(prev) = tr.from
|
||||
&& !deckcraft_anim::uses_slide_parts(&tr.kind, &tr.option)
|
||||
{
|
||||
let threads =
|
||||
if cfg!(target_arch = "wasm32") { 0 } else { std::thread::available_parallelism().map(|n| n.get().min(8) as u16).unwrap_or(0) };
|
||||
let opts = RenderOpts { scale: size.0 as f64 / doc.slide_size.width.max(1.0), size: Some(size), threads, ..Default::default() };
|
||||
let img = deckcraft_render::render_slide(doc, prev, &opts);
|
||||
tr.old = Some(crate::textures::upload(ctx, "trans-old", crate::textures::to_color_image(&img, false)));
|
||||
}
|
||||
let aspect = doc.slide_size.width / doc.slide_size.height.max(1.0);
|
||||
let part = |k: usize| tr.parts.as_ref().and_then(|p| p.get(k).cloned().flatten());
|
||||
let parts = [part(0), part(1), part(2), part(3)];
|
||||
|
||||
Reference in New Issue
Block a user