Skip to main content

Application

Trait Application 

Source
pub trait Application {
    type Model;
    type Message: Send + 'static;

    // Required methods
    fn init(&self) -> (Self::Model, Effect<Self::Message>);
    fn update(
        &self,
        model: &mut Self::Model,
        message: Self::Message,
    ) -> Effect<Self::Message>;
    fn view(&self, model: &Self::Model) -> View;
    fn subscriptions(&self, model: &Self::Model) -> Subscription<Self::Message>;
}
Expand description

A full-screen terminal application, as a value that describes a program rather than the program’s running state.

The implementing type holds what the program is made of — configuration, a theme, the paths it operates on — and Model holds what changes while it runs. The runtime owns the live model: it holds a Model, borrows the Application, lends the model to update mutably one message at a time, lends it to view and subscriptions immutably, and returns it when the application shuts down. Nothing here reads a terminal, spawns a task, or draws.

That split is what makes the four methods testable without a terminal. A test builds the application, calls init for a model and its first effect, drives update with messages of its own, and reads view as a value:

use urushi::{TextStyle, View};
use urushi_tui_app::{Application, Effect, Subscription};

struct Counter;

enum Message {
    Increment,
}

impl Application for Counter {
    type Model = i32;
    type Message = Message;

    fn init(&self) -> (Self::Model, Effect<Self::Message>) {
        (0, Effect::none())
    }

    fn update(&self, model: &mut Self::Model, message: Self::Message)
        -> Effect<Self::Message> {
        match message {
            Message::Increment => *model += 1,
        }
        Effect::none()
    }

    fn view(&self, model: &Self::Model) -> View {
        View::text(model.to_string(), TextStyle::new())
    }

    fn subscriptions(&self, _model: &Self::Model) -> Subscription<Self::Message> {
        Subscription::none()
    }
}

let application = Counter;
let (mut model, _effect) = application.init();
application.update(&mut model, Message::Increment);
assert_eq!(model, 1);

§Bounds

Message is Send + 'static because an effect completes on another thread or task and must send its message back. Model and Self carry no bound: the runtime keeps the model on the thread that runs update and view, and the entry point that runs an application blocks that thread rather than handing the model to another.

Required Associated Types§

Source

type Model

The state the runtime owns while the application runs.

Source

type Message: Send + 'static

What the application reacts to.

Required Methods§

Source

fn init(&self) -> (Self::Model, Effect<Self::Message>)

The initial model, and the work to start with it.

Source

fn update( &self, model: &mut Self::Model, message: Self::Message, ) -> Effect<Self::Message>

Applies one message to the model and returns the work it asks for.

This is the only method that may change the model, which is what the receivers say: update takes it mutably and every other method takes it immutably. Returning Effect::shutdown here ends the run.

Source

fn view(&self, model: &Self::Model) -> View

The view of the current model.

It receives no surface or rendering-environment input: an application that needs such a fact subscribes to it, stores what it needs in the model, and reads it here like any other state.

Source

fn subscriptions(&self, model: &Self::Model) -> Subscription<Self::Message>

The sources the application wants to hear from in this state.

The runtime reconciles this against what it is running after each update, so a source appears by being declared and stops by no longer being declared.

Implementors§