hollowtea 0.0.1

A hollowed TUI framework 👻
Documentation
# hollowtea

> A hollowed TUI framework.

tl;dr: Hollow Tea is your choice to go build TUI if love [Bubbletea](https://github.com/charmbracelet/bubbletea) and Rust.

🚧 hollowtea is under development.

Originally, Hollow Tea was created to serve [Boo text editor](https://github.com/raphamorim/boo), however I started to use in other projects, so I have decided to move to it's own repository and create a proper documentation.

Disclaimer: It is not a project from the [Charm.sh](https://charm.sh/) but the name is a tribute to the famous [Bubbletea](https://github.com/charmbracelet/bubbletea).

## Motivation

Hollow Tea was created to make TUI creating much more simple and intuitive. You shouldn't need to know any other dependencies besides Hollow Tea to create your TUI app.

## Hollow Tea follows "The Elm Architecture"

These three concepts are the core of The Elm Architecture:

- Model: the state of your application.
- View: a way to turn your state into HTML.
- Update: a way to update your state based on messages.

Let's see it in practice, by doing a quick TUI app.

## Quick TUI app: A color picker

<img width="400px" src="./assets/demo/demo-color-picker.gif" alt="Demo color picker" />

The following code:

```rust
use hollowtea::styles::*;
use hollowtea::{
    hollows::{Hollow, Text},
    term::event::{Event, KeyCode},
    Application, Command, Message, Model,
};

struct ColorPicker<'a> {
    current: usize,
    items: Vec<(&'a str, Color, Color)>,
}

impl ColorPicker<'_> {
    fn new() -> ColorPicker<'static> {
        let items = vec![
            ("yellow", Color::LightYellow, Color::Yellow),
            ("red", Color::LightRed, Color::Red),
            ("blue", Color::LightBlue, Color::Blue),
            ("magenta", Color::LightMagenta, Color::Magenta),
            ("green", Color::LightGreen, Color::Green),
            ("cyan", Color::LightCyan, Color::Cyan),
            ("black", Color::LightBlack, Color::Black),
            ("white", Color::LightWhite, Color::White),
        ];
        ColorPicker { current: 0, items }
    }
}

impl Model for ColorPicker<'_> {
    fn init(&self) -> Vec<Command> {
        vec![]
    }

    fn update(&mut self, message: Message) -> Vec<Command> {
        if let Some(Event::Key(key_event)) = message.as_ref::<Event>() {
            match key_event.code {
                KeyCode::Right => {
                    if self.current >= self.items.len() - 1 {
                        self.current = 0;
                    } else {
                        self.current += 1;
                    }
                }
                KeyCode::Left => {
                    if self.current == 0 {
                        self.current = self.items.len() - 1;
                    } else {
                        self.current -= 1;
                    }
                }
                KeyCode::Esc => {
                    return vec![Command::Quit];
                }
                _ => {}
            }
        }
        vec![]
    }

    fn view(&self) -> Box<dyn Hollow> {
        let mut content = String::default();
        if let Some(current) = self.items.get(self.current) {
            content.push_str(
                &StyleSheet::new()
                    .add(Style::TextStyle(TextStyle::Bold))
                    .add(Style::TextColor(Color::LightBlack))
                    .build("Pick a color: "),
            );

            let idx = self.current + 1;
            let indicator = String::from_utf8(vec![b' '; idx]).unwrap_or_default();
            content.push_str(
                &StyleSheet::new()
                    .add(Style::BackgroundColor(current.1.to_owned()))
                    .build(indicator),
            );

            let indicator =
                String::from_utf8(vec![b' '; self.items.len() - idx]).unwrap_or_default();
            content.push_str(
                &StyleSheet::new()
                    .add(Style::BackgroundColor(current.2.to_owned()))
                    .build(indicator),
            );
            content.push_str(" ❮ ");
            content.push_str(
                &StyleSheet::new()
                    .add(Style::TextStyle(TextStyle::Bold))
                    .add(Style::TextColor(current.2.to_owned()))
                    .build(current.0),
            );
            content.push_str(" ❯ ");
        }

        Text::new(content)
    }
}

fn main() {
    Application {
        inline: true,
        ..Application::default()
    }
    .run(&mut ColorPicker::new())
    .expect("failed to run");

    println!("app exited");
}
```


## TODO

- [x] `examples/color-picker`
- [ ] `examples/textarea`
- [ ] `examples/list`
- [ ] `examples/table`
- [ ] `examples/progress`
- [ ] `examples/grid`
- [ ] Interup
- [ ] Tutorial video building some TUI apps.