yacll 0.1.0

Yet Another Curses-Like Library
Documentation
/*!
A library designed to provide a curses alternative.

Explaining things is hard, so here's an example:
```
use yacll::Yogurt;

fn main() -> yacll::Result<()> {
    let mut y = Yogurt::new();
    // does not add newline
    y.print("Hello, world!\n")?;
    // flush the buffer (print just queues things for execution)
    y.flush()?;
    Ok(())
}
// try running `$ cargo run --example=quickstart` if you have the repository cloned!
```
*/

pub use crossterm::terminal::ClearType as ClrT;
use crossterm::{
    cursor, execute, queue,
    style::Print,
    terminal::{
        disable_raw_mode, enable_raw_mode, size, Clear, EnterAlternateScreen, LeaveAlternateScreen,
        SetSize,
    },
};
use std::{
    fmt::Display,
    io::{stdout, Stdout, Write},
};
use thiserror::Error;
pub mod stylize;

/// **Y**et An**o**ther Data Stora**g**e **U** St**r**uc**t**. Main struct for methods.
///
/// Unless a method specifies it flushes the buffer, calling the [`flush`](Yogurt::flush) method will be required for the changes to take effect.
#[derive(Debug)]
pub struct Yogurt {
    stdout: Stdout,
    /// Terminal size before entering alternate mode
    size: Option<Point>,
}

impl Default for Yogurt {
    fn default() -> Self {
        Yogurt::new()
    }
}

impl Yogurt {
    /// Print a string to the cursor location.
    pub fn print(&mut self, displ: impl Display) -> Result<()> {
        queue! {
            self.stdout,
            Print(displ),
        }?;
        Ok(())
    }
    /// Move the cursor to the specified point.
    pub fn mv(&mut self, point: impl Into<Point>) -> Result<()> {
        let point = Into::<Point>::into(point);
        queue! {
            self.stdout,
            cursor::MoveTo(point.x, point.y),
        }?;
        Ok(())
    }
    /// Move the cursor to the specified point and print.
    pub fn mv_print(&mut self, point: impl Into<Point>, displ: impl Display) -> Result<()> {
        self.mv(point)?;
        self.print(displ)?;
        Ok(())
    }
    /// Flush the buffer.
    pub fn flush(&mut self) -> Result<()> {
        self.stdout.flush()?;
        Ok(())
    }
    /// Create a new yogurt.
    pub fn new() -> Self {
        Yogurt {
            stdout: stdout(),
            size: None,
        }
    }
    /// Enter "alternate mode." See also [`leave_alt`](Yogurt::leave_alt).
    ///
    /// In alternate mode, the following things are true:
    ///
    /// - input will be avaliable imideately to the program (not line buffered)
    /// - special keys will not be processed (`^C` won't do anything!)
    /// - newline will not be proccessed
    /// - an "alternate screen" will be entered, which has no scroll area and any changes made to the screen will not be preserved when switching back to the main screen.
    ///
    /// In effect, alternate mode just enters an alternate screen and enables terminal raw mode.
    ///
    /// This function also flushes the buffer.
    pub fn enter_alt(&mut self) -> Result<()> {
        self.size = Some(Point::from(size()?));
        execute! {
            self.stdout,
            EnterAlternateScreen
        }?;
        enable_raw_mode()?;
        Ok(())
    }
    /// Leave "alternate mode." See also [`enter_alt`](Yogurt::enter_alt).
    ///
    /// This function also flushes the buffer.
    pub fn leave_alt(&mut self) -> Result<()> {
        if let Some(size) = self.size.take() {
            execute! {
                self.stdout,
                LeaveAlternateScreen,
                SetSize(size.x, size.y),
            }?;
            disable_raw_mode()?;
            Ok(())
        } else {
            Err(Yarr::NotInAltMode)
        }
    }
    /// Clear the screen.
    pub fn clear(&mut self, cleartype: ClrT) -> Result<()> {
        queue! {
            self.stdout,
            Clear(cleartype),
        }?;
        Ok(())
    }
}

/// A point on the screen.
#[derive(Debug)]
pub struct Point {
    pub x: u16,
    pub y: u16,
}

impl From<(u16, u16)> for Point {
    /// Convert a tuple to a point. The first tuple value is interpreted as `x`, while the second is interpreted as `y`.
    fn from(t: (u16, u16)) -> Self {
        Point { x: t.0, y: t.1 }
    }
}

impl From<Point> for (u16, u16) {
    fn from(point: Point) -> Self {
        (point.x, point.y)
    }
}

/// **Y**et **A**nother E**rr**or enum.
#[non_exhaustive]
#[derive(Error, Debug)]
pub enum Yarr {
    /// An IO error.
    #[error(transparent)]
    IO(#[from] std::io::Error),
    /// Tried to leave alternate mode, but wasn't in it already.
    #[error("tried to leave alternate mode, but wasn't in it already")]
    NotInAltMode,
}

/// Result with the given type and an instance of `Yarr`.
pub type Result<T> = core::result::Result<T, Yarr>;