animate 0.1.0

Animation library for Ratatui
Documentation
#[cfg(feature = "ratatui")]
mod enabled {
    use std::time::Duration;

    use animate_macros::animate;
    use crossterm::event::{self, Event, KeyCode};
    use ratatui::{
        Frame,
        layout::{Alignment, Constraint, Direction, Layout},
        style::{Color, Style},
        text::{Line, Span},
        widgets::{Block, Borders, Gauge, Paragraph},
    };

    #[animate]
    pub struct Counter {
        #[once(duration = 400, easing = linear)]
        value: u32,
        #[alternate(duration = 800, easing = ease_in_quad)]
        string: String,
    }

    impl Counter {
        pub fn increment(&mut self) {
            self.value += 10;
            self.string.set("i love rust".to_string());
        }

        pub fn decrement(&mut self) {
            self.value -= 10;
            self.string.set("grease is cool".to_string());
        }

        pub fn render(&self, frame: &mut Frame, area: ratatui::layout::Rect) {
            let display = format!("{}; {}", self.value, self.string);
            let gauge_val = self.value.clamp(0, 100) as u16;

            let chunks = Layout::default()
                .direction(Direction::Vertical)
                .constraints([Constraint::Length(3), Constraint::Length(3)])
                .split(area);

            frame.render_widget(
                Gauge::default()
                    .block(Block::default().borders(Borders::ALL).title(" counter "))
                    .gauge_style(Style::default().fg(ratatui::style::Color::Cyan))
                    .percent(gauge_val),
                chunks[0],
            );

            frame.render_widget(
                Paragraph::new(display)
                    .alignment(Alignment::Center)
                    .block(Block::default().borders(Borders::ALL)),
                chunks[1],
            );
        }
    }

    #[animate]
    pub struct ColorBox {
        #[alternate(duration = 600, easing = linear)]
        color: Color,
    }

    const PALETTE: &[Color] = &[
        Color::Rgb(220, 60, 60),
        Color::Rgb(60, 180, 220),
        Color::Rgb(80, 210, 130),
        Color::Rgb(200, 160, 50),
        Color::Rgb(160, 80, 220),
    ];

    impl ColorBox {
        pub fn next_color(&mut self, idx: &mut usize) {
            *idx = (*idx + 1) % PALETTE.len();
            self.color.set(PALETTE[*idx]);
        }

        pub fn render(&self, frame: &mut Frame, area: ratatui::layout::Rect) {
            let c = self.color.get();
            frame.render_widget(
                Paragraph::new(Line::from(vec![Span::raw("  color: ")])).block(
                    Block::default()
                        .borders(Borders::ALL)
                        .title(" color ")
                        .border_style(Style::default().fg(*c)),
                ),
                area,
            );
        }
    }

    pub struct App {
        pub counter: Counter,
        pub color_box: ColorBox,
        pub color_index: usize,
    }

    impl App {
        pub fn new() -> Self {
            Self {
                counter: Counter::new(0, String::from("initial")),
                color_box: ColorBox::new(PALETTE[0]),
                color_index: 0,
            }
        }

        pub fn draw(&self, frame: &mut Frame) {
            let area = frame.area();
            let chunks = Layout::default()
                .direction(Direction::Vertical)
                .margin(1)
                .constraints([
                    Constraint::Length(7),
                    Constraint::Length(3),
                    Constraint::Min(3),
                ])
                .split(area);

            self.counter.render(frame, chunks[0]);
            self.color_box.render(frame, chunks[1]);

            frame.render_widget(
                Paragraph::new(vec![
                    Line::from(""),
                    Line::from("  ↑ / k   increment counter"),
                    Line::from("  ↓ / j   decrement counter"),
                    Line::from("  space   cycle color"),
                    Line::from("  q       quit"),
                    Line::from(""),
                    Line::from(format!("  animating: ",)),
                ])
                .block(Block::default().borders(Borders::ALL).title(" controls ")),
                chunks[2],
            );
        }
    }

    pub fn run() -> Result<(), Box<dyn std::error::Error>> {
        let mut app = App::new();

        ratatui::run(|terminal| {
            loop {
                animate::tick();

                terminal.draw(|frame| {
                    app.draw(frame);
                })?;

                let timeout = Duration::from_millis(8);

                if event::poll(timeout)? {
                    if let Event::Key(key) = event::read()? {
                        match key.code {
                            KeyCode::Char('q') | KeyCode::Esc => break,
                            KeyCode::Up | KeyCode::Char('k') => app.counter.increment(),
                            KeyCode::Down | KeyCode::Char('j') => app.counter.decrement(),
                            KeyCode::Char(' ') => app.color_box.next_color(&mut app.color_index),
                            _ => {}
                        }
                    }
                }
            }
            Ok(())
        })
    }
}

#[cfg(feature = "ratatui")]
fn main() -> Result<(), Box<dyn std::error::Error>> {
    enabled::run()
}

#[cfg(not(feature = "ratatui"))]
fn main() {}