rattles 0.3.1

Minimal terminal spinners for Rust
Documentation
use crate::util::apply_direction;
use crate::{Rattle, Rattler, Size};
use core::marker::PhantomData;
use core::time::Duration;

#[derive(Debug, Clone)]
pub struct TickedRattler<T: Rattle, const REVERSED: bool = false> {
    pub(crate) interval_ms: u64,
    pub(crate) offset: usize,
    pub(crate) current: usize,
    pub(crate) _marker: PhantomData<T>,
}

impl<T: Rattle, const REVERSED: bool> TickedRattler<T, REVERSED> {
    pub fn new() -> Self {
        Self {
            interval_ms: T::INTERVAL.as_millis().max(1) as u64,
            offset: 0,
            current: 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()
            },
            current: 0,
            _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()
    }

    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 = apply_direction::<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;
        }

        apply_direction::<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())
    }
}