diff --git a/editor/src/edit/mod.rs b/editor/src/edit/mod.rs index deb552a707..d722496238 100644 --- a/editor/src/edit/mod.rs +++ b/editor/src/edit/mod.rs @@ -17,7 +17,7 @@ use map_model::{ use std::collections::{BTreeSet, HashMap}; pub enum EditMode { - ViewingDiffs(CommonState), + ViewingDiffs(CommonState, NewModalMenu), Saving(Wizard), Loading(Wizard), EditingStopSign(IntersectionID, NewModalMenu), @@ -25,37 +25,39 @@ pub enum EditMode { } impl EditMode { - pub fn new() -> EditMode { - EditMode::ViewingDiffs(CommonState::new()) + pub fn new(ctx: &EventCtx) -> EditMode { + EditMode::ViewingDiffs( + CommonState::new(), + NewModalMenu::new( + "Map Edit Mode", + vec![ + (Key::Escape, "quit"), + (Key::S, "save edits"), + (Key::L, "load different edits"), + ], + ctx, + ), + ) } pub fn event(state: &mut GameState, ctx: &mut EventCtx) -> EventLoopMode { - ctx.canvas.handle_event(ctx.input); - - // Common functionality - let mut txt = Text::prompt("Map Edit Mode"); - txt.add_line(state.ui.primary.map.get_edits().edits_name.clone()); - txt.add_line(state.ui.primary.map.get_edits().describe()); - txt.add_line("Right-click a lane or intersection to start editing".to_string()); match state.mode { - Mode::Edit(EditMode::ViewingDiffs(_)) - | Mode::Edit(EditMode::Saving(_)) - | Mode::Edit(EditMode::Loading(_)) => { + Mode::Edit(EditMode::ViewingDiffs(ref mut common, ref mut menu)) => { + let mut txt = Text::prompt("Map Edit Mode"); // TODO Display info/hints on more lines. - ctx.input - .set_mode_with_new_prompt("Map Edit Mode", txt, ctx.canvas); - // TODO Clicking this works, but the key doesn't - if ctx.input.modal_action("quit") { + txt.add_line(state.ui.primary.map.get_edits().edits_name.clone()); + txt.add_line(state.ui.primary.map.get_edits().describe()); + txt.add_line("Right-click a lane or intersection to start editing".to_string()); + menu.update_prompt(txt, ctx); + menu.handle_event(ctx); + if menu.action("quit") { // TODO Warn about unsaved edits state.mode = Mode::SplashScreen(Wizard::new(), None); return EventLoopMode::InputOnly; } - } - _ => {} - } - match state.mode { - Mode::Edit(EditMode::ViewingDiffs(ref mut common)) => { + ctx.canvas.handle_event(ctx.input); + // TODO Reset when transitioning in/out of this state? Or maybe we just don't draw // the effects of it. Or eventually, the Option itself will live in here // directly. @@ -72,10 +74,10 @@ impl EditMode { } // TODO Only if current edits are unsaved - if ctx.input.modal_action("save edits") { + if menu.action("save edits") { state.mode = Mode::Edit(EditMode::Saving(Wizard::new())); return EventLoopMode::InputOnly; - } else if ctx.input.modal_action("load different edits") { + } else if menu.action("load different edits") { state.mode = Mode::Edit(EditMode::Loading(Wizard::new())); return EventLoopMode::InputOnly; } @@ -131,7 +133,7 @@ impl EditMode { .is_some() || wizard.aborted() { - state.mode = Mode::Edit(EditMode::new()); + state.mode = Mode::Edit(EditMode::new(ctx)); } } Mode::Edit(EditMode::Loading(ref mut wizard)) => { @@ -141,13 +143,14 @@ impl EditMode { "Load which map edits?", ) { apply_map_edits(&mut state.ui, ctx, new_edits); - state.mode = Mode::Edit(EditMode::new()); + state.mode = Mode::Edit(EditMode::new(ctx)); } else if wizard.aborted() { - state.mode = Mode::Edit(EditMode::new()); + state.mode = Mode::Edit(EditMode::new(ctx)); } } Mode::Edit(EditMode::EditingStopSign(i, ref mut menu)) => { menu.handle_event(ctx); + ctx.canvas.handle_event(ctx.input); state.ui.primary.current_selection = state.ui.handle_mouseover( ctx, @@ -181,7 +184,7 @@ impl EditMode { apply_map_edits(&mut state.ui, ctx, new_edits); } } else if menu.action("quit") { - state.mode = Mode::Edit(EditMode::new()); + state.mode = Mode::Edit(EditMode::new(ctx)); } else if menu.action("reset to default") { let mut new_edits = state.ui.primary.map.get_edits().clone(); new_edits.stop_sign_overrides.remove(&i); @@ -190,7 +193,7 @@ impl EditMode { } Mode::Edit(EditMode::EditingTrafficSignal(ref mut editor)) => { if editor.event(ctx, &mut state.ui) { - state.mode = Mode::Edit(EditMode::new()); + state.mode = Mode::Edit(EditMode::new(ctx)); } } _ => unreachable!(), @@ -201,7 +204,7 @@ impl EditMode { pub fn draw(state: &GameState, g: &mut GfxCtx) { match state.mode { - Mode::Edit(EditMode::ViewingDiffs(ref common)) => { + Mode::Edit(EditMode::ViewingDiffs(ref common, ref menu)) => { state.ui.draw( g, common.draw_options(&state.ui), @@ -209,6 +212,7 @@ impl EditMode { &ShowEverything::new(), ); common.draw(g, &state.ui); + menu.draw(g); // TODO Similar to drawing areas with traffic or not -- would be convenient to just // supply a set of things to highlight and have something else take care of drawing diff --git a/editor/src/edit/traffic_signals.rs b/editor/src/edit/traffic_signals.rs index 8327b5d1d6..2c2982c33f 100644 --- a/editor/src/edit/traffic_signals.rs +++ b/editor/src/edit/traffic_signals.rs @@ -56,6 +56,7 @@ impl TrafficSignalEditor { pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> bool { self.menu.handle_event(ctx); self.diagram_top_left = self.menu.get_bottom_left(ctx); + ctx.canvas.handle_event(ctx.input); ui.primary.current_selection = ui.handle_mouseover( ctx, diff --git a/editor/src/game.rs b/editor/src/game.rs index 7e1c84d1c2..50e5d9756b 100644 --- a/editor/src/game.rs +++ b/editor/src/game.rs @@ -40,7 +40,7 @@ impl GameState { let splash = !flags.no_splash; let mut rng = flags.sim_flags.make_rng(); let mut game = GameState { - mode: Mode::Sandbox(SandboxMode::new()), + mode: Mode::Sandbox(SandboxMode::new(canvas)), ui: UI::new(flags, prerender, canvas), }; if splash { @@ -71,45 +71,6 @@ impl GameState { impl GUI for GameState { fn modal_menus(&self) -> Vec { vec![ - ModalMenu::new( - "Map Edit Mode", - vec![ - (Key::Escape, "quit"), - (Key::S, "save edits"), - (Key::L, "load different edits"), - ], - ), - ModalMenu::new( - "Sandbox Mode", - vec![ - (Key::Escape, "quit"), - (Key::LeftBracket, "slow down sim"), - (Key::RightBracket, "speed up sim"), - (Key::O, "save sim state"), - (Key::Y, "load previous sim state"), - (Key::U, "load next sim state"), - (Key::Space, "run/pause sim"), - (Key::M, "run one step of sim"), - (Key::X, "reset sim"), - (Key::S, "seed the sim with agents"), - // TODO Strange to always have this. Really it's a case of stacked modal? - (Key::F, "stop following agent"), - (Key::R, "stop showing agent's route"), - // TODO This should probably be a debug thing instead - (Key::L, "show/hide route for all agents"), - (Key::A, "show/hide active traffic"), - (Key::T, "start time traveling"), - ], - ), - ModalMenu::new("Agent Spawner", vec![(Key::Escape, "quit")]), - ModalMenu::new( - "Time Traveler", - vec![ - (Key::Escape, "quit"), - (Key::Comma, "rewind"), - (Key::Dot, "forwards"), - ], - ), ModalMenu::new( "Debug Mode", vec![ @@ -345,7 +306,7 @@ fn splash_screen( )? .as_str() { - x if x == sandbox => break Some(Mode::Sandbox(SandboxMode::new())), + x if x == sandbox => break Some(Mode::Sandbox(SandboxMode::new(ctx.canvas))), x if x == load_map => { let current_map = ui.primary.map.get_name().to_string(); if let Some((name, _)) = wizard.choose_something_no_keys::( @@ -361,14 +322,14 @@ fn splash_screen( let mut flags = ui.primary.current_flags.clone(); flags.sim_flags.load = PathBuf::from(format!("../data/maps/{}.abst", name)); *ui = UI::new(flags, ctx.prerender, ctx.canvas); - break Some(Mode::Sandbox(SandboxMode::new())); + break Some(Mode::Sandbox(SandboxMode::new(ctx.canvas))); } else if wizard.aborted() { break Some(Mode::SplashScreen(Wizard::new(), maybe_screensaver.take())); } else { break None; } } - x if x == edit => break Some(Mode::Edit(EditMode::new())), + x if x == edit => break Some(Mode::Edit(EditMode::new(ctx))), x if x == tutorial => { break Some(Mode::Tutorial(TutorialMode::Part1( ctx.canvas.center_to_map_pt(), diff --git a/editor/src/mission/scenario.rs b/editor/src/mission/scenario.rs index 7a2986be0b..49c3e682e6 100644 --- a/editor/src/mission/scenario.rs +++ b/editor/src/mission/scenario.rs @@ -48,7 +48,7 @@ impl ScenarioEditor { &mut ui.primary.current_flags.sim_flags.make_rng(), &mut Timer::new("instantiate scenario"), ); - return Some(Mode::Sandbox(SandboxMode::new())); + return Some(Mode::Sandbox(SandboxMode::new(ctx.canvas))); } else if ctx.input.modal_action("visualize") { let neighborhoods = Neighborhood::load_all( ui.primary.map.get_name(), diff --git a/editor/src/sandbox/mod.rs b/editor/src/sandbox/mod.rs index 89fba9719d..770edfee79 100644 --- a/editor/src/sandbox/mod.rs +++ b/editor/src/sandbox/mod.rs @@ -8,7 +8,7 @@ use crate::game::{GameState, Mode}; use crate::render::DrawOptions; use crate::ui::ShowEverything; use abstutil::elapsed_seconds; -use ezgui::{EventCtx, EventLoopMode, GfxCtx, Key, Text, Wizard}; +use ezgui::{Canvas, EventCtx, EventLoopMode, GfxCtx, Key, NewModalMenu, Text, Wizard}; use geom::Duration; use sim::{Benchmark, Sim, TripID}; use std::time::Instant; @@ -24,6 +24,7 @@ pub struct SandboxMode { state: State, // TODO Not while Spawning or TimeTraveling... common: CommonState, + menu: NewModalMenu, } enum State { @@ -38,33 +39,44 @@ enum State { } impl SandboxMode { - pub fn new() -> SandboxMode { + pub fn new(canvas: &Canvas) -> SandboxMode { SandboxMode { desired_speed: 1.0, state: State::Paused, following: None, route_viewer: route_viewer::RouteViewer::Inactive, show_activity: show_activity::ShowActivity::Inactive, - time_travel: time_travel::TimeTravel::new(), + time_travel: time_travel::TimeTravel::new(canvas), common: CommonState::new(), + menu: NewModalMenu::hacky_new( + "Sandbox Mode", + vec![ + (Key::Escape, "quit"), + (Key::LeftBracket, "slow down sim"), + (Key::RightBracket, "speed up sim"), + (Key::O, "save sim state"), + (Key::Y, "load previous sim state"), + (Key::U, "load next sim state"), + (Key::Space, "run/pause sim"), + (Key::M, "run one step of sim"), + (Key::X, "reset sim"), + (Key::S, "seed the sim with agents"), + // TODO Strange to always have this. Really it's a case of stacked modal? + (Key::F, "stop following agent"), + (Key::R, "stop showing agent's route"), + // TODO This should probably be a debug thing instead + (Key::L, "show/hide route for all agents"), + (Key::A, "show/hide active traffic"), + (Key::T, "start time traveling"), + ], + canvas, + ), } } pub fn event(state: &mut GameState, ctx: &mut EventCtx) -> EventLoopMode { match state.mode { Mode::Sandbox(ref mut mode) => { - ctx.canvas.handle_event(ctx.input); - state.ui.primary.current_selection = state.ui.handle_mouseover( - ctx, - None, - &state.ui.primary.sim, - &ShowEverything::new(), - false, - ); - if let Some(evmode) = mode.common.event(ctx, &state.ui) { - return evmode; - } - if let State::Spawning(ref mut spawner) = mode.state { if spawner.event(ctx, &mut state.ui) { mode.state = State::Paused; @@ -110,10 +122,24 @@ impl SandboxMode { txt.add_line("Showing active traffic".to_string()); } } - ctx.input - .set_mode_with_new_prompt("Sandbox Mode", txt, ctx.canvas); + mode.menu.update_prompt(txt, ctx); + mode.menu.handle_event(ctx); - if let Some(spawner) = spawner::AgentSpawner::new(ctx, &mut state.ui) { + ctx.canvas.handle_event(ctx.input); + state.ui.primary.current_selection = state.ui.handle_mouseover( + ctx, + None, + &state.ui.primary.sim, + &ShowEverything::new(), + false, + ); + if let Some(evmode) = mode.common.event(ctx, &state.ui) { + return evmode; + } + + if let Some(spawner) = + spawner::AgentSpawner::new(ctx, &mut state.ui, &mut mode.menu) + { mode.state = State::Spawning(spawner); return EventLoopMode::InputOnly; } @@ -148,13 +174,13 @@ impl SandboxMode { // get_canonical_point_for_trip println!("{} is gone... temporarily or not?", trip); } - if ctx.input.modal_action("stop following agent") { + if mode.menu.action("stop following agent") { mode.following = None; } } - mode.route_viewer.event(ctx, &mut state.ui); - mode.show_activity.event(ctx, &mut state.ui); - if ctx.input.modal_action("start time traveling") { + mode.route_viewer.event(ctx, &mut state.ui, &mut mode.menu); + mode.show_activity.event(ctx, &mut state.ui, &mut mode.menu); + if mode.menu.action("start time traveling") { mode.state = State::TimeTraveling; mode.time_travel.start(state.ui.primary.sim.time()); // Do this again, in case recording was previously disabled. @@ -162,7 +188,7 @@ impl SandboxMode { return EventLoopMode::InputOnly; } - if ctx.input.modal_action("quit") { + if mode.menu.action("quit") { // TODO This shouldn't be necessary when we plumb state around instead of // sharing it in the old structure. state.ui.primary.sim = Sim::new( @@ -174,14 +200,14 @@ impl SandboxMode { return EventLoopMode::InputOnly; } - if ctx.input.modal_action("slow down sim") { + if mode.menu.action("slow down sim") { mode.desired_speed -= ADJUST_SPEED; mode.desired_speed = mode.desired_speed.max(0.0); } - if ctx.input.modal_action("speed up sim") { + if mode.menu.action("speed up sim") { mode.desired_speed += ADJUST_SPEED; } - if !state.ui.primary.sim.is_empty() && ctx.input.modal_action("reset sim") { + if !state.ui.primary.sim.is_empty() && mode.menu.action("reset sim") { // TODO savestate_every gets lost state.ui.primary.sim = Sim::new( &state.ui.primary.map, @@ -193,10 +219,10 @@ impl SandboxMode { match mode.state { State::Paused => { - if ctx.input.modal_action("save sim state") { + if mode.menu.action("save sim state") { state.ui.primary.sim.save(); } - if ctx.input.modal_action("load previous sim state") { + if mode.menu.action("load previous sim state") { let prev_state = state .ui .primary @@ -215,7 +241,7 @@ impl SandboxMode { } } } - if ctx.input.modal_action("load next sim state") { + if mode.menu.action("load next sim state") { let next_state = state .ui .primary @@ -233,13 +259,13 @@ impl SandboxMode { } } - if ctx.input.modal_action("run/pause sim") { + if mode.menu.action("run/pause sim") { mode.state = State::Running { last_step: Instant::now(), benchmark: state.ui.primary.sim.start_benchmark(), speed: "...".to_string(), }; - } else if ctx.input.modal_action("run one step of sim") { + } else if mode.menu.action("run one step of sim") { state.ui.primary.sim.step(&state.ui.primary.map); //*ctx.recalculate_current_selection = true; } @@ -250,7 +276,7 @@ impl SandboxMode { ref mut benchmark, ref mut speed, } => { - if ctx.input.modal_action("run/pause sim") { + if mode.menu.action("run/pause sim") { mode.state = State::Paused; } else if ctx.input.nonblocking_is_update_event() { // TODO https://gafferongames.com/post/fix_your_timestep/ @@ -293,6 +319,7 @@ impl SandboxMode { &mode.time_travel, &ShowEverything::new(), ); + mode.time_travel.draw(g); } _ => { state.ui.draw( @@ -302,6 +329,7 @@ impl SandboxMode { &ShowEverything::new(), ); mode.common.draw(g, &state.ui); + mode.menu.draw(g); mode.route_viewer.draw(g, &state.ui); mode.show_activity.draw(g, &state.ui); } diff --git a/editor/src/sandbox/route_viewer.rs b/editor/src/sandbox/route_viewer.rs index 46412e7199..6c8e11d186 100644 --- a/editor/src/sandbox/route_viewer.rs +++ b/editor/src/sandbox/route_viewer.rs @@ -1,6 +1,6 @@ use crate::helpers::ID; use crate::ui::UI; -use ezgui::{Color, EventCtx, GfxCtx, Key}; +use ezgui::{Color, EventCtx, GfxCtx, Key, NewModalMenu}; use geom::{Duration, PolyLine}; use map_model::LANE_THICKNESS; use sim::{AgentID, TripID}; @@ -13,14 +13,14 @@ pub enum RouteViewer { } impl RouteViewer { - pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) { + pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI, menu: &mut NewModalMenu) { match self { RouteViewer::Inactive => { if let Some(agent) = ui.primary.current_selection.and_then(|id| id.agent_id()) { if let Some(trace) = ui.primary.sim.trace_route(agent, &ui.primary.map, None) { *self = RouteViewer::Hovering(ui.primary.sim.time(), agent, trace); } - } else if ctx.input.modal_action("show/hide route for all agents") { + } else if menu.action("show/hide route for all agents") { *self = debug_all_routes(ui); } } @@ -56,14 +56,14 @@ impl RouteViewer { } RouteViewer::Active(time, trip, _) => { // TODO Using the modal menu from parent is weird... - if ctx.input.modal_action("stop showing agent's route") { + if menu.action("stop showing agent's route") { *self = RouteViewer::Inactive; } else if *time != ui.primary.sim.time() { *self = show_route(*trip, ui); } } RouteViewer::DebugAllRoutes(time, _) => { - if ctx.input.modal_action("show/hide route for all agents") { + if menu.action("show/hide route for all agents") { *self = RouteViewer::Inactive; } else if *time != ui.primary.sim.time() { *self = debug_all_routes(ui); diff --git a/editor/src/sandbox/show_activity.rs b/editor/src/sandbox/show_activity.rs index 07376a7936..91d485c6a6 100644 --- a/editor/src/sandbox/show_activity.rs +++ b/editor/src/sandbox/show_activity.rs @@ -1,6 +1,6 @@ use crate::render::MIN_ZOOM_FOR_DETAIL; use crate::ui::UI; -use ezgui::{Color, EventCtx, GfxCtx}; +use ezgui::{Color, EventCtx, GfxCtx, NewModalMenu}; use geom::{Bounds, Duration, Polygon, Pt2D}; use map_model::{RoadID, Traversable}; use std::collections::HashMap; @@ -12,18 +12,18 @@ pub enum ShowActivity { } impl ShowActivity { - pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) { + pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI, menu: &mut NewModalMenu) { let zoomed = ctx.canvas.cam_zoom >= MIN_ZOOM_FOR_DETAIL; // If we survive past this, recompute current state. match self { ShowActivity::Inactive => { - if !ctx.input.modal_action("show/hide active traffic") { + if !menu.action("show/hide active traffic") { return; } } ShowActivity::Zoomed(time, ref heatmap) => { - if ctx.input.modal_action("show/hide active traffic") { + if menu.action("show/hide active traffic") { *self = ShowActivity::Inactive; return; } @@ -35,7 +35,7 @@ impl ShowActivity { } } ShowActivity::Unzoomed(time, _) => { - if ctx.input.modal_action("show/hide active traffic") { + if menu.action("show/hide active traffic") { *self = ShowActivity::Inactive; return; } diff --git a/editor/src/sandbox/spawner.rs b/editor/src/sandbox/spawner.rs index 04093be34f..1168eb84ba 100644 --- a/editor/src/sandbox/spawner.rs +++ b/editor/src/sandbox/spawner.rs @@ -2,7 +2,7 @@ use crate::helpers::ID; use crate::render::DrawOptions; use crate::ui::{ShowEverything, UI}; use abstutil::Timer; -use ezgui::{EventCtx, GfxCtx, Key}; +use ezgui::{EventCtx, GfxCtx, Key, NewModalMenu}; use geom::PolyLine; use map_model::{ BuildingID, IntersectionID, IntersectionType, LaneType, PathRequest, Position, LANE_THICKNESS, @@ -11,6 +11,7 @@ use rand::seq::SliceRandom; use sim::{DrivingGoal, Scenario, SidewalkSpot, TripSpec}; pub struct AgentSpawner { + menu: NewModalMenu, from: Source, maybe_goal: Option<(Goal, Option)>, } @@ -28,7 +29,12 @@ enum Goal { } impl AgentSpawner { - pub fn new(ctx: &mut EventCtx, ui: &mut UI) -> Option { + pub fn new( + ctx: &mut EventCtx, + ui: &mut UI, + sandbox_menu: &mut NewModalMenu, + ) -> Option { + let menu = NewModalMenu::new("Agent Spawner", vec![(Key::Escape, "quit")], ctx); let map = &ui.primary.map; match ui.primary.current_selection { Some(ID::Building(id)) => { @@ -37,6 +43,7 @@ impl AgentSpawner { .contextual_action(Key::F3, "spawn a pedestrian starting here") { return Some(AgentSpawner { + menu, from: Source::Walking(id), maybe_goal: None, }); @@ -50,6 +57,7 @@ impl AgentSpawner { .contextual_action(Key::F4, "spawn a car starting here") { return Some(AgentSpawner { + menu, from: Source::Driving( b.front_path.sidewalk.equiv_pos(driving_lane, map), ), @@ -65,6 +73,7 @@ impl AgentSpawner { .contextual_action(Key::F3, "spawn an agent starting here") { return Some(AgentSpawner { + menu, from: Source::Driving(Position::new(id, map.get_l(id).length() / 2.0)), maybe_goal: None, }); @@ -80,8 +89,7 @@ impl AgentSpawner { } None => { if ui.primary.sim.is_empty() { - // TODO Weird. This mode belongs to the parent SpawnMode, not AgentSpawner. - if ctx.input.modal_action("seed the sim with agents") { + if sandbox_menu.action("seed the sim with agents") { Scenario::scaled_run(map, ui.primary.current_flags.num_agents).instantiate( &mut ui.primary.sim, map, @@ -100,11 +108,15 @@ impl AgentSpawner { // Returns true if the spawner editor is done and we should go back to main sandbox mode. pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> bool { // TODO Instructions to select target building/lane - ctx.input.set_mode("Agent Spawner", ctx.canvas); - if ctx.input.modal_action("quit") { + self.menu.handle_event(ctx); + if self.menu.action("quit") { return true; } + ctx.canvas.handle_event(ctx.input); + ui.primary.current_selection = + ui.handle_mouseover(ctx, None, &ui.primary.sim, &ShowEverything::new(), false); + let map = &ui.primary.map; let new_goal = match ui.primary.current_selection { @@ -236,6 +248,8 @@ impl AgentSpawner { opts.override_colors.insert(src, ui.cs.get("selected")); ui.draw(g, opts, &ui.primary.sim, &ShowEverything::new()); + self.menu.draw(g); + if let Some((_, Some(ref trace))) = self.maybe_goal { g.draw_polygon(ui.cs.get("route"), &trace.make_polygons(LANE_THICKNESS)); } diff --git a/editor/src/sandbox/time_travel.rs b/editor/src/sandbox/time_travel.rs index 4bd6a0ef79..a2a8f966a0 100644 --- a/editor/src/sandbox/time_travel.rs +++ b/editor/src/sandbox/time_travel.rs @@ -1,12 +1,13 @@ use crate::ui::UI; use abstutil::MultiMap; -use ezgui::EventCtx; +use ezgui::{Canvas, EventCtx, GfxCtx, Key, NewModalMenu, Text}; use geom::Duration; use map_model::{Map, Traversable}; use sim::{CarID, DrawCarInput, DrawPedestrianInput, GetDrawAgents, PedestrianID, TIMESTEP}; use std::collections::BTreeMap; pub struct TimeTravel { + menu: NewModalMenu, // TODO Could be more efficient state_per_time: BTreeMap, pub current_time: Option, @@ -23,7 +24,7 @@ struct StateAtTime { } impl TimeTravel { - pub fn new() -> TimeTravel { + pub fn new(canvas: &Canvas) -> TimeTravel { TimeTravel { state_per_time: BTreeMap::new(), current_time: None, @@ -31,6 +32,15 @@ impl TimeTravel { first_time: Duration::ZERO, last_time: Duration::ZERO, should_record: false, + menu: NewModalMenu::hacky_new( + "Time Traveler", + vec![ + (Key::Escape, "quit"), + (Key::Comma, "rewind"), + (Key::Dot, "forwards"), + ], + canvas, + ), } } @@ -81,22 +91,27 @@ impl TimeTravel { // Returns true if done. pub fn event(&mut self, ctx: &mut EventCtx) -> bool { let time = self.current_time.unwrap(); - ctx.input.set_mode_with_prompt( - "Time Traveler", - format!("Time Traveler at {}", time), - &ctx.canvas, - ); - if time > self.first_time && ctx.input.modal_action("rewind") { + self.menu.handle_event(ctx); + self.menu + .update_prompt(Text::prompt(&format!("Time Traveler at {}", time)), ctx); + + ctx.canvas.handle_event(ctx.input); + + if time > self.first_time && self.menu.action("rewind") { self.current_time = Some(time - TIMESTEP); - } else if time < self.last_time && ctx.input.modal_action("forwards") { + } else if time < self.last_time && self.menu.action("forwards") { self.current_time = Some(time + TIMESTEP); - } else if ctx.input.modal_action("quit") { + } else if self.menu.action("quit") { self.current_time = None; return true; } false } + pub fn draw(&self, g: &mut GfxCtx) { + self.menu.draw(g); + } + fn get_current_state(&self) -> &StateAtTime { &self.state_per_time[&self.current_time.unwrap()] } diff --git a/ezgui/src/widgets/modal_menu.rs b/ezgui/src/widgets/modal_menu.rs index 30a21341cd..9fe3ae9862 100644 --- a/ezgui/src/widgets/modal_menu.rs +++ b/ezgui/src/widgets/modal_menu.rs @@ -1,5 +1,5 @@ use crate::widgets::{Menu, Position}; -use crate::{EventCtx, GfxCtx, InputResult, Key, ScreenPt, Text}; +use crate::{Canvas, EventCtx, GfxCtx, InputResult, Key, ScreenPt, Text}; pub struct NewModalMenu { menu: Menu, @@ -8,6 +8,15 @@ pub struct NewModalMenu { impl NewModalMenu { pub fn new(prompt_line: &str, choices: Vec<(Key, &str)>, ctx: &EventCtx) -> NewModalMenu { + NewModalMenu::hacky_new(prompt_line, choices, ctx.canvas) + } + + // TODO Pass EventCtx when constructing the GUI? + pub fn hacky_new( + prompt_line: &str, + choices: Vec<(Key, &str)>, + canvas: &Canvas, + ) -> NewModalMenu { let mut menu = Menu::new( Some(Text::prompt(prompt_line)), choices @@ -17,7 +26,7 @@ impl NewModalMenu { false, true, Position::TopRightOfScreen, - ctx.canvas, + canvas, ); menu.mark_all_inactive(); NewModalMenu { @@ -62,6 +71,11 @@ impl NewModalMenu { false } + pub fn update_prompt(&mut self, txt: Text, _: &EventCtx) { + // TODO Do need to recalculate geometry + self.menu.change_prompt(txt); + } + pub fn draw(&self, g: &mut GfxCtx) { self.menu.draw(g); }