# 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.