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}