use std::fmt;
use std::time::Duration;
use crate::fmt::{num, secs};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
pub enum Play {
#[default]
Scroll,
Load,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Edge {
Top,
Center,
Bottom,
Percent(f32),
Px(f32),
}
impl fmt::Display for Edge {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::Top => f.write_str("top"),
Self::Center => f.write_str("center"),
Self::Bottom => f.write_str("bottom"),
Self::Percent(v) => write!(f, "{}%", num(v).as_deref().unwrap_or("0")),
Self::Px(v) => write!(f, "{}px", num(v).as_deref().unwrap_or("0")),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ScrollPos {
Edges {
trigger: Edge,
viewport: Edge,
},
Distance(f32),
}
impl ScrollPos {
#[must_use]
pub const fn new(trigger: Edge, viewport: Edge) -> Self {
Self::Edges { trigger, viewport }
}
}
impl fmt::Display for ScrollPos {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::Edges { trigger, viewport } => write!(f, "{trigger} {viewport}"),
Self::Distance(px) => {
let px = num(px.max(0.0)).unwrap_or_else(|| "0".to_owned());
write!(f, "+={px}")
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Action {
Play,
Pause,
Resume,
Reverse,
Restart,
Reset,
Complete,
None,
}
impl Action {
const fn gsap(self) -> &'static str {
match self {
Self::Play => "play",
Self::Pause => "pause",
Self::Resume => "resume",
Self::Reverse => "reverse",
Self::Restart => "restart",
Self::Reset => "reset",
Self::Complete => "complete",
Self::None => "none",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ToggleActions {
pub on_enter: Action,
pub on_leave: Action,
pub on_enter_back: Action,
pub on_leave_back: Action,
}
impl ToggleActions {
#[must_use]
pub const fn new(
on_enter: Action,
on_leave: Action,
on_enter_back: Action,
on_leave_back: Action,
) -> Self {
Self {
on_enter,
on_leave,
on_enter_back,
on_leave_back,
}
}
}
impl fmt::Display for ToggleActions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} {} {} {}",
self.on_enter.gsap(),
self.on_leave.gsap(),
self.on_enter_back.gsap(),
self.on_leave_back.gsap()
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Scrub {
Linked,
Smooth(Duration),
}
impl fmt::Display for Scrub {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::Linked => f.write_str("true"),
Self::Smooth(d) => f.write_str(&secs(d)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::grammar::{is_scroll_pos, is_secs};
use proptest::prelude::*;
#[test]
fn edges_write_gsap_words() {
assert_eq!(Edge::Top.to_string(), "top");
assert_eq!(Edge::Center.to_string(), "center");
assert_eq!(Edge::Bottom.to_string(), "bottom");
assert_eq!(Edge::Percent(85.0).to_string(), "85%");
assert_eq!(Edge::Px(-120.5).to_string(), "-120.5px");
assert_eq!(Edge::Percent(f32::NAN).to_string(), "0%");
assert_eq!(Edge::Px(f32::INFINITY).to_string(), "0px");
}
#[test]
fn scroll_pos_writes_two_edges_or_a_distance() {
assert_eq!(
ScrollPos::new(Edge::Top, Edge::Percent(85.0)).to_string(),
"top 85%"
);
assert_eq!(
ScrollPos::new(Edge::Bottom, Edge::Top).to_string(),
"bottom top"
);
assert_eq!(ScrollPos::Distance(500.0).to_string(), "+=500");
assert_eq!(ScrollPos::Distance(-5.0).to_string(), "+=0");
assert_eq!(ScrollPos::Distance(f32::NAN).to_string(), "+=0");
}
#[test]
fn toggle_actions_write_four_words() {
let all = ToggleActions::new(Action::Play, Action::Pause, Action::Resume, Action::Reverse);
assert_eq!(all.to_string(), "play pause resume reverse");
let rest = ToggleActions::new(
Action::Restart,
Action::Reset,
Action::Complete,
Action::None,
);
assert_eq!(rest.to_string(), "restart reset complete none");
}
#[test]
fn scrub_writes_true_or_seconds() {
assert_eq!(Scrub::Linked.to_string(), "true");
assert_eq!(Scrub::Smooth(Duration::from_millis(500)).to_string(), "0.5");
assert!(is_secs(&Scrub::Smooth(Duration::from_secs(1)).to_string()));
}
#[test]
fn play_defaults_to_scroll() {
assert_eq!(Play::default(), Play::Scroll);
}
fn edge() -> impl Strategy<Value = Edge> {
let f = proptest::num::f32::ANY;
prop_oneof![
Just(Edge::Top),
Just(Edge::Center),
Just(Edge::Bottom),
f.prop_map(Edge::Percent),
f.prop_map(Edge::Px),
]
}
proptest! {
#[test]
fn every_scroll_pos_matches_the_js_grammar(
a in edge(),
b in edge(),
d in proptest::num::f32::ANY,
) {
let edges = ScrollPos::new(a, b).to_string();
prop_assert!(is_scroll_pos(&edges), "{edges}");
let dist = ScrollPos::Distance(d).to_string();
prop_assert!(is_scroll_pos(&dist), "{dist}");
}
}
}