feat: add collapse_horizontal slide transition (#560)

This adds a new `collapse_horizontal` slide transition that collapses
the current slide into the center of the screen.



https://github.com/user-attachments/assets/a398c286-ae3e-4c99-81aa-26aba498726d
This commit is contained in:
Matias Fontanini 2025-04-21 18:59:02 -07:00 committed by GitHub
commit 94ce0a9225
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 105 additions and 10 deletions

View File

@ -586,6 +586,22 @@
}
},
"additionalProperties": false
},
{
"description": "Collapse the current slide into the center of the screen.",
"type": "object",
"required": [
"style"
],
"properties": {
"style": {
"type": "string",
"enum": [
"collapse_horizontal"
]
}
},
"additionalProperties": false
}
]
},

View File

@ -553,6 +553,9 @@ pub enum SlideTransitionStyleConfig {
/// Fade the new slide into the previous one.
Fade,
/// Collapse the current slide into the center of the screen.
CollapseHorizontal,
}
fn make_keybindings<const N: usize>(raw_bindings: [&str; N]) -> Vec<KeyBinding> {

View File

@ -29,7 +29,8 @@ use crate::{
theme::{ProcessingThemeError, raw::PresentationTheme},
third_party::ThirdPartyRender,
transitions::{
AnimateTransition, AnimationFrame, LinesFrame, TransitionDirection, fade::FadeAnimation,
AnimateTransition, AnimationFrame, LinesFrame, TransitionDirection,
collapse_horizontal::CollapseHorizontalAnimation, fade::FadeAnimation,
slide_horizontal::SlideHorizontalAnimation,
},
};
@ -518,6 +519,9 @@ impl<'a> Presenter<'a> {
SlideTransitionStyleConfig::Fade => {
self.run_animation(drawer, first, FadeAnimation::new(left, right, direction), config)
}
SlideTransitionStyleConfig::CollapseHorizontal => {
self.run_animation(drawer, first, CollapseHorizontalAnimation::new(left, right, direction), config)
}
}
}

View File

@ -0,0 +1,65 @@
use super::{AnimateTransition, LinesFrame, TransitionDirection};
use crate::terminal::virt::TerminalGrid;
pub(crate) struct CollapseHorizontalAnimation {
from: TerminalGrid,
to: TerminalGrid,
}
impl CollapseHorizontalAnimation {
pub(crate) fn new(left: TerminalGrid, right: TerminalGrid, direction: TransitionDirection) -> Self {
let (from, to) = match direction {
TransitionDirection::Next => (left, right),
TransitionDirection::Previous => (right, left),
};
Self { from, to }
}
}
impl AnimateTransition for CollapseHorizontalAnimation {
type Frame = LinesFrame;
fn build_frame(&self, frame: usize, _previous_frame: usize) -> Self::Frame {
let mut rows = Vec::new();
for (from, to) in self.from.rows.iter().zip(&self.to.rows) {
// Take the first and last `frame` cells
let to_prefix = to.iter().take(frame);
let to_suffix = to.iter().rev().take(frame).rev();
let total_rows_from = from.len() - frame * 2;
let from = from.iter().skip(frame).take(total_rows_from);
let row = to_prefix.chain(from).chain(to_suffix).copied().collect();
rows.push(row)
}
let grid = TerminalGrid { rows, background_color: self.from.background_color, images: Default::default() };
LinesFrame::from(&grid)
}
fn total_frames(&self) -> usize {
self.from.rows[0].len() / 2
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{markdown::elements::Line, transitions::utils::build_grid};
use rstest::rstest;
fn as_text(line: Line) -> String {
line.0.into_iter().map(|l| l.content).collect()
}
#[rstest]
#[case(0, &["ABCDEF"])]
#[case(1, &["1BCDE6"])]
#[case(2, &["12CD56"])]
#[case(3, &["123456"])]
fn transition(#[case] frame: usize, #[case] expected: &[&str]) {
let left = build_grid(&["ABCDEF"]);
let right = build_grid(&["123456"]);
let transition = CollapseHorizontalAnimation::new(left, right, TransitionDirection::Next);
let lines: Vec<_> = transition.build_frame(frame, 0).lines.into_iter().map(as_text).collect();
assert_eq!(lines, expected);
}
}

View File

@ -8,6 +8,7 @@ use crate::{
use std::fmt::Debug;
use unicode_width::UnicodeWidthStr;
pub(crate) mod collapse_horizontal;
pub(crate) mod fade;
pub(crate) mod slide_horizontal;
@ -109,6 +110,19 @@ impl AnimationFrame for LinesFrame {
}
}
#[cfg(test)]
mod utils {
use crate::terminal::virt::{StyledChar, TerminalGrid};
pub(crate) fn build_grid(rows: &[&str]) -> TerminalGrid {
let rows = rows
.iter()
.map(|r| r.chars().map(|c| StyledChar { character: c, style: Default::default() }).collect())
.collect();
TerminalGrid { rows, background_color: None, images: Default::default() }
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -69,21 +69,12 @@ impl AnimateTransition for SlideHorizontalAnimation {
#[cfg(test)]
mod tests {
use super::*;
use crate::terminal::virt::StyledChar;
use rstest::rstest;
fn as_text(line: Line) -> String {
line.0.into_iter().map(|l| l.content).collect()
}
fn build_grid(rows: &[&str]) -> TerminalGrid {
let rows = rows
.iter()
.map(|r| r.chars().map(|c| StyledChar { character: c, style: Default::default() }).collect())
.collect();
TerminalGrid { rows, background_color: None, images: Default::default() }
}
#[rstest]
#[case::next_frame0(0, TransitionDirection::Next, &["AB", "CD"])]
#[case::next_frame1(1, TransitionDirection::Next, &["BE", "DG"])]
@ -94,6 +85,8 @@ mod tests {
#[case::previous_frame2(2, TransitionDirection::Previous, &["AB", "CD"])]
#[case::previous_way_past(100, TransitionDirection::Previous, &["AB", "CD"])]
fn build_frame(#[case] frame: usize, #[case] direction: TransitionDirection, #[case] expected: &[&str]) {
use crate::transitions::utils::build_grid;
let left = build_grid(&["AB", "CD"]);
let right = build_grid(&["EF", "GH"]);
let dimensions = WindowSize { rows: 2, columns: 2, height: 0, width: 0 };