use crate::Size;
use core::marker::PhantomData;
use core::time::Duration;
#[cfg(feature = "std")]
use crate::clock;
pub trait Rattle: Copy + Default {
const SIZE: Size;
const INTERVAL: Duration;
const FRAMES: &'static [&'static [&'static str]];
}
#[derive(Debug, Clone)]
pub struct Rattler<T: Rattle, const REVERSED: bool = false> {
interval_ms: u64,
offset: usize,
_marker: PhantomData<T>,
}
#[derive(Debug, Clone)]
pub struct TickedRattler<T: Rattle, const REVERSED: bool = false> {
interval_ms: u64,
offset: usize,
current: usize,
_marker: PhantomData<T>,
}
impl<T: Rattle, const REVERSED: bool> Rattler<T, REVERSED> {
pub fn new() -> Self {
let interval = T::INTERVAL;
Self {
interval_ms: interval.as_millis().max(1) as u64,
offset: 0,
_marker: PhantomData,
}
}
pub fn with_offset(offset: usize) -> Self {
Self {
interval_ms: T::INTERVAL.as_millis().max(1) as u64,
offset: if T::FRAMES.is_empty() {
0
} else {
offset % T::FRAMES.len()
},
_marker: PhantomData,
}
}
pub const fn size(&self) -> Size {
T::SIZE
}
pub fn len(&self) -> usize {
T::FRAMES.len()
}
pub fn is_empty(&self) -> bool {
T::FRAMES.is_empty()
}
fn index_at_elapsed(&self, elapsed: Duration) -> usize {
let len = T::FRAMES.len();
let base = base_index_at_elapsed_ms(elapsed.as_millis() as u64, self.interval_ms, len);
let shifted = if len == 0 {
0
} else {
(base + self.offset) % len
};
playback_index_const::<REVERSED>(shifted, len)
}
pub fn frames(&self, index: usize) -> &'static [&'static str] {
let len = T::FRAMES.len();
if len == 0 {
return &[];
}
let shifted = (index + self.offset) % len;
let idx = playback_index_const::<REVERSED>(shifted, len);
&T::FRAMES[idx]
}
pub fn frame(&self, index: usize) -> &'static str {
self.frames(index)[0]
}
pub fn frames_at(&self, elapsed: Duration) -> &'static [&'static str] {
let idx = self.index_at_elapsed(elapsed);
&T::FRAMES[idx]
}
pub fn frame_at(&self, elapsed: Duration) -> &'static str {
self.frames_at(elapsed)[0]
}
#[cfg(feature = "std")]
pub fn current_frames(&self) -> &'static [&'static str] {
self.frames_at(clock::elapsed())
}
#[cfg(feature = "std")]
pub fn current_frame(&self) -> &'static str {
self.current_frames()[0]
}
#[cfg(feature = "std")]
pub fn index(&self) -> usize {
self.index_at_elapsed(clock::elapsed())
}
pub const fn is_reversed(&self) -> bool {
REVERSED
}
pub const fn interval(&self) -> Duration {
Duration::from_millis(self.interval_ms)
}
pub fn set_interval(mut self, interval: Duration) -> Self {
self.interval_ms = interval.as_millis().max(1) as u64;
self
}
pub fn offset(mut self, offset: usize) -> Self {
self.offset = if T::FRAMES.is_empty() {
0
} else {
offset % T::FRAMES.len()
};
self
}
pub fn into_ticked(self) -> TickedRattler<T, REVERSED> {
TickedRattler {
interval_ms: self.interval_ms,
offset: self.offset,
current: 0,
_marker: PhantomData,
}
}
}
impl<T: Rattle, const REVERSED: bool> Default for Rattler<T, REVERSED> {
fn default() -> Self {
Self::new()
}
}
impl<T: Rattle> Rattler<T, false> {
pub fn reverse(self) -> Rattler<T, true> {
Rattler {
interval_ms: self.interval_ms,
offset: self.offset,
_marker: PhantomData,
}
}
}
impl<T: Rattle> Rattler<T, true> {
pub fn reverse(self) -> Rattler<T, false> {
Rattler {
interval_ms: self.interval_ms,
offset: self.offset,
_marker: PhantomData,
}
}
}
impl<T: Rattle, const REVERSED: bool> TickedRattler<T, REVERSED> {
pub fn new() -> Self {
Rattler::<T, REVERSED>::new().into_ticked()
}
pub fn with_offset(offset: usize) -> Self {
Rattler::<T, REVERSED>::with_offset(offset).into_ticked()
}
pub const fn size(&self) -> Size {
T::SIZE
}
pub fn len(&self) -> usize {
T::FRAMES.len()
}
pub fn is_empty(&self) -> bool {
T::FRAMES.is_empty()
}
pub fn current_frames(&self) -> &'static [&'static str] {
let len = T::FRAMES.len();
if len == 0 {
return &[];
}
let shifted = (self.current + self.offset) % len;
let idx = playback_index_const::<REVERSED>(shifted, len);
&T::FRAMES[idx]
}
pub fn current_frame(&self) -> &'static str {
self.current_frames()[0]
}
pub fn index(&self) -> usize {
let len = T::FRAMES.len();
if len == 0 {
return 0;
}
playback_index_const::<REVERSED>((self.current + self.offset) % len, len)
}
pub fn tick(&mut self) -> &'static [&'static str] {
let len = T::FRAMES.len();
if len == 0 {
return &[];
}
self.current = (self.current + 1) % len;
self.current_frames()
}
pub fn tick_by(&mut self, steps: usize) -> &'static [&'static str] {
let len = T::FRAMES.len();
if len == 0 {
return &[];
}
self.current = (self.current + (steps % len)) % len;
self.current_frames()
}
pub fn reset(&mut self) {
self.current = 0;
}
pub const fn is_reversed(&self) -> bool {
REVERSED
}
pub const fn interval(&self) -> Duration {
Duration::from_millis(self.interval_ms)
}
pub fn set_interval(mut self, interval: Duration) -> Self {
self.interval_ms = interval.as_millis().max(1) as u64;
self
}
pub fn offset(mut self, offset: usize) -> Self {
self.offset = if T::FRAMES.is_empty() {
0
} else {
offset % T::FRAMES.len()
};
self
}
pub fn into_timed(self) -> Rattler<T, REVERSED> {
Rattler {
interval_ms: self.interval_ms,
offset: self.offset,
_marker: PhantomData,
}
}
}
impl<T: Rattle, const REVERSED: bool> Default for TickedRattler<T, REVERSED> {
fn default() -> Self {
Self::new()
}
}
impl<T: Rattle> TickedRattler<T, false> {
pub fn reverse(self) -> TickedRattler<T, true> {
TickedRattler {
interval_ms: self.interval_ms,
offset: self.offset,
current: self.current,
_marker: PhantomData,
}
}
}
impl<T: Rattle> TickedRattler<T, true> {
pub fn reverse(self) -> TickedRattler<T, false> {
TickedRattler {
interval_ms: self.interval_ms,
offset: self.offset,
current: self.current,
_marker: PhantomData,
}
}
}
impl<T: Rattle, const REVERSED: bool> Iterator for TickedRattler<T, REVERSED> {
type Item = &'static [&'static str];
fn next(&mut self) -> Option<Self::Item> {
Some(self.tick())
}
}
#[cfg(feature = "std")]
impl<T: Rattle, const REVERSED: bool> Iterator for Rattler<T, REVERSED> {
type Item = &'static [&'static str];
fn next(&mut self) -> Option<Self::Item> {
Some(self.current_frames())
}
}
fn playback_index_const<const REVERSED: bool>(index: usize, len: usize) -> usize {
if len == 0 {
return 0;
}
if REVERSED { len - 1 - index } else { index }
}
fn base_index_at_elapsed_ms(elapsed_ms: u64, interval_ms: u64, len: usize) -> usize {
if len == 0 {
return 0;
}
((elapsed_ms / interval_ms.max(1)) as usize) % len
}