typo-eq 0.2.0

Typo-eq is a typing training app for other languages. All it needs is a dictionary for words and their translations.
Documentation
use std::io::{stdout, Write};

use crossterm::{terminal::{EnterAlternateScreen, enable_raw_mode, disable_raw_mode, LeaveAlternateScreen, Clear, ClearType}, style::{Color, SetForegroundColor, ResetColor}, execute, cursor::{MoveTo, position, MoveToRow, Hide, Show}, event::{PushKeyboardEnhancementFlags, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags}};

use super::util::term_center;

pub struct Renderer;

#[derive(Default)]
pub enum TextAlign {
    #[default]
    Left,
    Center,
    Right
}

impl Renderer {
    pub fn init () -> Self {
        enable_raw_mode()
        .expect("This app requires raw mode to be available in order to function correctly");
        let mut stdout = stdout();
        execute!(
            stdout,
            EnterAlternateScreen,
            Hide,
            PushKeyboardEnhancementFlags(
                KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
                    | KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES
                    | KeyboardEnhancementFlags::REPORT_EVENT_TYPES
            )
        ).unwrap();
        Self
    }
    pub fn exit(self: &Self) {
        let mut stdout = stdout();
        execute!(
            stdout,
            PopKeyboardEnhancementFlags,
            Show,
            LeaveAlternateScreen,
        ).unwrap();
    
        disable_raw_mode().unwrap();
    }
    pub fn reset_color(self: &Self) {
        let mut stdout = stdout();
        execute!(
            stdout,
            ResetColor,
        ).expect("Could not reset terminal colours");
    }
    pub fn print(self: &Self, word: &str, color: Option<Color>, background_color: Option<Color>) {
        let (row, col) = position().unwrap();
        self.print_at(
            word, 
            (row, col), 
            TextAlign::Left, 
            color, background_color, 
            Some(Clear(ClearType::CurrentLine))
        )
    }
    pub fn print_at_center_default(self: &Self, word: &str) {
        self.print_at(
            word, term_center(), 
            TextAlign::Center, 
            None, None, 
            Some(Clear(ClearType::CurrentLine))
        )
    }
    pub fn print_at_center(self: &Self, word: &str, offset: (i16, i16), align: Option<TextAlign>, color: Option<Color>, background_color: Option<Color>, clear: Option<Clear>) {
        let center = term_center();
        self.print_at(
            word, 
            (
                (center.0 as i16 + offset.0) as u16, 
                (center.1 as i16 + offset.1) as u16
            ), 
            align.unwrap_or(TextAlign::Center), 
            color, background_color, 
            clear
        )
    }
    pub fn print_at(self: &Self, word: &str, position: (u16, u16), align: TextAlign, color: Option<Color>, background_color: Option<Color>, clear: Option<Clear>) {
        let mut stdout = stdout();
        if let Some(color) = color {
            execute!(
                stdout,
                SetForegroundColor(color),
            ).expect("Could not set text colour");
        }
        if let Some(color) = background_color {
            execute!(
                stdout,
                SetForegroundColor(color),
            ).expect("Could not set background colour");
        }
        let x ;
        let y = position.1;
        match align {
            TextAlign::Left => {
                x = position.0;
            }
            TextAlign::Center => {
                x = position.0 - word.chars().count() as u16 / 2;
            }
            TextAlign::Right => {
                x = position.0 - word.chars().count() as u16;
            }
        }
        execute!(
            stdout,
            MoveTo(x, y),
        ).expect(format!("Could not move to position {} {}", x, y).as_str());
        if let Some(clear) = clear {
            execute!(
                stdout,
                clear,
            ).expect("Could not clear line");
        }
        write!(
            stdout,
            "{}", word,
        ).expect("Could not write to stdout");
        stdout.lock().flush().unwrap();
        execute!(
            stdout,
            ResetColor
        ).expect("Could not reset text colour after render");
    }
    pub fn clear_line_at_center(self: &Self, offset: (i16, i16)) {
        let center = term_center();
        self.clear_line_at(
            (
                (center.0 as i16 + offset.0) as u16, 
                (center.1 as i16 + offset.1) as u16
            ), 
        )
    }
    pub fn clear_down_from_center_at(self: &Self, offset_y: i16) {
        let center = term_center();
        let mut stdout = stdout();
        execute!(
            stdout,
            MoveToRow((center.1 as i16 + offset_y) as u16),
            Clear(ClearType::FromCursorDown)
        ).expect("Could not clear lines down from center")
    }
    pub fn clear_line_at(self: &Self, position: (u16, u16)) {
        let mut stdout = stdout();
        execute!(
            stdout,
            MoveTo(position.0, position.1),
            Clear(ClearType::CurrentLine)
        ).expect("Could not clear line")
    }
}