ratatui-image 11.0.6

An image widget for ratatui, supporting sixels, kitty, iterm2, and unicode-halfblocks
Documentation
use std::{fs, time::Duration};

use image::GenericImageView;
use ratatui::{
    Frame,
    crossterm::event::{self, Event, KeyCode, KeyEventKind},
    layout::{Rect, Size},
    style::{Color, Stylize},
    widgets::{Block, Borders, Paragraph},
};
use ratatui_image::{
    picker::Picker,
    sliced::{SignedPosition, SlicedImage, SlicedProtocol},
};

struct App {
    sliced: SlicedProtocol,
    position: SignedPosition,
    background_text: Vec<String>,
    stopped: bool,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut terminal = ratatui::init();

    let picker = Picker::from_query_stdio()?;
    let dyn_img = image::ImageReader::open("./assets/Ada.png")?.decode()?;

    let mut terminal_size = Size::default();
    terminal.draw(|f| {
        terminal_size = f.area().into();
    })?;

    let pixel_size = dyn_img.dimensions();
    let sliced = SlicedProtocol::new(&picker, dyn_img, None)?;

    let mut background_text = format!(
        r#"Protocol: {:?}
font_size: {:?}
pixel size: {:?}
cols/rows: {:?}
terminal: {:?}
---------------
"#,
        picker.protocol_type(),
        picker.font_size(),
        pixel_size,
        (sliced.size().width, sliced.size().height),
        (terminal_size.width, terminal_size.height),
    );
    let source = fs::read_to_string("./examples/sliced.rs")?;
    background_text.push_str(&source);

    background_text = background_text
        .lines()
        .enumerate()
        .map(|(i, line)| format!("{:02}: {}\n", i + 1, line))
        .collect();

    let size = sliced.size();
    let mut app = App {
        sliced,
        position: (0, -((size.height / 2) as i16)).into(),
        background_text: Vec::new(),
        stopped: false,
    };

    app.background_text = background_text.split("\n").map(|s| s.to_string()).collect();

    loop {
        let mut had_event = false;
        if ratatui::crossterm::event::poll(Duration::from_millis(100))? {
            if let Event::Key(key) = event::read()? {
                if key.kind == KeyEventKind::Press {
                    match key.code {
                        KeyCode::Char('q') => {
                            break;
                        }
                        KeyCode::Char('j') => {
                            app.stopped = true;
                            app.position.y += 1;
                            had_event = true;
                        }
                        KeyCode::Char('k') => {
                            app.stopped = true;
                            app.position.y -= 1;
                            had_event = true;
                        }
                        _ => {}
                    }
                }
            }
        }

        if app.stopped && !had_event {
            continue;
        }
        if !app.stopped {
            app.position.y += 1;
        }

        terminal.draw(|f| {
            let inner_height = f.area().height.saturating_sub(2) as i16;
            if app.position.y >= inner_height {
                app.position.y = -(app.sliced.size().height as i16);
            }
            if app.position.y < -(app.sliced.size().height as i16) {
                app.position.y = inner_height - 1;
            }

            ui(f, &app)
        })?;
    }

    ratatui::restore();

    Ok(())
}

fn ui(f: &mut Frame<'_>, app: &App) {
    let area = f.area();
    let block = Block::default()
        .borders(Borders::ALL)
        .title(format!("Scliced imaged on Y position {}", app.position.y))
        .bg(Color::Blue);
    let inner_area = block.inner(area);
    f.render_widget(block, area);

    for i in 0..f.area().height - 2 {
        if i as usize >= app.background_text.len() {
            break;
        }
        let p = Paragraph::new(app.background_text[i as usize].clone());
        f.render_widget(
            p,
            Rect::new(inner_area.x, inner_area.y + i, inner_area.width, 1),
        );
    }

    f.render_widget(SlicedImage::new(&app.sliced, app.position), inner_area);
}