widgetry/
app_state.rs

1//! A widgetry application splits its state into two pieces: global shared state that lasts for the
2//! entire lifetime of the application, and a stack of smaller states, only one of which is active
3//! at a time. For example, imagine an application to view a map. The shared state would include
4//! the map and pre-rendered geometry for it. The individual states might start with a splash
5//! screen or menu to choose a map, then a map viewer, then maybe a state to drill down into pieces
6//! of the map.
7
8use abstutil::CloneableAny;
9
10use crate::{Canvas, Color, EventCtx, GfxCtx, Outcome, Panel};
11
12/// Any data that should last the entire lifetime of the application should be stored in the struct
13/// implementing this trait.
14pub trait SharedAppState {
15    /// Before `State::event` is called, call this.
16    fn before_event(&mut self) {}
17    /// When DrawBaselayer::DefaultDraw is called, run this.
18    fn draw_default(&self, _: &mut GfxCtx) {}
19
20    /// Will be called if `State::event` or `State::draw` panics.
21    fn dump_before_abort(&self, _: &Canvas) {}
22    /// Called before a normal exit, like window close
23    fn before_quit(&self, _: &Canvas) {}
24
25    /// If widgetry determines the video card is low on memory, this may be called. The application
26    /// should make its best effort to delete any unused Drawables.
27    fn free_memory(&mut self) {}
28}
29
30pub(crate) struct App<A: SharedAppState> {
31    /// A stack of states
32    pub(crate) states: Vec<Box<dyn State<A>>>,
33    pub(crate) shared_app_state: A,
34}
35
36impl<A: 'static + SharedAppState> App<A> {
37    pub(crate) fn event(&mut self, ctx: &mut EventCtx) {
38        self.shared_app_state.before_event();
39
40        let transition = self
41            .states
42            .last_mut()
43            .unwrap()
44            .event(ctx, &mut self.shared_app_state);
45        if self.execute_transition(ctx, transition) {
46            // Let the new state initialize with a fake event. Usually these just return
47            // Transition::Keep, but nothing stops them from doing whatever. (For example, entering
48            // tutorial mode immediately pushes on a Warper.) So just recurse.
49            ctx.no_op_event(true, |ctx| self.event(ctx));
50        }
51    }
52
53    pub(crate) fn draw(&self, g: &mut GfxCtx) {
54        let state = self.states.last().unwrap();
55
56        match state.draw_baselayer() {
57            DrawBaselayer::DefaultDraw => {
58                self.shared_app_state.draw_default(g);
59            }
60            DrawBaselayer::Custom => {}
61            DrawBaselayer::PreviousState => {
62                if self.states.len() >= 2 {
63                    match self.states[self.states.len() - 2].draw_baselayer() {
64                        DrawBaselayer::DefaultDraw => {
65                            self.shared_app_state.draw_default(g);
66                        }
67                        DrawBaselayer::Custom => {}
68                        // Don't recurse, but at least clear the screen, because the state is
69                        // usually expecting the previous thing to happen.
70                        DrawBaselayer::PreviousState => {
71                            g.clear(Color::BLACK);
72                        }
73                    }
74
75                    self.states[self.states.len() - 2].draw(g, &self.shared_app_state);
76                } else {
77                    // I'm not entirely sure why this happens, but crashing isn't ideal.
78                    warn!(
79                        "A state requested DrawBaselayer::PreviousState, but it's the only state \
80                         on the stack!"
81                    );
82                    g.clear(Color::BLACK);
83                }
84            }
85        }
86        state.draw(g, &self.shared_app_state);
87    }
88
89    /// If true, then the top-most state on the stack needs to be "woken up" with a fake mouseover
90    /// event.
91    fn execute_transition(&mut self, ctx: &mut EventCtx, transition: Transition<A>) -> bool {
92        match transition {
93            Transition::Keep => false,
94            Transition::KeepWithMouseover => true,
95            Transition::Pop => {
96                let mut state = self.states.pop().unwrap();
97                state.on_destroy(ctx, &mut self.shared_app_state);
98                if self.states.is_empty() {
99                    if cfg!(target_arch = "wasm32") {
100                        // Just kidding, don't actually leave.
101                        self.states.push(state);
102                    // TODO Once PopupMsg is lifted here, add an explanation
103                    } else {
104                        self.shared_app_state.before_quit(ctx.canvas);
105                        std::process::exit(0);
106                    }
107                }
108                true
109            }
110            Transition::ModifyState(cb) => {
111                cb(
112                    self.states.last_mut().unwrap(),
113                    ctx,
114                    &mut self.shared_app_state,
115                );
116                true
117            }
118            Transition::ConsumeState(cb) => {
119                let mut last = self.states.pop().unwrap();
120                last.on_destroy(ctx, &mut self.shared_app_state);
121                let new_states = cb(last, ctx, &mut self.shared_app_state);
122                self.states.extend(new_states);
123                true
124            }
125            Transition::Push(state) => {
126                self.states.push(state);
127                true
128            }
129            Transition::Replace(state) => {
130                self.states
131                    .pop()
132                    .unwrap()
133                    .on_destroy(ctx, &mut self.shared_app_state);
134                self.states.push(state);
135                true
136            }
137            Transition::Clear(states) => {
138                while !self.states.is_empty() {
139                    self.states
140                        .pop()
141                        .unwrap()
142                        .on_destroy(ctx, &mut self.shared_app_state);
143                }
144                self.states.extend(states);
145                true
146            }
147            Transition::Recreate => {
148                // TODO Don't call on_destroy?
149                let mut last = self.states.pop().unwrap();
150                let replacement = last.recreate(ctx, &mut self.shared_app_state);
151                self.states.push(replacement);
152                true
153            }
154            Transition::Multi(list) => {
155                // Always wake-up just the last state remaining after the sequence
156                for t in list {
157                    self.execute_transition(ctx, t);
158                }
159                true
160            }
161        }
162    }
163}
164
165/// Before `State::draw` is called, draw something else.
166pub enum DrawBaselayer {
167    /// Call `SharedAppState::draw_default`.
168    DefaultDraw,
169    /// Don't draw anything.
170    Custom,
171    /// Call the previous state's `draw`. This won't recurse, even if that state specifies
172    /// `PreviousState`.
173    PreviousState,
174}
175
176/// A temporary state of an application. There's a stack of these, with the most recent being the
177/// active one.
178pub trait State<A>: downcast_rs::Downcast {
179    /// Respond to a UI event, such as input or time passing.
180    fn event(&mut self, ctx: &mut EventCtx, shared_app_state: &mut A) -> Transition<A>;
181    /// Draw
182    fn draw(&self, g: &mut GfxCtx, shared_app_state: &A);
183
184    /// Specifies what to draw before draw()
185    fn draw_baselayer(&self) -> DrawBaselayer {
186        DrawBaselayer::DefaultDraw
187    }
188
189    /// Before this state is popped or replaced, call this.
190    fn on_destroy(&mut self, _: &mut EventCtx, _: &mut A) {}
191    // We don't need an on_enter -- the constructor for the state can just do it.
192
193    /// Respond to `Transition::Recreate` by assuming state in the app has changed, but preserving
194    /// the `State`-specific state appropriately.
195    fn recreate(&mut self, _: &mut EventCtx, _: &mut A) -> Box<dyn State<A>> {
196        panic!("This state hasn't implemented support for Transition::Recreate");
197    }
198}
199
200downcast_rs::impl_downcast!(State<A>);
201
202/// When a state responds to an event, it can specify some way to manipulate the stack of states.
203pub enum Transition<A> {
204    /// Don't do anything, keep the current state as the active one
205    Keep,
206    /// Keep the current state as the active one, but immediately call `event` again with a mouse
207    /// moved event
208    KeepWithMouseover,
209    /// Destroy the current state, and resume from the previous one
210    Pop,
211    /// If a state needs to pass data back to its parent, use this. In the callback, you have to
212    /// downcast the previous state to populate it with data.
213    ModifyState(Box<dyn FnOnce(&mut Box<dyn State<A>>, &mut EventCtx, &mut A)>),
214    /// This destroys the current state, running the callback on it, and pushes new states onto the
215    /// stack. The callback can consume the current state, thus salvaging fields from it without
216    /// cloning.
217    ConsumeState(
218        Box<dyn FnOnce(Box<dyn State<A>>, &mut EventCtx, &mut A) -> Vec<Box<dyn State<A>>>>,
219    ),
220    /// Push a new active state on the top of the stack.
221    Push(Box<dyn State<A>>),
222    /// Replace the current state with a new one. Equivalent to Pop, then Push.
223    Replace(Box<dyn State<A>>),
224    /// Replace the entire stack of states with this stack.
225    Clear(Vec<Box<dyn State<A>>>),
226    /// Call `State::recreate` on the current top of the stack
227    Recreate,
228    /// Execute a sequence of transitions in order.
229    Multi(Vec<Transition<A>>),
230}
231
232/// Many states fit a pattern of managing a single panel, handling mouseover events, and other
233/// interactions on the map. Implementing this instead of `State` reduces some boilerplate.
234pub trait SimpleState<A> {
235    /// Called when something on the panel has been clicked. Since the action is just a string,
236    /// the fallback case can just use `unreachable!()`.
237    fn on_click(
238        &mut self,
239        ctx: &mut EventCtx,
240        app: &mut A,
241        action: &str,
242        panel: &mut Panel,
243    ) -> Transition<A>;
244    /// Called when something on the panel has been clicked.
245    fn on_click_custom(
246        &mut self,
247        _ctx: &mut EventCtx,
248        _app: &mut A,
249        _action: Box<dyn CloneableAny>,
250        _panel: &mut Panel,
251    ) -> Transition<A> {
252        Transition::Keep
253    }
254    /// Called when something on the panel has changed. If a transition is returned, stop handling
255    /// the event and immediately apply the transition.
256    fn panel_changed(
257        &mut self,
258        _: &mut EventCtx,
259        _: &mut A,
260        _: &mut Panel,
261    ) -> Option<Transition<A>> {
262        None
263    }
264    /// Called when the mouse has moved.
265    fn on_mouseover(&mut self, _: &mut EventCtx, _: &mut A) {}
266    /// If a panel `on_click` event didn't occur and `panel_changed` didn't return  transition, then
267    /// call this to handle all other events.
268    fn other_event(&mut self, _: &mut EventCtx, _: &mut A) -> Transition<A> {
269        Transition::Keep
270    }
271    fn draw(&self, _: &mut GfxCtx, _: &A) {}
272    fn draw_baselayer(&self) -> DrawBaselayer {
273        DrawBaselayer::DefaultDraw
274    }
275}
276
277impl<A: 'static> dyn SimpleState<A> {
278    pub fn new_state(panel: Panel, inner: Box<dyn SimpleState<A>>) -> Box<dyn State<A>> {
279        Box::new(SimpleStateWrapper { panel, inner })
280    }
281}
282
283pub struct SimpleStateWrapper<A> {
284    panel: Panel,
285    inner: Box<dyn SimpleState<A>>,
286}
287
288impl<A: 'static> State<A> for SimpleStateWrapper<A> {
289    fn event(&mut self, ctx: &mut EventCtx, app: &mut A) -> Transition<A> {
290        if ctx.redo_mouseover() {
291            self.inner.on_mouseover(ctx, app);
292        }
293        match self.panel.event(ctx) {
294            Outcome::Clicked(action) => self.inner.on_click(ctx, app, &action, &mut self.panel),
295            Outcome::ClickCustom(data) => {
296                self.inner.on_click_custom(ctx, app, data, &mut self.panel)
297            }
298            Outcome::Changed(_) => self
299                .inner
300                .panel_changed(ctx, app, &mut self.panel)
301                .unwrap_or_else(|| self.inner.other_event(ctx, app)),
302            Outcome::DragDropReleased(_, _, _) | Outcome::Focused(_) | Outcome::Nothing => {
303                self.inner.other_event(ctx, app)
304            }
305        }
306    }
307
308    fn draw(&self, g: &mut GfxCtx, app: &A) {
309        self.inner.draw(g, app);
310        // Draw last
311        self.panel.draw(g);
312    }
313    fn draw_baselayer(&self) -> DrawBaselayer {
314        self.inner.draw_baselayer()
315    }
316}