diff --git a/editor/src/edit/mod.rs b/editor/src/edit/mod.rs index 308c6585e1..09778ec172 100644 --- a/editor/src/edit/mod.rs +++ b/editor/src/edit/mod.rs @@ -60,7 +60,10 @@ impl EditMode { // the effects of it. Or eventually, the Option itself will live in here // directly. // TODO Only mouseover lanes and intersections? - state.ui.handle_mouseover(ctx, None); + state.ui.state.primary.current_selection = + state + .ui + .handle_mouseover(ctx, None, &state.ui.state.primary.sim); if let Some(ID::Lane(id)) = state.ui.state.primary.current_selection { let lane = state.ui.state.primary.map.get_l(id); @@ -128,7 +131,10 @@ impl EditMode { } } Mode::Edit(EditMode::EditingStopSign(i)) => { - state.ui.handle_mouseover(ctx, Some(i)); + state.ui.state.primary.current_selection = + state + .ui + .handle_mouseover(ctx, Some(i), &state.ui.state.primary.sim); ctx.input.set_mode_with_prompt( "Stop Sign Editor", @@ -191,7 +197,9 @@ impl EditMode { match state.mode { Mode::Edit(EditMode::ViewingDiffs) => { - state.ui.new_draw(g, None, override_color); + state + .ui + .new_draw(g, None, override_color, &state.ui.state.primary.sim); // 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 @@ -235,7 +243,9 @@ impl EditMode { } Mode::Edit(EditMode::Saving(ref wizard)) | Mode::Edit(EditMode::Loading(ref wizard)) => { - state.ui.new_draw(g, None, override_color); + state + .ui + .new_draw(g, None, override_color, &state.ui.state.primary.sim); // TODO Still draw the diffs, yo wizard.draw(g); @@ -265,7 +275,9 @@ impl EditMode { }, ); } - state.ui.new_draw(g, Some(i), override_color); + state + .ui + .new_draw(g, Some(i), override_color, &state.ui.state.primary.sim); } Mode::Edit(EditMode::EditingTrafficSignal(ref editor)) => { editor.draw(g, state); diff --git a/editor/src/edit/traffic_signals.rs b/editor/src/edit/traffic_signals.rs index 634d6fd799..50fde0185e 100644 --- a/editor/src/edit/traffic_signals.rs +++ b/editor/src/edit/traffic_signals.rs @@ -46,7 +46,8 @@ impl TrafficSignalEditor { // Returns true if the editor is done and we should go back to main edit mode. pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> bool { - ui.handle_mouseover(ctx, Some(self.i)); + ui.state.primary.current_selection = + ui.handle_mouseover(ctx, Some(self.i), &ui.state.primary.sim); ctx.input.set_mode_with_prompt( "Traffic Signal Editor", @@ -255,7 +256,9 @@ impl TrafficSignalEditor { }, ); } - state.ui.new_draw(g, Some(self.i), override_color); + state + .ui + .new_draw(g, Some(self.i), override_color, &state.ui.state.primary.sim); let ctx = DrawCtx { cs: &state.ui.state.cs, diff --git a/editor/src/sandbox/mod.rs b/editor/src/sandbox/mod.rs index 17899d6ecb..e94641d575 100644 --- a/editor/src/sandbox/mod.rs +++ b/editor/src/sandbox/mod.rs @@ -1,6 +1,7 @@ mod route_viewer; mod show_activity; mod spawner; +mod time_travel; use crate::game::{GameState, Mode}; use abstutil::elapsed_seconds; @@ -17,6 +18,7 @@ pub struct SandboxMode { following: Option, route_viewer: route_viewer::RouteViewer, show_activity: show_activity::ShowActivity, + time_travel: time_travel::TimeTravel, state: State, } @@ -28,6 +30,7 @@ enum State { speed: String, }, Spawning(spawner::AgentSpawner), + TimeTraveling, } impl SandboxMode { @@ -38,6 +41,7 @@ impl SandboxMode { following: None, route_viewer: route_viewer::RouteViewer::Inactive, show_activity: show_activity::ShowActivity::Inactive, + time_travel: time_travel::TimeTravel::new(), } } @@ -45,7 +49,10 @@ impl SandboxMode { match state.mode { Mode::Sandbox(ref mut mode) => { ctx.canvas.handle_event(ctx.input); - state.ui.handle_mouseover(ctx, None); + state.ui.state.primary.current_selection = + state + .ui + .handle_mouseover(ctx, None, &state.ui.state.primary.sim); if let State::Spawning(ref mut spawner) = mode.state { if spawner.event(ctx, &mut state.ui) { @@ -53,7 +60,7 @@ impl SandboxMode { } return EventLoopMode::InputOnly; } - mode.time_travel.record_events(state); + mode.time_travel.record(&state.ui); if let State::TimeTraveling = mode.state { if mode.time_travel.event(ctx) { mode.state = State::Paused; @@ -139,6 +146,13 @@ impl SandboxMode { } 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.state = State::TimeTraveling; + mode.time_travel.start(state.ui.state.primary.sim.time()); + // Do this again, in case recording was previously disabled. + mode.time_travel.record(&state.ui); + return EventLoopMode::InputOnly; + } if ctx.input.modal_action("quit") { // TODO This shouldn't be necessary when we plumb state around instead of @@ -270,6 +284,7 @@ impl SandboxMode { EventLoopMode::Animation } State::Spawning(_) => unreachable!(), + State::TimeTraveling => unreachable!(), } } _ => unreachable!(), @@ -282,8 +297,15 @@ impl SandboxMode { State::Spawning(ref spawner) => { spawner.draw(g, &state.ui); } + State::TimeTraveling => { + state + .ui + .new_draw(g, None, HashMap::new(), &mode.time_travel); + } _ => { - state.ui.new_draw(g, None, HashMap::new()); + state + .ui + .new_draw(g, None, HashMap::new(), &state.ui.state.primary.sim); mode.route_viewer.draw(g, &state.ui); mode.show_activity.draw(g, &state.ui); } diff --git a/editor/src/sandbox/spawner.rs b/editor/src/sandbox/spawner.rs index 19300181ff..d12d964afd 100644 --- a/editor/src/sandbox/spawner.rs +++ b/editor/src/sandbox/spawner.rs @@ -235,7 +235,7 @@ impl AgentSpawner { }; let mut override_color = HashMap::new(); override_color.insert(src, ui.state.cs.get("selected")); - ui.new_draw(g, None, override_color); + ui.new_draw(g, None, override_color, &ui.state.primary.sim); if let Some((_, Some(ref trace))) = self.maybe_goal { g.draw_polygon( diff --git a/editor/src/sandbox/time_travel.rs b/editor/src/sandbox/time_travel.rs index 81500cd78c..ed8ca18d71 100644 --- a/editor/src/sandbox/time_travel.rs +++ b/editor/src/sandbox/time_travel.rs @@ -1,14 +1,15 @@ -use crate::plugins::PluginCtx; +use crate::ui::UI; use abstutil::MultiMap; +use ezgui::EventCtx; use geom::Duration; use map_model::{Map, Traversable}; -use sim::{CarID, DrawCarInput, DrawPedestrianInput, GetDrawAgents, PedestrianID, Sim, TIMESTEP}; +use sim::{CarID, DrawCarInput, DrawPedestrianInput, GetDrawAgents, PedestrianID, TIMESTEP}; use std::collections::BTreeMap; pub struct TimeTravel { // TODO Could be more efficient state_per_time: BTreeMap, - current_time: Option, + pub current_time: Option, first_time: Duration, last_time: Duration, should_record: bool, @@ -33,11 +34,18 @@ impl TimeTravel { } } - pub fn is_active(&self) -> bool { - self.current_time.is_some() + pub fn start(&mut self, time: Duration) { + assert!(self.current_time.is_none()); + self.should_record = true; + self.current_time = Some(time); } - fn record_state(&mut self, sim: &Sim, map: &Map) { + pub fn record(&mut self, ui: &UI) { + if !self.should_record { + return; + } + let map = &ui.state.primary.map; + let sim = &ui.state.primary.sim; let now = sim.time(); // Record state for this timestep, if needed. @@ -70,36 +78,27 @@ impl TimeTravel { self.state_per_time.insert(now, state); } - fn get_current_state(&self) -> &StateAtTime { - &self.state_per_time[&self.current_time.unwrap()] + // 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.current_time = Some(time - TIMESTEP); + } else if time < self.last_time && ctx.input.modal_action("forwards") { + self.current_time = Some(time + TIMESTEP); + } else if ctx.input.modal_action("quit") { + self.current_time = None; + return true; + } + false } - // Don't really need to indicate activeness here. - pub fn event(&mut self, ctx: &mut PluginCtx) { - if self.should_record { - self.record_state(&ctx.primary.sim, &ctx.primary.map); - } - - if let Some(time) = self.current_time { - 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.current_time = Some(time - TIMESTEP); - } else if time < self.last_time && ctx.input.modal_action("forwards") { - self.current_time = Some(time + TIMESTEP); - } else if ctx.input.modal_action("quit") { - self.current_time = None; - } - } else if ctx.input.action_chosen("start time traveling") { - if !self.should_record { - self.should_record = true; - self.record_state(&ctx.primary.sim, &ctx.primary.map); - } - self.current_time = Some(ctx.primary.sim.time()); - } + fn get_current_state(&self) -> &StateAtTime { + &self.state_per_time[&self.current_time.unwrap()] } } diff --git a/editor/src/state.rs b/editor/src/state.rs index 513c41138a..20937944c8 100644 --- a/editor/src/state.rs +++ b/editor/src/state.rs @@ -10,7 +10,7 @@ use ezgui::EventCtx; use ezgui::{Color, GfxCtx, Prerender}; use geom::Duration; use map_model::{IntersectionID, Map}; -use sim::{GetDrawAgents, Sim, SimFlags}; +use sim::{Sim, SimFlags}; use structopt::StructOpt; #[derive(StructOpt, Debug, Clone)] @@ -129,13 +129,6 @@ impl UIState { self.layers.show(obj) } - pub fn get_draw_agents(&self) -> &GetDrawAgents { - if self.primary_plugins.time_travel.is_active() { - return &self.primary_plugins.time_travel; - } - &self.primary.sim - } - pub fn event( &mut self, event_ctx: &mut EventCtx, diff --git a/editor/src/ui.rs b/editor/src/ui.rs index bb42ac9375..b8a749fd92 100644 --- a/editor/src/ui.rs +++ b/editor/src/ui.rs @@ -11,6 +11,7 @@ use ezgui::{ use geom::{Bounds, Circle, Distance, Polygon}; use map_model::{BuildingID, IntersectionID, LaneID, Traversable}; use serde_derive::{Deserialize, Serialize}; +use sim::GetDrawAgents; use std::collections::{HashMap, HashSet}; // TODO Collapse stuff! @@ -56,12 +57,7 @@ impl GUI for UI { (Some(Key::W), "manage scenarios"), ], ), - Folder::new( - "Simulation", - vec![ - (Some(Key::D), "diff all A/B trips"), - ], - ), + Folder::new("Simulation", vec![(Some(Key::D), "diff all A/B trips")]), Folder::new( "View", vec![ @@ -187,7 +183,7 @@ impl GUI for UI { } fn draw(&self, g: &mut GfxCtx) { - self.new_draw(g, None, HashMap::new()) + self.new_draw(g, None, HashMap::new(), &self.state.primary.sim) } fn dump_before_abort(&self, canvas: &Canvas) { @@ -268,13 +264,15 @@ impl UI { ctx.canvas.handle_event(ctx.input); // Always handle mouseover - self.handle_mouseover(ctx, None); + self.state.primary.current_selection = + self.handle_mouseover(ctx, None, &self.state.primary.sim); let mut recalculate_current_selection = false; self.state .event(ctx, &mut self.hints, &mut recalculate_current_selection); if recalculate_current_selection { - self.state.primary.current_selection = self.mouseover_something(&ctx, None); + self.state.primary.current_selection = + self.mouseover_something(&ctx, None, &self.state.primary.sim); } ctx.input.populate_osd(&mut self.hints.osd); @@ -310,6 +308,7 @@ impl UI { g: &mut GfxCtx, show_turn_icons_for: Option, override_color: HashMap, + source: &GetDrawAgents, ) { let ctx = DrawCtx { cs: &self.state.cs, @@ -359,6 +358,7 @@ impl UI { &g.prerender, &mut cache, show_turn_icons_for, + source, ); let mut drawn_all_buildings = false; @@ -424,24 +424,29 @@ impl UI { } } + // Because we have to sometimes borrow part of self for GetDrawAgents, this just returns the + // Option that the caller should assign. When this monolithic UI nonsense is dismantled, + // this weirdness goes away. pub fn handle_mouseover( - &mut self, + &self, ctx: &mut EventCtx, show_turn_icons_for: Option, - ) { + source: &GetDrawAgents, + ) -> Option { if !ctx.canvas.is_dragging() && ctx.input.get_moved_mouse().is_some() { - self.state.primary.current_selection = - self.mouseover_something(&ctx, show_turn_icons_for); + return self.mouseover_something(&ctx, show_turn_icons_for, source); } if ctx.input.window_lost_cursor() { - self.state.primary.current_selection = None; + return None; } + self.state.primary.current_selection } fn mouseover_something( &self, ctx: &EventCtx, show_turn_icons_for: Option, + source: &GetDrawAgents, ) -> Option { // Unzoomed mode. Ignore when debugging areas. if ctx.canvas.cam_zoom < MIN_ZOOM_FOR_DETAIL @@ -458,6 +463,7 @@ impl UI { ctx.prerender, &mut cache, show_turn_icons_for, + source, ); objects.reverse(); @@ -500,6 +506,7 @@ impl UI { prerender: &Prerender, agents: &'a mut AgentCache, show_turn_icons_for: Option, + source: &GetDrawAgents, ) -> Vec> { let map = &self.state.primary.map; let draw_map = &self.state.primary.draw_map; @@ -573,7 +580,6 @@ impl UI { // Expand all of the Traversables into agents, populating the cache if needed. { - let source = self.state.get_draw_agents(); let time = source.time(); for on in &agents_on {