Skip to main content

envision/app/update/
mod.rs

1//! Update helpers and result types for TEA applications.
2
3use super::command::Command;
4
5/// Result of an update operation.
6///
7/// This provides a convenient way to return both state changes
8/// and commands from an update function.
9#[derive(Debug)]
10pub struct UpdateResult<S, M> {
11    /// The updated state (if changed)
12    pub state: Option<S>,
13
14    /// Commands to execute
15    pub command: Command<M>,
16}
17
18impl<S, M> UpdateResult<S, M> {
19    /// Creates a result with no state change and no command.
20    pub fn none() -> Self {
21        Self {
22            state: None,
23            command: Command::none(),
24        }
25    }
26
27    /// Creates a result with updated state.
28    pub fn state(state: S) -> Self {
29        Self {
30            state: Some(state),
31            command: Command::none(),
32        }
33    }
34
35    /// Creates a result with a command.
36    pub fn command(command: Command<M>) -> Self {
37        Self {
38            state: None,
39            command,
40        }
41    }
42
43    /// Creates a result with both state and command.
44    pub fn with(state: S, command: Command<M>) -> Self {
45        Self {
46            state: Some(state),
47            command,
48        }
49    }
50
51    /// Adds a command to this result.
52    pub fn and_command(mut self, command: Command<M>) -> Self {
53        self.command = self.command.and(command);
54        self
55    }
56
57    /// Maps the state type.
58    pub fn map_state<T, F>(self, f: F) -> UpdateResult<T, M>
59    where
60        F: FnOnce(S) -> T,
61    {
62        UpdateResult {
63            state: self.state.map(f),
64            command: self.command,
65        }
66    }
67
68    /// Maps the message type.
69    pub fn map_message<N, F>(self, f: F) -> UpdateResult<S, N>
70    where
71        F: Fn(M) -> N + Clone + Send + 'static,
72        M: Send + 'static,
73        N: Send + 'static,
74    {
75        UpdateResult {
76            state: self.state,
77            command: self.command.map(f),
78        }
79    }
80}
81
82impl<S, M> Default for UpdateResult<S, M> {
83    fn default() -> Self {
84        Self::none()
85    }
86}
87
88/// A trait for types that can perform updates.
89///
90/// This is an alternative to implementing `App::update` directly,
91/// useful for component-based architectures.
92pub trait Update {
93    /// The state type being updated.
94    type State;
95
96    /// The message type that triggers updates.
97    type Message;
98
99    /// Perform the update.
100    fn update(&self, state: &mut Self::State, msg: Self::Message) -> Command<Self::Message>;
101}
102
103/// A function-based update implementation.
104///
105/// Wraps a function to implement the `Update` trait.
106pub struct FnUpdate<S, M, F>
107where
108    F: Fn(&mut S, M) -> Command<M>,
109{
110    f: F,
111    _phantom: std::marker::PhantomData<(S, M)>,
112}
113
114impl<S, M, F> FnUpdate<S, M, F>
115where
116    F: Fn(&mut S, M) -> Command<M>,
117{
118    /// Creates a new function-based updater.
119    pub fn new(f: F) -> Self {
120        Self {
121            f,
122            _phantom: std::marker::PhantomData,
123        }
124    }
125}
126
127impl<S, M, F> Update for FnUpdate<S, M, F>
128where
129    F: Fn(&mut S, M) -> Command<M>,
130{
131    type State = S;
132    type Message = M;
133
134    fn update(&self, state: &mut S, msg: M) -> Command<M> {
135        (self.f)(state, msg)
136    }
137}
138
139/// Extension trait for ergonomic state updates.
140pub trait StateExt: Sized {
141    /// Updates self and returns a command.
142    fn updated(self, cmd: Command<impl Clone>) -> UpdateResult<Self, impl Clone> {
143        UpdateResult {
144            state: Some(self),
145            command: cmd,
146        }
147    }
148
149    /// Returns self with no command.
150    fn unchanged(self) -> UpdateResult<Self, ()> {
151        UpdateResult {
152            state: Some(self),
153            command: Command::none(),
154        }
155    }
156}
157
158impl<T> StateExt for T {}
159
160#[cfg(test)]
161mod tests;