text_box 0.2.4

Create useful messages in console with text boxes using 'termion'.
Documentation
//! #text_box
//! 
//! Show useful messages in boxes in console.

extern crate termion;

use termion::cursor;
use termion::raw::IntoRawMode;

use std::io::{Write, stdout};
use std::fmt;

pub mod utils {
    use termion::{clear, cursor};
    use termion::raw::IntoRawMode;

    use std::io::{Write, stdout};

    /// Clear all screen function simplified.
    /// 
    /// # Example
    /// 
    /// You can call this function like this:
    /// ```
    /// use text_box::utils;
    /// 
    /// fn main() {
    ///     utils::clear_screen();
    /// }
    /// ```
    /// 
    /// or like this:
    /// ```
    /// use text_box::utils::clear_screen;
    /// 
    /// fn main() {
    ///     clear_screen();
    /// }
    /// ```
    pub fn clear_screen() {
        let mut stdout = stdout()
            .into_raw_mode()
            .unwrap();
        write!(stdout, "{}", clear::All).unwrap();
    }

    /// Cursor goto function simplified.
    /// 
    /// # Example
    /// 
    /// You can call this function like this:
    /// ```
    /// use text_box::utils;
    /// 
    /// fn main() {
    ///     utils::goto(1, 1);
    /// }
    /// ```
    /// 
    /// or like this:
    /// ```
    /// use text_box::utils::goto;
    /// 
    /// fn main() {
    ///     goto(1, 1);
    /// }
    /// ```
    pub fn goto(x: u16, y: u16) {
        let mut stdout = stdout()
            .into_raw_mode()
            .unwrap();
        write!(stdout, "{}", cursor::Goto(x, y)).unwrap();
    }
}

#[derive(Default)]
/// TextBox struct definition.
pub struct TextBox {
    x: u8, y: u8,
    width: u8, height: u8,
    border: u8,
    title: String,
    lines: Vec<String>
}

impl fmt::Display for TextBox {

    /// Added easy fmt::Display to print a TextBox just with print! println!
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut stdout = stdout()
            .into_raw_mode()
            .unwrap();
        let x = self.x as u16;
        let y = self.y as u16;
        let width = self.width as u16;
        let height = self.height as u16;

        let b: Vec<char>;
        if self.border == 1 {
            b = vec!['', '', '', '', '', ''];
        } else if self.border == 2 {
            b = vec!['', '', '', '', '', ''];
        } else {
            b = vec![' ', ' ', ' ', ' ', ' ', ' '];
        }

        write!(stdout, "{}", cursor::Goto(x, y)).unwrap();
        write!(f, "{}", b[2])?;
        write!(stdout, "{}", cursor::Goto(x + width + 1, y)).unwrap();
        write!(f, "{}", b[3])?;
        write!(stdout, "{}", cursor::Goto(x + width + 1, y + height + 1)).unwrap();
        write!(f, "{}", b[4])?;
        write!(stdout, "{}", cursor::Goto(x, y + height + 1)).unwrap();
        write!(f, "{}", b[5])?;

        for i in 0..width {
            write!(stdout, "{}", cursor::Goto(x + 1 + i, y)).unwrap();
            write!(f, "{}", b[0])?;
            write!(stdout, "{}", cursor::Goto(x + 1 + i, y + height + 1)).unwrap();
            write!(f, "{}", b[0])?;
        }
        
        for i in 0..height {
            write!(stdout, "{}", cursor::Goto(x, y + 1 + i)).unwrap();
            write!(f, "{}", b[1])?;
            if (i as usize) < self.lines.len() {
                write!(f, "{}", self.lines[i as usize])?;
            }
            write!(stdout, "{}", cursor::Goto(x + width + 1, y + 1 + i)).unwrap();
            write!(f, "{}", b[1])?;
        }
        write!(stdout, "{}", cursor::Goto(x + 1, y)).unwrap();
        write!(f, "{}", self.title)
    }
}

impl TextBox {

    /// Creates a new TextBox with the specified params.
    /// 
    /// # Example
    /// 
    /// ```
    /// let textbox = TextBox::new(
    ///   1, 1,                                                 // (x, y) coordinates.
    ///   15, 6,                                                // (width, height) box size.
    ///   2,                                                    // border type.
    ///   "DANGER",                                             // Box title.
    ///   "Some children are playing with dangerous weapons."   // Box text.
    /// ).unwrap();
    /// ```
    /// 
    /// This will print out:
    /// 
    /// ```plain
    /// ╔DANGER═════════╗
    /// ║Some children  ║
    /// ║are playing    ║
    /// ║with dangerous ║
    /// ║weapons.       ║
    /// ║               ║
    /// ║               ║
    /// ╚═══════════════╝
    /// ```
    /// 
    /// *Note: Termion use one-based coordinates, this means that the first point is (1, 1) at upside left corner.*
    pub fn new(x: u8, y: u8, width: u8, height: u8, border: u8, title: &str, text: &str) -> Option<TextBox> {
        if title.len() as u8 > width {
            eprintln!("ERROR: Title '{}' is too long for given width!", title);
            return None;
        }

        let mut lines: Vec<String> = Vec::new();
        let mut line = String::new();
        for word in text.split(' ') {
            if word.len() as u8 > width {
                eprintln!("ERROR: Word '{}' is too long for given width!", word);
                return None;
            } else {
                if (line.len() + word.len()) as u8 > width || word == "\n"  {
                    lines.push(line);
                    line = String::new();
                    if word != "\n" {
                        line.push_str(word);
                        line.push(' ');
                    }
                } else {
                    line.push_str(word);
                    line.push(' ');
                }
            }
        }
        lines.push(line);
        if lines.len() as u8 > height {
            eprintln!("ERROR: Total lines are greater than box height {} > {}!", lines.len(), height);
            return None;
        }

        Some( TextBox {
            x,
            y,
            width,
            height,
            border,
            title: title.to_string(),
            lines })
    }
}