rustey 0.1.2

An implementation of the Elm architecture in Rust
Documentation
# Rustey

TUIs with Ratatui and the Elm architecture.

## Components

The Elm Architecture consists of a small number of components that play nicely
together. The core element is the model. When the application starts the `init`
function is called to retrieve the initial model. After initialization the
`view` function is called to render the user interface. Once rendering is done
the application will wait for events. Events are emitted by the functions the
application is subscribed to. The `subscriptions` function will emit a number
of such functions based on the state of the model. Each function will run in a
thread of it's own and emit events as relevant.

The model is updated by the `update` function as it reacts to messages. After
each update the `subscriptions` function is called again allowing the update of
which subscriptions are runningl.

Apart from the subscription the model may also return a command, which is a
task that will be executed asynchronously using threads. When the task is
complete it may emit an event for the update function to handle.

## Usage

To use Rustey in your project, add `rustey, ratatui` and `crossterm` to your
`Cargo.toml` file:

```sh
cargo add rustey ratatui crossterm
```

The first thing you need to do is to create a model. The model is a struct that
contains the state of your application. You can create a model like this:

```rust
struct Model {
  counter: i32,
}
```

You will then need to define which messages the application can handle. Messages are
the events that the application can react to. You can define messages like this:

```rust
enum Msg {
  Increment,
  Decrement,
  Quit,
}
```

With the model and the messages in place you can define an application struct.

```rust
struct MyApp {}
```

And then implement the `RusteyApp` trait for your application. The `RusteyApp` trait for the application struct:

```rust
impl RusteyApp<Model, Msg> for MyApp {
    fn init(&self) -> (Model, Cmd<Msg>) {
        (Model { counter: 0 }, Cmd::None)
    }

    fn subscriptions(&self, _model: &Model) -> Subscriptions<Msg> {
        vec![]
    }

    fn update(&self, model: &mut Model, msg: Msg, quit_flag: &QuitFlag) -> Cmd<Msg> {
        match msg {
            Msg::Increment => {
                model.counter += 1;
                Cmd::None
            }
            Msg::Decrement => {
                model.counter -= 1;
                Cmd::None
            }
            Msg::Quit => {
                quit_flag.raise();
                Cmd::None
            }
        }
    }

    fn view(&self, frame: &mut Frame, model: &mut Model) {
        let text = format!("Counter: {}", model.counter);
        let paragraph = Paragraph::new(text).block(Block::default().borders(Borders::ALL));
        frame.render_widget(paragraph, frame.area());
    }

    fn map_event(&self, _model: &Model, event: Event) -> Option<Msg> {
        if let Event::Key(key) = event {
            match key.code {
                KeyCode::Char('+') => Some(Msg::Increment),
                KeyCode::Char('-') => Some(Msg::Decrement),
                KeyCode::Char('q') => Some(Msg::Quit),
                _ => None,
            }
        } else {
            None
        }
    }
}
```

With all this setup we're ready to run the application. The `main` function
will look like this:

```rust
fn main() -> std::io::Result<()> {
    let app = MyApp {};
    rustey::run(&app)?;
    Ok(())
}
```

And with this we have a full implementation of a TUI counter application in
less than 100 lines of code.

## Commands

Commands are one off tasks that are executed asynchronously in another thread.
This is useful for tasks that take a long time to complete, such as network
requests or file I/O.

To use commands first define a command type that implements the `Command<T>`
trait. Use the `sender` to send a message to the update function:

```rust
struct SomeTask {}

impl Command<Msg> for SomeTask {
    fn run(&self, sender: rustey::Sender<Msg>) {
        sender.send(Msg::TaskResult);
    }
}
```

With this task definition in place the task can now be initiated from either
the `init` function or the `update` function simply by returning the command
from either:

```rust
    fn update(&self, model: &mut Model, msg: Msg, quit_flag: &QuitFlag) -> Cmd<Msg> {
        match msg {
            Msg::DoTask => Cmd::Some(Box::new(SomeTask {})),
            ...
        }
    }
```

Rustey will take care of running the command when it's returned.

## Run loop

The main loop is initialized and runs like this:

```mermaid
flowchart TD
    init_terminal["init terminal"]
    init_app["get initial model and command"]
    create_quitflag["set up quit flag"]
    make_channel["set up message channel"]
    start_subs["start subscriptions"]
    main_loop["main event loop"]
    start_cmd["start command if present"]
    handle_cmd["handle command"]
    draw_ui["draw UI"]
    recv_msg["wait for message"]
    update_model["update model"]
    update_subs["update subscriptions"]
    check_quit["check quit flag"]
    break_loop["exit loop"]
    restore_terminal["restore terminal"]
    return_ok["finish"]
    fetch_event["fetch event"]
    map_event["map event"]

    init_terminal --> init_app --> create_quitflag --> make_channel --> start_subs --> main_loop
    main_loop --> start_cmd
    main_loop --> draw_ui
    start_cmd --> handle_cmd -- msg --> recv_msg
    recv_msg --> update_model
    update_model --> update_subs --> check_quit
    check_quit -- "yes" --> break_loop --> restore_terminal --> return_ok
    check_quit -- "no" --> main_loop
    fetch_event -- "event" --> map_event -- "msg" --> recv_msg
```

## Example application

An example application can be found at https://github.com/jacobat/rustey_list