use abstutil::CloneableAny;
use crate::{Canvas, Color, EventCtx, GfxCtx, Outcome, Panel};
pub trait SharedAppState {
fn before_event(&mut self) {}
fn draw_default(&self, _: &mut GfxCtx) {}
fn dump_before_abort(&self, _: &Canvas) {}
fn before_quit(&self, _: &Canvas) {}
fn free_memory(&mut self) {}
}
pub(crate) struct App<A: SharedAppState> {
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) {
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 => {}
DrawBaselayer::PreviousState => {
g.clear(Color::BLACK);
}
}
self.states[self.states.len() - 2].draw(g, &self.shared_app_state);
} else {
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);
}
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") {
self.states.push(state);
} 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) => {
for t in list {
self.execute_transition(ctx, t);
}
true
}
}
}
}
pub enum DrawBaselayer {
DefaultDraw,
Custom,
PreviousState,
}
pub trait State<A>: downcast_rs::Downcast {
fn event(&mut self, ctx: &mut EventCtx, shared_app_state: &mut A) -> Transition<A>;
fn draw(&self, g: &mut GfxCtx, shared_app_state: &A);
fn draw_baselayer(&self) -> DrawBaselayer {
DrawBaselayer::DefaultDraw
}
fn on_destroy(&mut self, _: &mut EventCtx, _: &mut A) {}
}
downcast_rs::impl_downcast!(State<A>);
pub enum Transition<A> {
Keep,
KeepWithMouseover,
Pop,
ModifyState(Box<dyn FnOnce(&mut Box<dyn State<A>>, &mut EventCtx, &mut A)>),
ReplaceWithData(
Box<dyn FnOnce(Box<dyn State<A>>, &mut EventCtx, &mut A) -> Vec<Box<dyn State<A>>>>,
),
Push(Box<dyn State<A>>),
Replace(Box<dyn State<A>>),
Clear(Vec<Box<dyn State<A>>>),
Multi(Vec<Transition<A>>),
}
pub trait SimpleState<A> {
fn on_click(
&mut self,
ctx: &mut EventCtx,
app: &mut A,
action: &str,
panel: &Panel,
) -> Transition<A>;
fn on_click_custom(
&mut self,
_ctx: &mut EventCtx,
_app: &mut A,
_action: Box<dyn CloneableAny>,
_panel: &Panel,
) -> Transition<A> {
Transition::Keep
}
fn panel_changed(
&mut self,
_: &mut EventCtx,
_: &mut A,
_: &mut Panel,
) -> Option<Transition<A>> {
None
}
fn on_mouseover(&mut self, _: &mut EventCtx, _: &mut A) {}
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_state(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::ClickCustom(data) => self.inner.on_click_custom(ctx, app, data, &self.panel),
Outcome::Changed(_) => self
.inner
.panel_changed(ctx, app, &mut 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);
self.panel.draw(g);
}
fn draw_baselayer(&self) -> DrawBaselayer {
self.inner.draw_baselayer()
}
}