Skip to main content

urushi_tui_app/
application.rs

1//! What a full-screen application is.
2
3use urushi::View;
4
5use super::effect::Effect;
6use super::subscription::Subscription;
7
8/// A full-screen terminal application, as a value that describes a program
9/// rather than the program's running state.
10///
11/// The implementing type holds what the program is made of — configuration, a
12/// theme, the paths it operates on — and [`Model`](Application::Model) holds
13/// what changes while it runs. The runtime owns the live model: it holds a
14/// `Model`, borrows the `Application`, lends the model to
15/// [`update`](Application::update) mutably one message at a time, lends it to
16/// [`view`](Application::view) and [`subscriptions`](Application::subscriptions)
17/// immutably, and returns it when the application shuts down. Nothing here
18/// reads a terminal, spawns a task, or draws.
19///
20/// That split is what makes the four methods testable without a terminal. A
21/// test builds the application, calls `init` for a model and its first effect,
22/// drives `update` with messages of its own, and reads `view` as a value:
23///
24/// ```
25/// use urushi::{TextStyle, View};
26/// use urushi_tui_app::{Application, Effect, Subscription};
27///
28/// struct Counter;
29///
30/// enum Message {
31///     Increment,
32/// }
33///
34/// impl Application for Counter {
35///     type Model = i32;
36///     type Message = Message;
37///
38///     fn init(&self) -> (Self::Model, Effect<Self::Message>) {
39///         (0, Effect::none())
40///     }
41///
42///     fn update(&self, model: &mut Self::Model, message: Self::Message)
43///         -> Effect<Self::Message> {
44///         match message {
45///             Message::Increment => *model += 1,
46///         }
47///         Effect::none()
48///     }
49///
50///     fn view(&self, model: &Self::Model) -> View {
51///         View::text(model.to_string(), TextStyle::new())
52///     }
53///
54///     fn subscriptions(&self, _model: &Self::Model) -> Subscription<Self::Message> {
55///         Subscription::none()
56///     }
57/// }
58///
59/// let application = Counter;
60/// let (mut model, _effect) = application.init();
61/// application.update(&mut model, Message::Increment);
62/// assert_eq!(model, 1);
63/// ```
64///
65/// # Bounds
66///
67/// [`Message`](Application::Message) is `Send + 'static` because an effect
68/// completes on another thread or task and must send its message back. `Model`
69/// and `Self` carry no bound: the runtime keeps the model on the thread that
70/// runs `update` and `view`, and the entry point that runs an application
71/// blocks that thread rather than handing the model to another.
72pub trait Application {
73    /// The state the runtime owns while the application runs.
74    type Model;
75
76    /// What the application reacts to.
77    type Message: Send + 'static;
78
79    /// The initial model, and the work to start with it.
80    fn init(&self) -> (Self::Model, Effect<Self::Message>);
81
82    /// Applies one message to the model and returns the work it asks for.
83    ///
84    /// This is the only method that may change the model, which is what the
85    /// receivers say: `update` takes it mutably and every other method takes it
86    /// immutably. Returning [`Effect::shutdown`] here ends the run.
87    fn update(&self, model: &mut Self::Model, message: Self::Message) -> Effect<Self::Message>;
88
89    /// The view of the current model.
90    ///
91    /// It receives no surface or rendering-environment input: an application
92    /// that needs such a fact subscribes to it, stores what it needs in the
93    /// model, and reads it here like any other state.
94    fn view(&self, model: &Self::Model) -> View;
95
96    /// The sources the application wants to hear from in this state.
97    ///
98    /// The runtime reconciles this against what it is running after each
99    /// `update`, so a source appears by being declared and stops by no longer
100    /// being declared.
101    fn subscriptions(&self, model: &Self::Model) -> Subscription<Self::Message>;
102}
103
104#[cfg(test)]
105#[path = "application_tests.rs"]
106mod tests;