termit-ui 0.0.1

Terminal UI with GUI-like layouts
Documentation
#[macro_use]
extern crate log;
extern crate chrono;
extern crate termit_ion as termion;
extern crate unicode_segmentation;

pub mod color;
pub mod command;
pub mod geometry;
pub mod graphics;
pub mod input;
pub mod mion;
pub mod screen;
pub mod widget;

use self::widget::*;
use crate::command::*;
use crate::geometry::*;
use crate::input::*;
use crate::screen::Screen;
use std::fmt;
use std::io;
use std::mem;
use std::sync::mpsc::{Receiver, Sender, TryRecvError};

pub trait GUI {
    fn react(&mut self) -> io::Result<()>;
}

pub trait GrinApp {
    fn run(&mut self) -> io::Result<()>;
    fn quit(&mut self);
}

pub struct Grin<GUI, Root> {
    command: Sender<GrinCommand>,
    input: Receiver<Result<GrinInput, io::Error>>,
    quitting: bool,
    gui: GUI,
    size: Point,
    root: Root,
    inputs: Vec<GrinInput>,
}

impl<GUI, Root> Grin<GUI, Root> {
    pub fn new(
        gui: GUI,
        command: Sender<GrinCommand>,
        input: Receiver<Result<GrinInput, io::Error>>,
        root: Root,
    ) -> Self {
        Self {
            command,
            input,
            gui,
            root,
            quitting: false,
            size: Default::default(),
            inputs: vec![],
        }
    }
}

impl<GUI, Root> Grin<GUI, Root>
where
    Root: Widget,
{
    fn receive_inputs(&mut self) -> io::Result<()> {
        loop {
            let input = self.input.try_recv();

            match input {
                Err(TryRecvError::Empty) => break,
                Err(TryRecvError::Disconnected) => {
                    error!("ui disconnected");
                    self.quit_priv();
                    break;
                }
                Ok(input) => {
                    match input {
                        Ok(GrinInput::Resize(size)) => self.size = size,
                        Ok(GrinInput::Key(KeyEvent {
                            key: Key::Char(' '),
                            ..
                        })) => {
                            info!("quit!");
                            self.quit_priv();
                            break;
                        }
                        _ => {}
                    }
                    self.log(format!("Input: {:?}", input));
                    if let Ok(input) = input {
                        self.inputs.push(input);
                    }
                }
            }
        }
        Ok(())
    }
    fn handle_inputs(&mut self) -> io::Result<()> {
        let inputs = mem::replace(&mut self.inputs, vec![]);

        let mut screen = Screen::new(self.size);
        {
            self.root.arrange(screen.get_area());
            self.root.consume(inputs);
            self.root.render(&mut screen)?;
        }

        for drawing in screen.into_iter() {
            self.command.send(GrinCommand::Draw(drawing)).map_err(|e| {
                io::Error::new(
                    io::ErrorKind::Other,
                    format!("Failed to send command,  {}", e),
                )
            })?;
        }
        Ok(())
    }
}

impl<UI, Root> GrinApp for Grin<UI, Root>
where
    UI: GUI,
    Root: Widget,
{
    fn run(&mut self) -> io::Result<()> {
        self.send(GrinCommand::ShowCursor(false));

        while !self.quitting {
            self.gui.react()?;
            self.receive_inputs()?;
            if !self.inputs.is_empty() {
                self.handle_inputs()?;
            }
        }
        Ok(())
    }

    fn quit(&mut self) {
        self.quit_priv()
    }
}

impl<UI, Root> Grin<UI, Root> {
    fn quit_priv(&mut self) {
        self.quitting = true;
        self.send(GrinCommand::Quit);
    }

    fn send(&mut self, event: GrinCommand) {
        let dbg = format!("{:?}", event);
        self.command
            .send(event)
            .unwrap_or_else(|e| error!("unable to send grin event {:?}. {}", dbg, e))
    }

    fn log<T: fmt::Display>(&mut self, what: T) {
        info!("{}", what)
    }
}

impl<UI, Root> Drop for Grin<UI, Root> {
    fn drop(&mut self) {
        self.quit_priv()
    }
}