Building a terminal application with tui-rs

# What is `tui-rs` 
`tui-rs` is a Rust library for building terminal applications. It provides several builtin widgets including: `List`, `Table`, `Paragraph` etc, which meet most of use cases. There is a [demo](https://github.com/fdehau/tui-rs) from the master repository.

# Features

* The crate supports 4 backends(`termion`, `rustbox`, `crossterm` and `pancurses`) to draw to terminal. But the crate itself doesn't provide interface for input handling, you need to make use of one of the backends cited directly.
* `tui-rs` re-draw new frame for the entire UI on each iteration. But no need to worry about the overhead for highly dynamic contents due to the implementation of the crate itself as well as the speed of Rust.

# Example
Here I'll give an example how to build a simple terminal application with `tui-rs` and backend `termion`.

## initialize

```rust

    // Terminal initialization
    let stdout = io::stdout().into_raw_mode()?;
    let stdout = MouseTerminal::from(stdout);
    let stdout = AlternateScreen::from(stdout);
    let backend = TermionBackend::new(stdout);
    let mut terminal = Terminal::new(backend)?;
    terminal.hide_cursor()?;
```

## handle event loop and application states

```rust
    loop {
        terminal.draw(|mut f| {
         // contents of the new frame
         // ...
         });

         // event handling
         // ... 
    }
```

### draw table
* style: here are the [colors](https://docs.rs/tui/0.7.0/tui/style/enum.Color.html) provided.
   
```rust
      let selected_style = Style::default().fg(Color::Yellow).modifier(Modifier::BOLD);
      let normal_style = Style::default().fg(Color::White);
```

* contents: `hearder` and `rows` will be used to construct `Table`. `Iterator` should be implemented for both of them.
  
```rust
      let header = ["Header1", "Header2", "Header3"];
      let rows = app.items.iter().enumerate().map(|(i, item)| {
          if i == app.selected {
             Row::StyledData(item.into_iter(), selected_style)
          } else {
             Row::StyledData(item.into_iter(), normal_style)
          }
      });

```

* table: here is [API](https://docs.rs/tui/0.7.0/tui/widgets/struct.Table.html#method.new) for `Table` widget. We need to set up the [layout](https://docs.rs/tui/0.7.0/tui/layout/index.html) and then pass the area for table in `.render()`.

```rust
        let rects = Layout::default()
            .constraints([Constraint::Percentage(100)].as_ref())
            .margin(5)
            .split(f.size());
        Table::new(header.into_iter(), rows)
            .block(Block::default().borders(Borders::ALL).title("Table"))
            .widths(&[
                Constraint::Percentage(50),
                Constraint::Length(30),
                Constraint::Max(10),
            ])
            .render(&mut f, rects[0]);
```

### event handling

* implement event wrapper with `mpsc::channal()` and `io::stdin()`

```rust
        let (tx, rx) = mpsc::channel();
        let input_handle = {
            let tx = tx.clone();
            thread::spawn(move || {
                let stdin = io::stdin();
                for evt in stdin.keys() {
                    match evt {
                        Ok(key) => {
                            if let Err(_) = tx.send(Event::Input(key)) {
                                return;
                            }
                            if key == config.exit_key {
                                return;
                            }
                        }
                        Err(_) => {}
                    }
                }
            })
        };
```
* events detection

```rust
        match events.next()? {
            Event::Input(key) => match key {
                Key::Char('q') => {
                    break;
                }
                Key::Down => {
                    app.selected += 1;
                    if app.selected > app.items.len() - 1 {
                        app.selected = 0;
                    }
                }
                Key::Up => {
                    if app.selected > 0 {
                        app.selected -= 1;
                    } else {
                        app.selected = app.items.len() - 1;
                    }
                }
                _ => {}
            },
            _ => {}
        };
```
### result

<img width="1379" alt="Screen Shot 2019-12-14 at 15.48.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537091/6825d696-f1ec-78c2-1469-202e09f31ccd.png">

# Conclusion
`tui-rs` make it easy to build a terminal application. You can not only build a GUI with the provided widgets, but also write your own to achieve highly-interactive features.