royal_rust 0.1.0

A read-only TUI for royalroad, with a corrisponding API
Documentation
use ratatui::{
    buffer::Buffer,
    layout::{Alignment, Rect},
    style::Color,
    text::Line,
    widgets::{StatefulWidget, Widget},
};

use tui_big_text::BigText;

pub struct ReadingWindow {
    margin: (u16, u16),
}

#[derive(Default)]
pub struct ReadingWindowState {
    pub text: Vec<String>,
    pub is_reading: bool,
    pub line: u16,
}

impl ReadingWindowState {
    pub fn wrap_lines(
        &self,
        width: u16,
        height: u16,
        text: &Vec<String>,
        margin: (u16, u16),
    ) -> Vec<String> {
        let mut wrapped_lines = vec![String::new(); margin.1 as usize];
        let mut text_line = self.line;
        let mut line_words = Vec::new();
        while wrapped_lines.len() < height as usize && text_line < text.len() as u16 {
            let mut cur_line = String::from_utf8(vec![b' '; margin.0 as usize]).unwrap();
            if line_words.is_empty() {
                line_words = text[text_line as usize].split(' ').rev().collect();
            }
            while !line_words.is_empty()
                && line_words.last().unwrap().len() + cur_line.len()
                    < (width as usize - margin.0 as usize * 2)
            {
                if !cur_line.is_empty() {
                    cur_line.push(' ');
                }
                cur_line.push_str(line_words.pop().unwrap());
            }
            wrapped_lines.push(cur_line);
            if line_words.is_empty() {
                text_line += 1;
                wrapped_lines.push(String::new());
            }
        }
        wrapped_lines
    }

    pub fn new(text: Vec<String>, line: u16, is_reading: bool) -> ReadingWindowState {
        Self {
            text,
            line,
            is_reading,
        }
    }
}

impl ReadingWindow {
    pub fn new(margin: (u16, u16)) -> ReadingWindow {
        Self { margin }
    }
}

impl StatefulWidget for ReadingWindow {
    type State = ReadingWindowState;
    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
        if state.is_reading {
            state.line = state.line.max(0);
            state.line = state.line.min(state.text.len() as u16 - 2);
            let lines = state.wrap_lines(area.width - 2, area.height - 2, &state.text, self.margin);
            for (i, line) in lines.into_iter().enumerate() {
                buf.set_line(
                    area.x + 1,
                    area.y + i as u16,
                    &Line::styled(line, Color::White),
                    area.width,
                );
            }
        } else {
            BigText::builder()
                .style(Color::White)
                .alignment(Alignment::Center)
                .lines(vec![
                    Line::styled("Royal", Color::White),
                    Line::styled("Rust", Color::White),
                ])
                .build()
                .unwrap()
                .render(
                    Rect {
                        x: area.x,
                        y: (area.y + area.height) / 2 - 7,
                        height: area.height,
                        width: area.width,
                    },
                    buf,
                );
        }
    }
}