# cocotte
A convenient way to build [Ratatui](https://ratatui.rs) terminal applications by composing independent sub-apps.
## Inspiration
--- You look sad, Grug. What is the matter?
--- Grug likes Ratatui. But Ratatui big! When Grug uses Ratatui, it takes all place in Grug's brain! Grug cannot do the things anymore!
--- Well Grug, you know, it's normal, tuis are kinda complicated when you think about it and...
--- Grug do not care! Grug only wants to care about what the little pieces do!
--- Mhhm. I think I know what you need, Grug. You need a Cocotte!
--- What is Cocotte?
--- A Cocotte is a good way to make a good Ratatui.
--- Is it dumb?
--- Yes Grug, do not worry.
## Presentation
Split your TUI into vertical panes. Each pane handles its own input, rendering and manages its state. Each pane also manages the shared app state.
```
+-----------------------------+
| |
| Browser (Constraint::Fill) |
| |
+-----------------------------+
```
## Usage
Add to `Cargo.toml`:
```toml
[dependencies]
cocotte = "0.1"
```
Define an event type, a state type, one or more `SubApp` implementations, then wire them together with `define_sub_apps!`:
```rust
use cocotte::{define_sub_apps, SubApp};
use cocotte::eyre::Result;
use cocotte::ratatui::Frame;
use cocotte::ratatui::layout::{Constraint, Rect};
use cocotte::ratatui::widgets::{Block, Borders};
use crossterm::event::{self, Event, KeyCode, KeyEvent};
#[derive(Clone, Debug)]
enum AppEvent {
Tick,
Key(KeyEvent),
}
#[derive(Default)]
struct State {
should_quit: bool,
}
struct Pane;
impl Pane {
fn new() -> Self {
Self
}
}
impl SubApp<AppEvent, State> for Pane {
fn handle_input(&mut self, event: &mut AppEvent, state: &mut State) {
if let AppEvent::Key(KeyEvent {
code: KeyCode::Char('q') | KeyCode::Esc,
..
}) = event
{
state.should_quit = true;
}
}
fn render(&self, frame: &mut Frame, area: Rect, _state: &mut State) {
let block = Block::default().borders(Borders::ALL).title("Pane");
frame.render_widget(block, area);
}
fn constraints(&self) -> Constraint {
Constraint::Fill(1)
}
}
define_sub_apps! {
event = AppEvent;
state = State;
Pane(Pane) => Pane::new(),
}
fn main() -> Result<()> {
let mut app = make_app()?;
let mut state = State::default();
let mut setup = AppEvent::Tick;
app.handle_input(&mut setup, &mut state);
while !state.should_quit {
app.draw(&mut state)?;
if let Event::Key(key) = event::read()? {
let mut event = AppEvent::Key(key);
app.handle_input(&mut event, &mut state);
}
}
Ok(())
}
```
See `examples/simple` (`cargo run --example simple`) for a full file-browser example.
## References
Grug by https://grugbrain.dev/
## License
MIT