diff --git a/ezgui/src/widgets/wizard.rs b/ezgui/src/widgets/wizard.rs index 9dc79b4aef..1df0e7a968 100644 --- a/ezgui/src/widgets/wizard.rs +++ b/ezgui/src/widgets/wizard.rs @@ -349,15 +349,14 @@ impl<'a, 'b> WrappedWizard<'a, 'b> { self.wizard.alive = false; } - // Note this will abort the wizard once done! pub fn acknowledge, F: Fn() -> Vec>( &mut self, title: &str, make_lines: F, - ) -> bool { + ) -> Option<()> { if !self.ready_results.is_empty() { self.ready_results.pop_front(); - return true; + return Some(()); } if self.wizard.log_scroller.is_none() { @@ -375,9 +374,9 @@ impl<'a, 'b> WrappedWizard<'a, 'b> { { self.wizard.confirmed_state.push(Box::new(())); self.wizard.log_scroller = None; - true + Some(()) } else { - false + None } } } diff --git a/game/src/common/agent.rs b/game/src/common/agent.rs index 4cb63b4419..ac36978af1 100644 --- a/game/src/common/agent.rs +++ b/game/src/common/agent.rs @@ -1,6 +1,6 @@ use crate::common::route_viewer::RouteViewer; use crate::common::{ColorLegend, RouteExplorer, TripExplorer}; -use crate::game::{Transition, WizardState}; +use crate::game::{msg, Transition, WizardState}; use crate::render::{AgentColorScheme, MIN_ZOOM_FOR_DETAIL}; use crate::ui::UI; use ezgui::{hotkey, Choice, EventCtx, GfxCtx, Key, MenuUnderButton, ModalMenu}; @@ -71,9 +71,12 @@ impl AgentTools { self.following = Some((trip, None, ui.primary.sim.time())); } TripResult::TripDone => { - println!("{} is done or aborted, so no more following", trip); self.following = None; menu.remove_action("stop following agent", ctx); + return Some(Transition::Push(msg( + "Follower", + vec![format!("{} is done or aborted, so no more following", trip)], + ))); } TripResult::TripDoesntExist => { println!("{} doesn't exist yet, so not following", trip); diff --git a/game/src/common/warp.rs b/game/src/common/warp.rs index beb6634815..93dd3bd52f 100644 --- a/game/src/common/warp.rs +++ b/game/src/common/warp.rs @@ -25,10 +25,8 @@ fn warp_to(wiz: &mut Wizard, ctx: &mut EventCtx, ui: &mut UI) -> Option Box { cnt += 1; } } - // TODO pop this up. warn about road names changing and being weird. :) - println!( - "Changed {} {:?} lanes to {:?} lanes on {}", - cnt, from, to, road_name - ); + // TODO warn about road names changing and being weird. :) + wizard.acknowledge("Bulk lane edit", || { + vec![format!( + "Changed {} {:?} lanes to {:?} lanes on {}", + cnt, from, to, road_name + )] + })?; apply_map_edits(&mut ui.primary, &ui.cs, ctx, edits); Some(Transition::Pop) })) diff --git a/game/src/game.rs b/game/src/game.rs index 4eb12832d0..bffff7202d 100644 --- a/game/src/game.rs +++ b/game/src/game.rs @@ -193,3 +193,12 @@ impl State for WizardState { self.wizard.draw(g); } } + +// TODO Word wrap +pub fn msg>(title: &'static str, lines: Vec) -> Box { + let str_lines: Vec = lines.into_iter().map(|l| l.into()).collect(); + WizardState::new(Box::new(move |wiz, ctx, _| { + wiz.wrap(ctx).acknowledge(title, || str_lines.clone())?; + Some(Transition::Pop) + })) +} diff --git a/game/src/sandbox/gameplay.rs b/game/src/sandbox/gameplay.rs index 241ee7aced..e4979abf44 100644 --- a/game/src/sandbox/gameplay.rs +++ b/game/src/sandbox/gameplay.rs @@ -1,4 +1,4 @@ -use crate::game::{Transition, WizardState}; +use crate::game::{msg, Transition, WizardState}; use crate::render::AgentColorScheme; use crate::sandbox::{analytics, bus_explorer, spawner, SandboxMode}; use crate::ui::UI; @@ -195,7 +195,7 @@ impl GameplayState { )))); } if self.menu.action("help") { - return Some(help(vec!["This simulation is empty by default.", "Try right-clicking an intersection and choosing to spawn agents (or just hover over it and press Z).", "You can also spawn agents from buildings or lanes.", "You can also start a full scenario to get realistic traffic."])); + return Some(Transition::Push(msg("Help", vec!["This simulation is empty by default.", "Try right-clicking an intersection and choosing to spawn agents (or just hover over it and press Z).", "You can also spawn agents from buildings or lanes.", "You can also start a full scenario to get realistic traffic."]))); } if let Some(new_state) = spawner::AgentSpawner::new(ctx, ui) { return Some(Transition::Push(new_state)); @@ -209,11 +209,14 @@ impl GameplayState { )))); } if self.menu.action("help") { - return Some(help(vec![ - "Do things seem a bit quiet?", - "The simulation starts at midnight, so you might need to wait a bit.", - "Try using the speed controls on the left.", - ])); + return Some(Transition::Push(msg( + "Help", + vec![ + "Do things seem a bit quiet?", + "The simulation starts at midnight, so you might need to wait a bit.", + "Try using the speed controls on the left.", + ], + ))); } } State::OptimizeBus { @@ -279,12 +282,15 @@ impl GameplayState { )))); } if self.menu.action("help") { - return Some(help(vec![ - "First find where the bus gets stuck.", - "Then use edit mode to try to speed things up.", - "Try making dedicated bus lanes", - "and adjusting traffic signals.", - ])); + return Some(Transition::Push(msg( + "Help", + vec![ + "First find where the bus gets stuck.", + "Then use edit mode to try to speed things up.", + "Try making dedicated bus lanes", + "and adjusting traffic signals.", + ], + ))); } } State::CreateGridlock { ref mut time } => { @@ -304,11 +310,11 @@ impl GameplayState { } if self.menu.action("help") { - return Some(help(vec![ + return Some(Transition::Push(msg("Help", vec![ "You might notice a few places in the map where gridlock forms already.", "You can make things worse!", "How few lanes can you close for construction before everything grinds to a halt?", - ])); + ]))); } } State::FasterTrips { mode, ref mut time } => { @@ -320,9 +326,10 @@ impl GameplayState { } if self.menu.action("help") { - return Some(help(vec![ - "How can you possibly speed up all trips of some mode?", - ])); + return Some(Transition::Push(msg( + "Help", + vec!["How can you possibly speed up all trips of some mode?"], + ))); } } } @@ -442,17 +449,6 @@ fn change_scenario(wiz: &mut Wizard, ctx: &mut EventCtx, ui: &mut UI) -> Option< )))) } -// TODO Word wrap -fn help(lines: Vec<&'static str>) -> Transition { - Transition::Push(WizardState::new(Box::new(move |wiz, ctx, _| { - if wiz.wrap(ctx).acknowledge("Help", || lines.clone()) { - Some(Transition::Pop) - } else { - None - } - }))) -} - // Must call menu.event first. Returns true if the caller should set the analytics to the custom // thing. fn manage_analytics( diff --git a/game/src/sandbox/mod.rs b/game/src/sandbox/mod.rs index 98f34bc112..a14deda70e 100644 --- a/game/src/sandbox/mod.rs +++ b/game/src/sandbox/mod.rs @@ -8,7 +8,7 @@ mod trip_stats; use crate::common::{time_controls, AgentTools, CommonState, SpeedControls}; use crate::debug::DebugMode; use crate::edit::EditMode; -use crate::game::{State, Transition, WizardState}; +use crate::game::{msg, State, Transition, WizardState}; use crate::helpers::ID; use crate::ui::{ShowEverything, UI}; use ezgui::{ @@ -190,7 +190,7 @@ impl State for SandboxMode { } if self.save_tools.action("load previous sim state") { self.speed.pause(); - ctx.loading_screen("load previous savestate", |ctx, mut timer| { + if let Some(t) = ctx.loading_screen("load previous savestate", |ctx, mut timer| { let prev_state = ui .primary .sim @@ -202,14 +202,20 @@ impl State for SandboxMode { Some(new_sim) => { ui.primary.sim = new_sim; ui.recalculate_current_selection(ctx); + None } - None => println!("Couldn't load previous savestate {:?}", prev_state), + None => Some(Transition::Push(msg( + "Error", + vec![format!("Couldn't load previous savestate {:?}", prev_state)], + ))), } - }); + }) { + return t; + } } if self.save_tools.action("load next sim state") { self.speed.pause(); - ctx.loading_screen("load next savestate", |ctx, mut timer| { + if let Some(t) = ctx.loading_screen("load next savestate", |ctx, mut timer| { let next_state = ui.primary.sim.find_next_savestate(ui.primary.sim.time()); match next_state .clone() @@ -218,10 +224,16 @@ impl State for SandboxMode { Some(new_sim) => { ui.primary.sim = new_sim; ui.recalculate_current_selection(ctx); + None } - None => println!("Couldn't load next savestate {:?}", next_state), + None => Some(Transition::Push(msg( + "Error", + vec![format!("Couldn't load next savestate {:?}", next_state)], + ))), } - }); + }) { + return t; + } } if self.save_tools.action("pick a savestate to load") { self.speed.pause(); diff --git a/game/src/sandbox/spawner.rs b/game/src/sandbox/spawner.rs index c0a36530aa..469ca83a10 100644 --- a/game/src/sandbox/spawner.rs +++ b/game/src/sandbox/spawner.rs @@ -1,5 +1,5 @@ use crate::common::CommonState; -use crate::game::{State, Transition}; +use crate::game::{msg, State, Transition}; use crate::helpers::ID; use crate::render::DrawOptions; use crate::ui::{ShowEverything, UI}; @@ -213,7 +213,7 @@ impl State for AgentSpawner { if self.maybe_goal.is_some() && ctx.input.contextual_action(Key::F3, "end the agent here") { let mut rng = ui.primary.current_flags.sim_flags.make_rng(); let sim = &mut ui.primary.sim; - schedule_trip( + let err = schedule_trip( &self.from, self.maybe_goal.take().unwrap().0, map, @@ -223,7 +223,11 @@ impl State for AgentSpawner { sim.spawn_all_trips(map, &mut Timer::new("spawn trip"), false); sim.step(map, SMALL_DT); ui.recalculate_current_selection(ctx); - return Transition::Pop; + if let Some(e) = err { + return Transition::Replace(msg("Spawning error", vec![e])); + } else { + return Transition::Pop; + } } Transition::Keep @@ -311,7 +315,14 @@ fn spawn_agents_around(i: IntersectionID, ui: &mut UI, ctx: &EventCtx) { ui.recalculate_current_selection(ctx); } -fn schedule_trip(src: &Source, raw_goal: Goal, map: &Map, sim: &mut Sim, rng: &mut XorShiftRng) { +// Returns optional error message +fn schedule_trip( + src: &Source, + raw_goal: Goal, + map: &Map, + sim: &mut Sim, + rng: &mut XorShiftRng, +) -> Option { match src { Source::WalkFromBldg(_) | Source::WalkFromSidewalk(_) => { let start = match src { @@ -327,8 +338,7 @@ fn schedule_trip(src: &Source, raw_goal: Goal, map: &Map, sim: &mut Sim, rng: &m if let Some(goal) = SidewalkSpot::end_at_border(to, map) { goal } else { - println!("Can't end a walking trip at {}; no sidewalks", to); - return; + return Some(format!("Can't end a walking trip at {}; no sidewalks", to)); } } }; @@ -368,8 +378,7 @@ fn schedule_trip(src: &Source, raw_goal: Goal, map: &Map, sim: &mut Sim, rng: &m if let Some(g) = DrivingGoal::end_at_border(to, vec![LaneType::Driving], map) { g } else { - println!("Can't end a car trip at {}; no driving lanes", to); - return; + return Some(format!("Can't end a car trip at {}; no driving lanes", to)); } } }; @@ -387,7 +396,7 @@ fn schedule_trip(src: &Source, raw_goal: Goal, map: &Map, sim: &mut Sim, rng: &m map, ); } else { - println!("Can't make a car appear at {:?}", from); + return Some(format!("Can't make a car appear at {:?}", from)); } } Source::WalkFromBldgThenMaybeUseCar(b) => { @@ -405,4 +414,5 @@ fn schedule_trip(src: &Source, raw_goal: Goal, map: &Map, sim: &mut Sim, rng: &m } } } + None } diff --git a/game/src/splash_screen.rs b/game/src/splash_screen.rs index c3f229fba3..517700471c 100644 --- a/game/src/splash_screen.rs +++ b/game/src/splash_screen.rs @@ -193,7 +193,7 @@ fn splash_screen( x if x == tutorial => Some(Transition::Push(Box::new(TutorialMode::new(ctx)))), x if x == mission => Some(Transition::Push(Box::new(MissionEditMode::new(ctx)))), x if x == about => { - if wizard.acknowledge("About A/B Street", || { + wizard.acknowledge("About A/B Street", || { vec![ "Author: Dustin Carlino (dabreegster@gmail.com)", "http://github.com/dabreegster/abstreet", @@ -201,14 +201,11 @@ fn splash_screen( "", "Press ENTER to continue", ] - }) { - Some(Transition::Replace(Box::new(SplashScreen { - wizard: Wizard::new(), - maybe_screensaver: maybe_screensaver.take(), - }))) - } else { - None - } + })?; + Some(Transition::Replace(Box::new(SplashScreen { + wizard: Wizard::new(), + maybe_screensaver: maybe_screensaver.take(), + }))) } x if x == quit => Some(Transition::Pop), _ => unreachable!(),