1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
//! A widgetry application splits its state into two pieces: global shared state that lasts for the
//! entire lifetime of the application, and a stack of smaller states, only one of which is active
//! at a time. For example, imagine an application to view a map. The shared state would include
//! the map and pre-rendered geometry for it. The individual states might start with a splash
//! screen or menu to choose a map, then a map viewer, then maybe a state to drill down into pieces
//! of the map.

use crate::{Canvas, Color, EventCtx, GfxCtx, Outcome, Panel};

/// Any data that should last the entire lifetime of the application should be stored in the struct
/// implementing this trait.
pub trait SharedAppState {
    /// Before `State::event` is called, call this.
    fn before_event(&mut self) {}
    /// When DrawBaselayer::DefaultDraw is called, run this.
    fn draw_default(&self, _: &mut GfxCtx) {}

    /// Will be called if `State::event` or `State::draw` panics.
    fn dump_before_abort(&self, _: &Canvas) {}
    /// Called before a normal exit, like window close
    fn before_quit(&self, _: &Canvas) {}
}

pub(crate) struct App<A: SharedAppState> {
    /// A stack of states
    pub(crate) states: Vec<Box<dyn State<A>>>,
    pub(crate) shared_app_state: A,
}

impl<A: SharedAppState> App<A> {
    pub(crate) fn event(&mut self, ctx: &mut EventCtx) {
        self.shared_app_state.before_event();

        let transition = self
            .states
            .last_mut()
            .unwrap()
            .event(ctx, &mut self.shared_app_state);
        if self.execute_transition(ctx, transition) {
            // Let the new state initialize with a fake event. Usually these just return
            // Transition::Keep, but nothing stops them from doing whatever. (For example, entering
            // tutorial mode immediately pushes on a Warper.) So just recurse.
            ctx.no_op_event(true, |ctx| self.event(ctx));
        }
    }

    pub(crate) fn draw(&self, g: &mut GfxCtx) {
        let state = self.states.last().unwrap();

        match state.draw_baselayer() {
            DrawBaselayer::DefaultDraw => {
                self.shared_app_state.draw_default(g);
            }
            DrawBaselayer::Custom => {}
            DrawBaselayer::PreviousState => {
                if self.states.len() >= 2 {
                    match self.states[self.states.len() - 2].draw_baselayer() {
                        DrawBaselayer::DefaultDraw => {
                            self.shared_app_state.draw_default(g);
                        }
                        DrawBaselayer::Custom => {}
                        // Don't recurse, but at least clear the screen, because the state is
                        // usually expecting the previous thing to happen.
                        DrawBaselayer::PreviousState => {
                            g.clear(Color::BLACK);
                        }
                    }

                    self.states[self.states.len() - 2].draw(g, &self.shared_app_state);
                } else {
                    // I'm not entirely sure why this happens, but crashing isn't ideal.
                    warn!(
                        "A state requested DrawBaselayer::PreviousState, but it's the only state \
                         on the stack!"
                    );
                    g.clear(Color::BLACK);
                }
            }
        }
        state.draw(g, &self.shared_app_state);
    }

    /// If true, then the top-most state on the stack needs to be "woken up" with a fake mouseover
    /// event.
    fn execute_transition(&mut self, ctx: &mut EventCtx, transition: Transition<A>) -> bool {
        match transition {
            Transition::Keep => false,
            Transition::KeepWithMouseover => true,
            Transition::Pop => {
                let mut state = self.states.pop().unwrap();
                state.on_destroy(ctx, &mut self.shared_app_state);
                if self.states.is_empty() {
                    if cfg!(target_arch = "wasm32") {
                        // Just kidding, don't actually leave.
                        self.states.push(state);
                    // TODO Once PopupMsg is lifted here, add an explanation
                    } else {
                        self.shared_app_state.before_quit(ctx.canvas);
                        std::process::exit(0);
                    }
                }
                true
            }
            Transition::ModifyState(cb) => {
                cb(
                    self.states.last_mut().unwrap(),
                    ctx,
                    &mut self.shared_app_state,
                );
                true
            }
            Transition::ReplaceWithData(cb) => {
                let mut last = self.states.pop().unwrap();
                last.on_destroy(ctx, &mut self.shared_app_state);
                let new_states = cb(last, ctx, &mut self.shared_app_state);
                self.states.extend(new_states);
                true
            }
            Transition::Push(state) => {
                self.states.push(state);
                true
            }
            Transition::Replace(state) => {
                self.states
                    .pop()
                    .unwrap()
                    .on_destroy(ctx, &mut self.shared_app_state);
                self.states.push(state);
                true
            }
            Transition::Clear(states) => {
                while !self.states.is_empty() {
                    self.states
                        .pop()
                        .unwrap()
                        .on_destroy(ctx, &mut self.shared_app_state);
                }
                self.states.extend(states);
                true
            }
            Transition::Multi(list) => {
                // Always wake-up just the last state remaining after the sequence
                for t in list {
                    self.execute_transition(ctx, t);
                }
                true
            }
        }
    }
}

/// Before `State::draw` is called, draw something else.
pub enum DrawBaselayer {
    /// Call `SharedAppState::draw_default`.
    DefaultDraw,
    /// Don't draw anything.
    Custom,
    /// Call the previous state's `draw`. This won't recurse, even if that state specifies
    /// `PreviousState`.
    PreviousState,
}

/// A temporary state of an application. There's a stack of these, with the most recent being the
/// active one.
pub trait State<A>: downcast_rs::Downcast {
    /// Respond to a UI event, such as input or time passing.
    fn event(&mut self, ctx: &mut EventCtx, shared_app_state: &mut A) -> Transition<A>;
    /// Draw
    fn draw(&self, g: &mut GfxCtx, shared_app_state: &A);

    /// Specifies what to draw before draw()
    fn draw_baselayer(&self) -> DrawBaselayer {
        DrawBaselayer::DefaultDraw
    }

    /// Before this state is popped or replaced, call this.
    fn on_destroy(&mut self, _: &mut EventCtx, _: &mut A) {}
    // We don't need an on_enter -- the constructor for the state can just do it.
}

downcast_rs::impl_downcast!(State<A>);

/// When a state responds to an event, it can specify some way to manipulate the stack of states.
pub enum Transition<A> {
    /// Don't do anything, keep the current state as the active one
    Keep,
    /// Keep the current state as the active one, but immediately call `event` again with a mouse
    /// moved event
    KeepWithMouseover,
    /// Destroy the current state, and resume from the previous one
    Pop,
    /// If a state needs to pass data back to its parent, use this. In the callback, you have to
    /// downcast the previous state to populate it with data.
    ModifyState(Box<dyn FnOnce(&mut Box<dyn State<A>>, &mut EventCtx, &mut A)>),
    // TODO This is like Replace + ModifyState, then returning a few Push's from the callback. Not
    // sure how to express it in terms of the others without complicating ModifyState everywhere.
    ReplaceWithData(
        Box<dyn FnOnce(Box<dyn State<A>>, &mut EventCtx, &mut A) -> Vec<Box<dyn State<A>>>>,
    ),
    /// Push a new active state on the top of the stack.
    Push(Box<dyn State<A>>),
    /// Replace the current state with a new one. Equivalent to Pop, then Push.
    Replace(Box<dyn State<A>>),
    /// Replace the entire stack of states with this stack.
    Clear(Vec<Box<dyn State<A>>>),
    /// Execute a sequence of transitions in order.
    Multi(Vec<Transition<A>>),
}

/// Many states fit a pattern of managing a single panel, handling mouseover events, and other
/// interactions on the map. Implementing this instead of `State` reduces some boilerplate.
pub trait SimpleState<A> {
    /// Called when something on the panel has been clicked. Since the action is just a string,
    /// the fallback case can just use `unreachable!()`.
    fn on_click(
        &mut self,
        ctx: &mut EventCtx,
        app: &mut A,
        action: &str,
        panel: &Panel,
    ) -> Transition<A>;
    /// Called when something on the panel has changed. If a transition is returned, stop handling
    /// the event and immediately apply the transition.
    fn panel_changed(&mut self, _: &mut EventCtx, _: &mut A, _: &Panel) -> Option<Transition<A>> {
        None
    }
    /// Called when the mouse has moved.
    fn on_mouseover(&mut self, _: &mut EventCtx, _: &mut A) {}
    /// If a panel `on_click` event didn't occur and `panel_changed` didn't return  transition, then
    /// call this to handle all other events.
    fn other_event(&mut self, _: &mut EventCtx, _: &mut A) -> Transition<A> {
        Transition::Keep
    }
    fn draw(&self, _: &mut GfxCtx, _: &A) {}
    fn draw_baselayer(&self) -> DrawBaselayer {
        DrawBaselayer::DefaultDraw
    }
}

impl<A: 'static> dyn SimpleState<A> {
    pub fn new(panel: Panel, inner: Box<dyn SimpleState<A>>) -> Box<dyn State<A>> {
        Box::new(SimpleStateWrapper { panel, inner })
    }
}

pub struct SimpleStateWrapper<A> {
    panel: Panel,
    inner: Box<dyn SimpleState<A>>,
}

impl<A: 'static> State<A> for SimpleStateWrapper<A> {
    fn event(&mut self, ctx: &mut EventCtx, app: &mut A) -> Transition<A> {
        if ctx.redo_mouseover() {
            self.inner.on_mouseover(ctx, app);
        }
        match self.panel.event(ctx) {
            Outcome::Clicked(action) => self.inner.on_click(ctx, app, &action, &self.panel),
            Outcome::Changed => self
                .inner
                .panel_changed(ctx, app, &self.panel)
                .unwrap_or_else(|| self.inner.other_event(ctx, app)),
            Outcome::Nothing => self.inner.other_event(ctx, app),
        }
    }

    fn draw(&self, g: &mut GfxCtx, app: &A) {
        self.inner.draw(g, app);
        // Draw last
        self.panel.draw(g);
    }
    fn draw_baselayer(&self) -> DrawBaselayer {
        self.inner.draw_baselayer()
    }
}