diff --git a/editor/src/abtest/score.rs b/editor/src/abtest/score.rs index 76d6587a43..5ed6a38f39 100644 --- a/editor/src/abtest/score.rs +++ b/editor/src/abtest/score.rs @@ -1,9 +1,8 @@ -use crate::game::{State, Transition}; +use crate::game::{State, Transition, WizardState}; use crate::ui::PerMapUI; use crate::ui::UI; use ezgui::{ hotkey, EventCtx, GfxCtx, HorizontalAlignment, Key, ModalMenu, Text, VerticalAlignment, Wizard, - WrappedWizard, }; use geom::Duration; use itertools::Itertools; @@ -83,19 +82,13 @@ impl Scoreboard { } impl State for Scoreboard { - fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition { + fn event(&mut self, ctx: &mut EventCtx, _: &mut UI) -> Transition { self.menu.handle_event(ctx, None); if self.menu.action("quit") { return Transition::Pop; } if self.menu.action("browse trips") { - return Transition::Push(Box::new(BrowseTrips { - trips: CompareTrips::new( - ui.primary.sim.get_finished_trips(), - ui.secondary.as_ref().unwrap().sim.get_finished_trips(), - ), - wizard: Wizard::new(), - })); + return Transition::Push(WizardState::new(Box::new(browse_trips))); } Transition::Keep } @@ -109,28 +102,8 @@ impl State for Scoreboard { } } -struct BrowseTrips { - trips: CompareTrips, - wizard: Wizard, -} - -impl State for BrowseTrips { - fn event(&mut self, ctx: &mut EventCtx, _: &mut UI) -> Transition { - if pick_trip(&self.trips, &mut self.wizard.wrap(ctx)).is_some() { - // TODO show more details... - return Transition::Pop; - } else if self.wizard.aborted() { - return Transition::Pop; - } - Transition::Keep - } - - fn draw(&self, g: &mut GfxCtx, _: &UI) { - self.wizard.draw(g); - } -} - -fn pick_trip(trips: &CompareTrips, wizard: &mut WrappedWizard) -> Option { +fn browse_trips(wiz: &mut Wizard, ctx: &mut EventCtx, ui: &mut UI) -> Option { + let mut wizard = wiz.wrap(ctx); let mode = wizard .choose_something_no_keys::( "Browse which trips?", @@ -145,6 +118,10 @@ fn pick_trip(trips: &CompareTrips, wizard: &mut WrappedWizard) -> Option )? .1; // TODO Ewwww. Can't do this inside choices_generator because trips isn't &'a static. + let trips = CompareTrips::new( + ui.primary.sim.get_finished_trips(), + ui.secondary.as_ref().unwrap().sim.get_finished_trips(), + ); let mut filtered: Vec<&(TripID, TripMode, Duration, Duration)> = trips .finished_trips .iter() @@ -156,12 +133,12 @@ fn pick_trip(trips: &CompareTrips, wizard: &mut WrappedWizard) -> Option .into_iter() .map(|(id, _, t1, t2)| (format!("{} taking {} vs {}", id, t1, t2), *id)) .collect(); - wizard - .choose_something_no_keys::( - "Examine which trip?", - Box::new(move || choices.clone()), - ) - .map(|(_, id)| id) + wizard.choose_something_no_keys::( + "Examine which trip?", + Box::new(move || choices.clone()), + )?; + // TODO show more details... + Some(Transition::Pop) } pub struct CompareTrips { diff --git a/editor/src/edit/mod.rs b/editor/src/edit/mod.rs index 20fd2335ca..a94fd3a478 100644 --- a/editor/src/edit/mod.rs +++ b/editor/src/edit/mod.rs @@ -3,7 +3,7 @@ mod traffic_signals; use crate::common::CommonState; use crate::debug::DebugMode; -use crate::game::{State, Transition}; +use crate::game::{State, Transition, WizardState}; use crate::helpers::{ColorScheme, ID}; use crate::render::{ DrawCtx, DrawIntersection, DrawLane, DrawMap, DrawOptions, DrawTurn, Renderable, @@ -99,13 +99,9 @@ impl State for EditMode { // TODO Only if current edits are unsaved if self.menu.action("save edits") { - return Transition::Push(Box::new(Saving { - wizard: Wizard::new(), - })); + return Transition::Push(WizardState::new(Box::new(save_edits))); } else if self.menu.action("load different edits") { - return Transition::Push(Box::new(Loading { - wizard: Wizard::new(), - })); + return Transition::Push(WizardState::new(Box::new(load_edits))); } if let Some(ID::Lane(id)) = ui.primary.current_selection { @@ -292,79 +288,10 @@ impl State for EditMode { } } -struct Saving { - wizard: Wizard, -} +fn save_edits(wiz: &mut Wizard, ctx: &mut EventCtx, ui: &mut UI) -> Option { + let map = &mut ui.primary.map; + let mut wizard = wiz.wrap(ctx); -impl State for Saving { - fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition { - ctx.canvas.handle_event(ctx.input); - if save_edits(self.wizard.wrap(ctx), &mut ui.primary.map).is_some() || self.wizard.aborted() - { - Transition::Pop - } else { - Transition::Keep - } - } - - fn draw(&self, g: &mut GfxCtx, _: &UI) { - // TODO Still draw the diffs, yo - self.wizard.draw(g); - } -} - -struct Loading { - wizard: Wizard, -} - -impl State for Loading { - fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition { - ctx.canvas.handle_event(ctx.input); - if let Some(new_edits) = load_edits( - &ui.primary.map, - &mut self.wizard.wrap(ctx), - "Load which map edits?", - ) { - apply_map_edits(&mut ui.primary, &ui.cs, ctx, new_edits); - Transition::Pop - } else if self.wizard.aborted() { - Transition::Pop - } else { - Transition::Keep - } - } - - fn draw(&self, g: &mut GfxCtx, _: &UI) { - // TODO Still draw the diffs, yo - self.wizard.draw(g); - } -} - -struct BulkEditLanes { - road: RoadID, - wizard: Wizard, -} - -impl State for BulkEditLanes { - fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition { - ctx.canvas.handle_event(ctx.input); - if let Some(edits) = bulk_edit(self.road, &mut self.wizard.wrap(ctx), &ui.primary.map) { - apply_map_edits(&mut ui.primary, &ui.cs, ctx, edits); - Transition::Pop - } else if self.wizard.aborted() { - Transition::Pop - } else { - Transition::Keep - } - } - - fn draw(&self, g: &mut GfxCtx, _: &UI) { - // TODO Still draw the diffs, yo - self.wizard.draw(g); - } -} - -fn save_edits(mut wizard: WrappedWizard, map: &mut Map) -> Option<()> { let rename = if map.get_edits().edits_name == "no_edits" { Some(wizard.input_string("Name these map edits")?) } else { @@ -386,7 +313,25 @@ fn save_edits(mut wizard: WrappedWizard, map: &mut Map) -> Option<()> { } map.get_edits().save(); } - Some(()) + Some(Transition::Pop) +} + +fn load_edits(wiz: &mut Wizard, ctx: &mut EventCtx, ui: &mut UI) -> Option { + let map = &mut ui.primary.map; + let mut wizard = wiz.wrap(ctx); + + // TODO Exclude current + let map_name = map.get_name().to_string(); + let (_, new_edits) = wizard.choose_something_no_keys::( + "Load which map edits?", + Box::new(move || { + let mut list = abstutil::load_all_objects("edits", &map_name); + list.push(("no_edits".to_string(), MapEdits::new(map_name.clone()))); + list + }), + )?; + apply_map_edits(&mut ui.primary, &ui.cs, ctx, new_edits); + Some(Transition::Pop) } // For lane editing @@ -532,19 +477,28 @@ pub fn apply_map_edits( bundle.map.simplify_edits(&mut timer); } -fn load_edits(map: &Map, wizard: &mut WrappedWizard, query: &str) -> Option { - // TODO Exclude current? - let map_name = map.get_name().to_string(); - wizard - .choose_something_no_keys::( - query, - Box::new(move || { - let mut list = abstutil::load_all_objects("edits", &map_name); - list.push(("no_edits".to_string(), MapEdits::new(map_name.clone()))); - list - }), - ) - .map(|(_, e)| e) +struct BulkEditLanes { + road: RoadID, + wizard: Wizard, +} + +impl State for BulkEditLanes { + fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition { + ctx.canvas.handle_event(ctx.input); + if let Some(edits) = bulk_edit(self.road, &mut self.wizard.wrap(ctx), &ui.primary.map) { + apply_map_edits(&mut ui.primary, &ui.cs, ctx, edits); + Transition::Pop + } else if self.wizard.aborted() { + Transition::Pop + } else { + Transition::Keep + } + } + + fn draw(&self, g: &mut GfxCtx, _: &UI) { + // TODO Still draw the diffs, yo + self.wizard.draw(g); + } } fn bulk_edit(r: RoadID, wizard: &mut WrappedWizard, map: &Map) -> Option { diff --git a/editor/src/game.rs b/editor/src/game.rs index 73e509e08c..1076a5d21d 100644 --- a/editor/src/game.rs +++ b/editor/src/game.rs @@ -2,7 +2,7 @@ use crate::render::DrawOptions; use crate::sandbox::SandboxMode; use crate::splash_screen::SplashScreen; use crate::ui::{Flags, ShowEverything, UI}; -use ezgui::{Canvas, EventCtx, EventLoopMode, GfxCtx, GUI}; +use ezgui::{Canvas, EventCtx, EventLoopMode, GfxCtx, Wizard, GUI}; // This is the top-level of the GUI logic. This module should just manage interactions between the // top-level game states. @@ -195,3 +195,38 @@ pub enum Transition { PushWithMode(Box, EventLoopMode), ReplaceWithMode(Box, EventLoopMode), } + +// TODO Maybe let callers stash expensive data computed once here, and let the cb borrow it. +// Use cases: both BrowseTrips's +pub struct WizardState { + wizard: Wizard, + // Returning None means stay in this WizardState + cb: Box Option>, +} + +impl WizardState { + pub fn new( + cb: Box Option>, + ) -> Box { + Box::new(WizardState { + wizard: Wizard::new(), + cb, + }) + } +} + +impl State for WizardState { + fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition { + ctx.canvas.handle_event(ctx.input); + if let Some(t) = (self.cb)(&mut self.wizard, ctx, ui) { + return t; + } else if self.wizard.aborted() { + return Transition::Pop; + } + Transition::Keep + } + + fn draw(&self, g: &mut GfxCtx, _: &UI) { + self.wizard.draw(g); + } +} diff --git a/editor/src/sandbox/mod.rs b/editor/src/sandbox/mod.rs index 248d9b1a6b..6949a77423 100644 --- a/editor/src/sandbox/mod.rs +++ b/editor/src/sandbox/mod.rs @@ -7,11 +7,9 @@ use crate::common::{ }; use crate::debug::DebugMode; use crate::edit::EditMode; -use crate::game::{State, Transition}; +use crate::game::{State, Transition, WizardState}; use crate::ui::{ShowEverything, UI}; -use ezgui::{ - hotkey, lctrl, EventCtx, EventLoopMode, GfxCtx, Key, ModalMenu, Text, Wizard, WrappedWizard, -}; +use ezgui::{hotkey, lctrl, EventCtx, EventLoopMode, GfxCtx, Key, ModalMenu, Text, Wizard}; use geom::Duration; use sim::Sim; @@ -175,10 +173,7 @@ impl State for SandboxMode { }); } if self.menu.action("pick a savestate to load") { - return Transition::Push(Box::new(LoadSavestate { - path: ui.primary.sim.save_dir(), - wizard: Wizard::new(), - })); + return Transition::Push(WizardState::new(Box::new(load_savestate))); } if let Some(t) = time_controls(ctx, ui, &mut self.menu) { @@ -213,41 +208,22 @@ impl State for SandboxMode { } } -struct LoadSavestate { - path: String, - wizard: Wizard, -} +fn load_savestate(wiz: &mut Wizard, ctx: &mut EventCtx, ui: &mut UI) -> Option { + let path = ui.primary.sim.save_dir(); -impl State for LoadSavestate { - fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition { - if let Some(ss) = pick_savestate(&self.path, &mut self.wizard.wrap(ctx)) { - ctx.loading_screen("load savestate", |ctx, mut timer| { - ui.primary.sim = Sim::load_savestate(ss, &mut timer).unwrap(); - ui.recalculate_current_selection(ctx); - }); - return Transition::Pop; - } else if self.wizard.aborted() { - return Transition::Pop; - } - Transition::Keep - } + let (ss, _) = wiz.wrap(ctx).choose_something_no_keys::<()>( + "Load which savestate?", + Box::new(move || { + abstutil::list_dir(std::path::Path::new(&path)) + .into_iter() + .map(|f| (f, ())) + .collect() + }), + )?; - fn draw(&self, g: &mut GfxCtx, _: &UI) { - self.wizard.draw(g); - } -} - -fn pick_savestate(path: &str, wizard: &mut WrappedWizard) -> Option { - let path_copy = path.to_string(); - wizard - .choose_something_no_keys::<()>( - "Load which savestate?", - Box::new(move || { - abstutil::list_dir(std::path::Path::new(&path_copy)) - .into_iter() - .map(|f| (f, ())) - .collect() - }), - ) - .map(|(f, _)| f) + ctx.loading_screen("load savestate", |ctx, mut timer| { + ui.primary.sim = Sim::load_savestate(ss, &mut timer).expect("Can't load savestate"); + ui.recalculate_current_selection(ctx); + }); + Some(Transition::Pop) } diff --git a/editor/src/sandbox/score.rs b/editor/src/sandbox/score.rs index ece39abab7..c806113f98 100644 --- a/editor/src/sandbox/score.rs +++ b/editor/src/sandbox/score.rs @@ -1,12 +1,11 @@ -use crate::game::{State, Transition}; +use crate::game::{State, Transition, WizardState}; use crate::ui::UI; use ezgui::{ hotkey, EventCtx, GfxCtx, HorizontalAlignment, Key, ModalMenu, Text, VerticalAlignment, Wizard, - WrappedWizard, }; use geom::{Duration, DurationHistogram}; use itertools::Itertools; -use sim::{FinishedTrips, TripID, TripMode}; +use sim::{TripID, TripMode}; pub struct Scoreboard { menu: ModalMenu, @@ -47,16 +46,13 @@ impl Scoreboard { } impl State for Scoreboard { - fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition { + fn event(&mut self, ctx: &mut EventCtx, _: &mut UI) -> Transition { self.menu.handle_event(ctx, None); if self.menu.action("quit") { return Transition::Pop; } if self.menu.action("browse trips") { - return Transition::Push(Box::new(BrowseTrips { - trips: ui.primary.sim.get_finished_trips(), - wizard: Wizard::new(), - })); + return Transition::Push(WizardState::new(Box::new(browse_trips))); } Transition::Keep } @@ -70,42 +66,21 @@ impl State for Scoreboard { } } -struct BrowseTrips { - trips: FinishedTrips, - wizard: Wizard, -} - -impl State for BrowseTrips { - fn event(&mut self, ctx: &mut EventCtx, _: &mut UI) -> Transition { - if pick_trip(&self.trips, &mut self.wizard.wrap(ctx)).is_some() { - // TODO show trip departure, where it started and ended - return Transition::Pop; - } else if self.wizard.aborted() { - return Transition::Pop; - } - Transition::Keep - } - - fn draw(&self, g: &mut GfxCtx, _: &UI) { - self.wizard.draw(g); - } -} - -fn pick_trip(trips: &FinishedTrips, wizard: &mut WrappedWizard) -> Option { - let mode = wizard - .choose_something_no_keys::( - "Browse which trips?", - Box::new(|| { - vec![ - ("walk".to_string(), TripMode::Walk), - ("bike".to_string(), TripMode::Bike), - ("transit".to_string(), TripMode::Transit), - ("drive".to_string(), TripMode::Drive), - ] - }), - )? - .1; +fn browse_trips(wiz: &mut Wizard, ctx: &mut EventCtx, ui: &mut UI) -> Option { + let mut wizard = wiz.wrap(ctx); + let (_, mode) = wizard.choose_something_no_keys::( + "Browse which trips?", + Box::new(|| { + vec![ + ("walk".to_string(), TripMode::Walk), + ("bike".to_string(), TripMode::Bike), + ("transit".to_string(), TripMode::Transit), + ("drive".to_string(), TripMode::Drive), + ] + }), + )?; // TODO Ewwww. Can't do this inside choices_generator because trips isn't &'a static. + let trips = ui.primary.sim.get_finished_trips(); let mut filtered: Vec<&(TripID, TripMode, Duration)> = trips .finished_trips .iter() @@ -118,10 +93,10 @@ fn pick_trip(trips: &FinishedTrips, wizard: &mut WrappedWizard) -> Option( - "Examine which trip?", - Box::new(move || choices.clone()), - ) - .map(|(_, id)| id) + wizard.choose_something_no_keys::( + "Examine which trip?", + Box::new(move || choices.clone()), + )?; + // TODO show trip departure, where it started and ended + Some(Transition::Pop) } diff --git a/editor/src/sandbox/spawner.rs b/editor/src/sandbox/spawner.rs index 0bdb7f67b5..99ff5c0f4e 100644 --- a/editor/src/sandbox/spawner.rs +++ b/editor/src/sandbox/spawner.rs @@ -1,14 +1,13 @@ use crate::common::CommonState; -use crate::game::{State, Transition}; +use crate::game::{State, Transition, WizardState}; use crate::helpers::ID; use crate::render::DrawOptions; use crate::ui::{ShowEverything, UI}; use abstutil::Timer; -use ezgui::{hotkey, EventCtx, GfxCtx, Key, ModalMenu, Wizard, WrappedWizard}; +use ezgui::{hotkey, EventCtx, GfxCtx, Key, ModalMenu, Wizard}; use geom::{Duration, PolyLine}; use map_model::{ - BuildingID, IntersectionID, IntersectionType, LaneType, Map, PathRequest, Position, - LANE_THICKNESS, + BuildingID, IntersectionID, IntersectionType, LaneType, PathRequest, Position, LANE_THICKNESS, }; use rand::seq::SliceRandom; use rand::Rng; @@ -94,9 +93,7 @@ impl AgentSpawner { } None => { if ui.primary.sim.is_empty() && sandbox_menu.action("start a scenario") { - return Some(Box::new(InstantiateScenario { - wizard: Wizard::new(), - })); + return Some(WizardState::new(Box::new(instantiate_scenario))); } } _ => {} @@ -358,51 +355,17 @@ fn spawn_agents_around(i: IntersectionID, ui: &mut UI, ctx: &EventCtx) { ui.recalculate_current_selection(ctx); } -// TODO Dedupe with code from mission/mod.rs. -struct InstantiateScenario { - wizard: Wizard, -} - -impl State for InstantiateScenario { - fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition { - if let Some(scenario) = pick_scenario( - ui.primary.current_flags.num_agents, - &ui.primary.map, - &mut self.wizard.wrap(ctx), - ) { - ctx.loading_screen("instantiate scenario", |_, timer| { - scenario.instantiate( - &mut ui.primary.sim, - &ui.primary.map, - &mut ui.primary.current_flags.sim_flags.make_rng(), - timer, - ); - ui.primary.sim.step(&ui.primary.map, SMALL_DT); - }); - return Transition::Pop; - } else if self.wizard.aborted() { - return Transition::Pop; - } - Transition::Keep - } - - fn draw(&self, g: &mut GfxCtx, _: &UI) { - self.wizard.draw(g); - } -} - -fn pick_scenario( - num_agents: Option, - map: &Map, - wizard: &mut WrappedWizard, -) -> Option { +fn instantiate_scenario(wiz: &mut Wizard, ctx: &mut EventCtx, ui: &mut UI) -> Option { + let num_agents = ui.primary.current_flags.num_agents; let builtin = if let Some(n) = num_agents { format!("random scenario with {} agents", n) } else { "random scenario with some agents".to_string() }; + let map = &ui.primary.map; let map_name = map.get_name().to_string(); - let (_, scenario_name) = wizard.choose_something_no_keys::( + + let (_, scenario_name) = wiz.wrap(ctx).choose_something_no_keys::( "Instantiate which scenario?", Box::new(move || { let mut list = vec![ @@ -413,7 +376,8 @@ fn pick_scenario( list }), )?; - Some(if scenario_name == "builtin" { + + let scenario = if scenario_name == "builtin" { if let Some(n) = num_agents { Scenario::scaled_run(map, n) } else { @@ -427,5 +391,15 @@ fn pick_scenario( &mut Timer::throwaway(), ) .unwrap() - }) + }; + ctx.loading_screen("instantiate scenario", |_, timer| { + scenario.instantiate( + &mut ui.primary.sim, + &ui.primary.map, + &mut ui.primary.current_flags.sim_flags.make_rng(), + timer, + ); + ui.primary.sim.step(&ui.primary.map, SMALL_DT); + }); + Some(Transition::Pop) }