scrin 0.1.80

A terminal UI toolkit with panes, widgets, overlays, animations, and Aisling-powered effects/loaders.
Documentation
use crossterm::{
    cursor,
    event::{self, Event, KeyCode, KeyModifiers},
    execute,
    terminal::{self, EnterAlternateScreen, LeaveAlternateScreen},
};
use scrin::core::buffer::Buffer;
use scrin::core::color::Color;
use scrin::core::rect::Rect;
use scrin::effects::EffectPlayer;
use scrin::status_bar::{StatusBar, StatusBarPosition};
use scrin::widgets::block::{Block, BorderStyle};
use scrin::widgets::Widget;
use std::io::{self, Write};

fn main() -> io::Result<()> {
    let mut stdout = io::stdout();
    terminal::enable_raw_mode()?;
    execute!(stdout, EnterAlternateScreen, cursor::Hide)?;

    let kinds = EffectPlayer::all_kinds();
    let mut current_index = 0;
    let text = "scrin";
    let mut player = EffectPlayer::new(kinds[current_index], text);
    let mut tick: usize = 0;
    let mut status = StatusBar::new().with_position(StatusBarPosition::Bottom);

    let result = run_loop(
        &mut stdout,
        &mut current_index,
        &mut player,
        &mut tick,
        &mut status,
        text,
    );

    execute!(stdout, cursor::Show, LeaveAlternateScreen)?;
    terminal::disable_raw_mode()?;
    result
}

fn run_loop(
    stdout: &mut io::Stdout,
    current_index: &mut usize,
    player: &mut EffectPlayer,
    tick: &mut usize,
    status: &mut StatusBar,
    text: &str,
) -> io::Result<()> {
    let kinds = EffectPlayer::all_kinds();

    loop {
        let (cols, rows) = terminal::size()?;
        let mut buffer = Buffer::new(cols as usize, rows as usize);

        let outer = Rect::new(0, 0, cols, rows - 2);
        let block = Block::new("Effect Demo — left/right switch, r restart, q quit")
            .with_borders(BorderStyle::Rounded)
            .with_border_color(Color::rgb(88, 166, 255));
        block.render(&mut buffer, outer);

        let inner = block.inner(outer);
        let effect_area = Rect::new(
            inner.x,
            inner.y + 1,
            inner.width,
            inner.height.saturating_sub(2),
        );

        let info_text = format!(
            "  [{}/{}] {} ({} frames)",
            *current_index + 1,
            kinds.len(),
            player.name(),
            player.total_frames()
        );
        buffer.set_str(
            inner.x as usize,
            inner.y as usize,
            &info_text,
            Color::rgb(255, 178, 72),
            None,
        );

        player.set_frame(*tick);
        player.render_to_buffer(&mut buffer, effect_area);

        status.set_left(
            &format!("Effect: {}", player.name()),
            Color::rgb(88, 166, 255),
        );
        status.set_right(
            &format!("Frame {}/{}", *tick + 1, player.total_frames()),
            Color::rgb(139, 148, 158),
        );
        let status_area = Rect::new(0, rows - 1, cols, 1);
        status.render(&mut buffer, status_area);

        write!(stdout, "\x1b[H{}", buffer.to_ansi_string())?;
        stdout.flush()?;

        if event::poll(std::time::Duration::from_millis(80))? {
            if let Event::Key(key) = event::read()? {
                match (key.modifiers, key.code) {
                    (KeyModifiers::CONTROL, KeyCode::Char('c'))
                    | (KeyModifiers::CONTROL, KeyCode::Char('q'))
                    | (_, KeyCode::Char('q')) => return Ok(()),
                    (_, KeyCode::Right) | (_, KeyCode::Char('l')) => {
                        *current_index = (*current_index + 1) % kinds.len();
                        *player = EffectPlayer::new(kinds[*current_index], text);
                        *tick = 0;
                    }
                    (_, KeyCode::Left) | (_, KeyCode::Char('h')) => {
                        *current_index = if *current_index == 0 {
                            kinds.len() - 1
                        } else {
                            *current_index - 1
                        };
                        *player = EffectPlayer::new(kinds[*current_index], text);
                        *tick = 0;
                    }
                    (_, KeyCode::Char('r')) => {
                        *tick = 0;
                        *player = EffectPlayer::new(kinds[*current_index], text);
                    }
                    _ => {}
                }
            }
        }

        *tick = tick.wrapping_add(1) % player.total_frames().max(1);
    }
}