use crate::CubicBezier;
use crate::TIME_EPSILON;
use crate::ease::Ease;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Dir {
Left,
Right,
Up,
Down,
}
impl Dir {
pub(crate) fn name(self) -> &'static str {
match self {
Self::Left => "left",
Self::Right => "right",
Self::Up => "up",
Self::Down => "down",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LayerPose {
pub opacity: f64,
pub dx: f64,
pub dy: f64,
pub scale: f64,
pub wipe: Option<(Dir, f64)>,
}
impl Default for LayerPose {
fn default() -> Self {
Self {
opacity: 1.0,
dx: 0.0,
dy: 0.0,
scale: 1.0,
wipe: None,
}
}
}
impl LayerPose {
pub fn new() -> Self {
Self::default()
}
pub fn opacity(mut self, opacity: f64) -> Self {
self.opacity = opacity;
self
}
pub fn offset(mut self, dx: f64, dy: f64) -> Self {
self.dx = dx;
self.dy = dy;
self
}
pub fn scale(mut self, scale: f64) -> Self {
self.scale = scale;
self
}
pub fn wipe(mut self, dir: Dir, amount: f64) -> Self {
self.wipe = Some((dir, amount));
self
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Blend {
pub from: LayerPose,
pub to: LayerPose,
pub incoming_on_top: bool,
pub mix: bool,
}
impl Default for Blend {
fn default() -> Self {
Self {
from: LayerPose::new(),
to: LayerPose::new(),
incoming_on_top: true,
mix: false,
}
}
}
#[derive(Clone, Copy)]
pub enum Kind {
Fade,
Slide(Dir),
Wipe(Dir),
ZoomIn,
ZoomOut,
Custom(fn(f64) -> Blend),
}
impl Kind {
pub(crate) fn name(self) -> &'static str {
match self {
Self::Fade => "fade",
Self::Slide(_) => "slide",
Self::Wipe(_) => "wipe",
Self::ZoomIn => "zoom_in",
Self::ZoomOut => "zoom_out",
Self::Custom(_) => "custom",
}
}
pub(crate) fn dir(self) -> Option<Dir> {
match self {
Self::Slide(d) | Self::Wipe(d) => Some(d),
_ => None,
}
}
}
#[derive(Clone, Copy)]
pub struct Recorded {
pub(crate) kind: Kind,
pub(crate) seconds: f64,
pub(crate) ease: CubicBezier,
pub(crate) overlap: bool,
pub(crate) start: f64,
}
impl Recorded {
pub fn new(kind: Kind, seconds: f64, ease: CubicBezier, overlap: bool, start: f64) -> Self {
Self {
kind,
seconds,
ease,
overlap,
start,
}
}
pub fn kind_name(self) -> &'static str {
self.kind.name()
}
pub fn dir_name(self) -> Option<&'static str> {
self.kind.dir().map(Dir::name)
}
pub fn overlap(self) -> bool {
self.overlap
}
pub fn seconds(self) -> f64 {
self.seconds
}
pub fn start(self) -> f64 {
self.start
}
pub fn active(self, now: f64) -> bool {
self.seconds > 0.0
&& now + TIME_EPSILON >= self.start
&& now + TIME_EPSILON < self.start + self.seconds
}
pub fn progress(self, now: f64) -> f64 {
if self.seconds <= 0.0 {
return 1.0;
}
let t = ((now - self.start) / self.seconds).clamp(0.0, 1.0);
self.ease.ease(t).clamp(0.0, 1.0)
}
pub fn blend_at(self, now: f64) -> Blend {
blend(self.kind, self.progress(now))
}
}
pub(crate) fn blend(kind: Kind, t: f64) -> Blend {
let t = t.clamp(0.0, 1.0);
match kind {
Kind::Fade => Blend {
mix: true,
..Blend::default()
},
Kind::Slide(dir) => {
let (fx, fy, tx, ty) = shift(dir, t);
Blend {
from: LayerPose::new().offset(fx, fy),
to: LayerPose::new().offset(tx, ty),
..Blend::default()
}
}
Kind::Wipe(dir) => Blend {
to: LayerPose::new().wipe(dir, t),
..Blend::default()
},
Kind::ZoomIn => Blend {
to: LayerPose::new().opacity(t).scale(0.6 + 0.4 * t),
..Blend::default()
},
Kind::ZoomOut => Blend {
from: LayerPose::new().opacity(1.0 - t).scale(1.0 + 0.4 * t),
incoming_on_top: false,
..Blend::default()
},
Kind::Custom(f) => f(t),
}
}
fn shift(dir: Dir, t: f64) -> (f64, f64, f64, f64) {
match dir {
Dir::Left => (-t, 0.0, 1.0 - t, 0.0),
Dir::Right => (t, 0.0, t - 1.0, 0.0),
Dir::Up => (0.0, -t, 0.0, 1.0 - t),
Dir::Down => (0.0, t, 0.0, t - 1.0),
}
}