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

```
+-----------------------------+
|  Input (Constraint::Length) |
+-----------------------------+
|                             |
|  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
