diff --git a/docs/design.md b/docs/design.md index 087e31f1ca..cfde4bbaf3 100644 --- a/docs/design.md +++ b/docs/design.md @@ -900,7 +900,7 @@ have to add? - a quadtree and way to get onscreen stuff - UI - a toggleablelayer for it - - and clearing selection state maybe + = and clearing selection state maybe - are we mouseover it? (right order) - draw it (right order) - pick the color for it @@ -976,5 +976,11 @@ so it feels like we implicitly have a big enum of active plugin, with each of th = refactor the toggleablelayer stuff, then move them to list of plugins too = clean up selection state... should warp and hider be able to modify it, or just rerun mouseover_something? - - initiate plugins in the plugin's event; stop doing stuff directly in UI + = initiate plugins in the plugin's event; stop doing stuff directly in UI - basically, make UI.event() just the active plugin list thing as much as possible. + - deal with overlapping keys that still kinda happen (sim ctrl, escape game) + - bug: do need to recalculate current_selection whenever anything potentially changes camera, like follow + + - then rethink colors, with simplified single plugin + - then finally try out a unified quadtree! + - and see how much boilerplate a new type would need. diff --git a/editor/src/plugins/debug_objects.rs b/editor/src/plugins/debug_objects.rs index 8dd1a7e5f7..6832cc2fcc 100644 --- a/editor/src/plugins/debug_objects.rs +++ b/editor/src/plugins/debug_objects.rs @@ -91,7 +91,9 @@ fn debug(id: &ID, map: &Map, control_map: &ControlMap, sim: &mut Sim) { ID::Building(id) => { map.get_b(*id).dump_debug(); } - ID::Car(_) => {} + ID::Car(id) => { + sim.debug_car(*id); + } ID::Pedestrian(id) => { sim.debug_ped(*id); } diff --git a/editor/src/plugins/floodfill.rs b/editor/src/plugins/floodfill.rs index 0e58eadfa4..31978e7027 100644 --- a/editor/src/plugins/floodfill.rs +++ b/editor/src/plugins/floodfill.rs @@ -4,10 +4,12 @@ use colors::{ColorScheme, Colors}; use ezgui::UserInput; use graphics::types::Color; use map_model::{Lane, LaneID, Map}; +use objects::ID; use piston::input::Key; use std::collections::{HashSet, VecDeque}; // Keeps track of state so this can be interactively visualized +#[derive(PartialEq)] pub enum Floodfiller { Inactive, Active { @@ -24,7 +26,7 @@ impl Floodfiller { // TODO doesn't guarantee all visited lanes are connected? are dead-ends possible with the // current turn definitions? - pub fn start(start: LaneID) -> Floodfiller { + fn start(start: LaneID) -> Floodfiller { let mut queue = VecDeque::new(); queue.push_back(start); Floodfiller::Active { @@ -35,7 +37,19 @@ impl Floodfiller { // TODO step backwards! - pub fn event(&mut self, map: &Map, input: &mut UserInput) -> bool { + pub fn event(&mut self, map: &Map, input: &mut UserInput, selected: Option) -> bool { + if *self == Floodfiller::Inactive { + match selected { + Some(ID::Lane(id)) => { + if input.key_pressed(Key::F, "start floodfilling from this lane") { + *self = Floodfiller::start(id); + return true; + } + } + _ => {} + } + } + let mut new_state: Option = None; match self { Floodfiller::Inactive => {} diff --git a/editor/src/plugins/follow.rs b/editor/src/plugins/follow.rs index 7cfcffbeff..3b9aa1154d 100644 --- a/editor/src/plugins/follow.rs +++ b/editor/src/plugins/follow.rs @@ -1,8 +1,10 @@ use ezgui::{Canvas, UserInput}; use map_model::Map; +use objects::ID; use piston::input::Key; use sim::{CarID, PedestrianID, Sim}; +#[derive(PartialEq)] pub enum FollowState { Empty, FollowingCar(CarID), @@ -16,7 +18,26 @@ impl FollowState { map: &Map, sim: &Sim, canvas: &mut Canvas, + selected: Option, ) -> bool { + if *self == FollowState::Empty { + match selected { + Some(ID::Car(id)) => { + if input.key_pressed(Key::F, "follow this car") { + *self = FollowState::FollowingCar(id); + return true; + } + } + Some(ID::Pedestrian(id)) => { + if input.key_pressed(Key::F, "follow this pedestrian") { + *self = FollowState::FollowingPedestrian(id); + return true; + } + } + _ => {} + } + } + let quit = match self { FollowState::Empty => false, // TODO be generic and take an AgentID diff --git a/editor/src/plugins/geom_validation.rs b/editor/src/plugins/geom_validation.rs index b3de06265a..fcea8d42f1 100644 --- a/editor/src/plugins/geom_validation.rs +++ b/editor/src/plugins/geom_validation.rs @@ -5,7 +5,7 @@ use geo::prelude::Intersects; use geom::{Polygon, Pt2D}; use map_model::{geometry, BuildingID, IntersectionID, LaneID, Map, ParcelID}; use piston::input::Key; -use render; +use render::DrawMap; // TODO just have one of these #[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] @@ -31,7 +31,7 @@ impl Validator { Validator::Inactive } - pub fn start(draw_map: &render::DrawMap) -> Validator { + fn start(draw_map: &DrawMap) -> Validator { let mut objects: Vec<(ID, Vec>)> = Vec::new(); for l in &draw_map.lanes { objects.push((ID::Lane(l.id), make_polys(&l.polygon))); @@ -88,10 +88,20 @@ impl Validator { } } - pub fn event(&mut self, input: &mut UserInput, canvas: &mut Canvas, map: &Map) -> bool { + pub fn event( + &mut self, + input: &mut UserInput, + canvas: &mut Canvas, + map: &Map, + draw_map: &DrawMap, + ) -> bool { let mut new_state: Option = None; match self { - Validator::Inactive => {} + Validator::Inactive => { + if input.unimportant_key_pressed(Key::I, "Validate map geometry") { + new_state = Some(Validator::start(draw_map)); + } + } Validator::Active { gen, current_problem, diff --git a/editor/src/plugins/show_route.rs b/editor/src/plugins/show_route.rs index cb5c99babf..58d41d25cc 100644 --- a/editor/src/plugins/show_route.rs +++ b/editor/src/plugins/show_route.rs @@ -2,17 +2,37 @@ use colors::{ColorScheme, Colors}; use ezgui::UserInput; use graphics::types::Color; use map_model::LaneID; +use objects::ID; use piston::input::Key; use sim::{AgentID, Sim}; use std::collections::HashSet; +#[derive(PartialEq)] pub enum ShowRouteState { Empty, Active(AgentID, HashSet), } impl ShowRouteState { - pub fn event(&mut self, input: &mut UserInput, sim: &Sim) -> bool { + pub fn event(&mut self, input: &mut UserInput, sim: &Sim, selected: Option) -> bool { + if *self == ShowRouteState::Empty { + match selected { + Some(ID::Car(id)) => { + if input.key_pressed(Key::R, "show this car's route") { + *self = ShowRouteState::Active(AgentID::Car(id), HashSet::new()); + return true; + } + } + Some(ID::Pedestrian(id)) => { + if input.key_pressed(Key::R, "show this pedestrian's route") { + *self = ShowRouteState::Active(AgentID::Pedestrian(id), HashSet::new()); + return true; + } + } + _ => {} + } + } + let quit = match self { ShowRouteState::Empty => false, ShowRouteState::Active(agent, ref mut lanes) => { diff --git a/editor/src/plugins/sim_controls.rs b/editor/src/plugins/sim_controls.rs index 1014763953..1d403f2b43 100644 --- a/editor/src/plugins/sim_controls.rs +++ b/editor/src/plugins/sim_controls.rs @@ -3,6 +3,7 @@ use control::ControlMap; use ezgui::{EventLoopMode, UserInput}; use map_model::Map; +use objects::ID; use piston::input::{Key, UpdateEvent}; use sim::{Benchmark, Sim, TIMESTEP}; use std::time::{Duration, Instant}; @@ -33,7 +34,11 @@ impl SimController { map: &Map, control_map: &ControlMap, sim: &mut Sim, + selected: Option, ) -> EventLoopMode { + if input.unimportant_key_pressed(Key::S, "Seed the map with agents") { + sim.small_spawn(map); + } if input.unimportant_key_pressed(Key::LeftBracket, "slow down sim") { self.desired_speed -= ADJUST_SPEED; self.desired_speed = self.desired_speed.max(0.0); @@ -68,6 +73,22 @@ impl SimController { } } + match selected { + Some(ID::Car(id)) => { + if input.key_pressed(Key::A, "start this parked car") { + sim.start_parked_car(map, id); + } + } + Some(ID::Lane(id)) => { + if map.get_l(id).is_sidewalk() + && input.key_pressed(Key::A, "spawn a pedestrian here") + { + sim.spawn_pedestrian(map, id); + } + } + _ => {} + } + if input.use_event_directly().update_args().is_some() { if let Some(tick) = self.last_step { // TODO https://gafferongames.com/post/fix_your_timestep/ diff --git a/editor/src/plugins/stop_sign_editor.rs b/editor/src/plugins/stop_sign_editor.rs index d70432269f..d028cf11f3 100644 --- a/editor/src/plugins/stop_sign_editor.rs +++ b/editor/src/plugins/stop_sign_editor.rs @@ -10,6 +10,7 @@ use map_model::{Map, Turn}; use objects::ID; use piston::input::Key; +#[derive(PartialEq)] pub enum StopSignEditor { Inactive, Active(IntersectionID), @@ -20,10 +21,6 @@ impl StopSignEditor { StopSignEditor::Inactive } - pub fn start(i: IntersectionID) -> StopSignEditor { - StopSignEditor::Active(i) - } - pub fn event( &mut self, input: &mut UserInput, @@ -31,6 +28,20 @@ impl StopSignEditor { control_map: &mut ControlMap, selected: Option, ) -> bool { + if *self == StopSignEditor::Inactive { + match selected { + Some(ID::Intersection(id)) => { + if control_map.stop_signs.contains_key(&id) + && input.key_pressed(Key::E, &format!("edit stop signs for {}", id)) + { + *self = StopSignEditor::Active(id); + return true; + } + } + _ => {} + } + } + let mut new_state: Option = None; match self { StopSignEditor::Inactive => {} diff --git a/editor/src/plugins/traffic_signal_editor.rs b/editor/src/plugins/traffic_signal_editor.rs index 2253a45095..40008d19bb 100644 --- a/editor/src/plugins/traffic_signal_editor.rs +++ b/editor/src/plugins/traffic_signal_editor.rs @@ -11,6 +11,7 @@ use map_model::{IntersectionID, Turn}; use objects::ID; use piston::input::Key; +#[derive(PartialEq)] pub enum TrafficSignalEditor { Inactive, Active { @@ -24,13 +25,6 @@ impl TrafficSignalEditor { TrafficSignalEditor::Inactive } - pub fn start(i: IntersectionID) -> TrafficSignalEditor { - TrafficSignalEditor::Active { - i, - current_cycle: 0, - } - } - pub fn event( &mut self, input: &mut UserInput, @@ -38,6 +32,23 @@ impl TrafficSignalEditor { control_map: &mut ControlMap, selected: Option, ) -> bool { + if *self == TrafficSignalEditor::Inactive { + match selected { + Some(ID::Intersection(id)) => { + if control_map.traffic_signals.contains_key(&id) + && input.key_pressed(Key::E, &format!("edit traffic signal for {}", id)) + { + *self = TrafficSignalEditor::Active { + i: id, + current_cycle: 0, + }; + return true; + } + } + _ => {} + } + } + let mut new_state: Option = None; match self { TrafficSignalEditor::Inactive => {} diff --git a/editor/src/ui.rs b/editor/src/ui.rs index 3e361588d4..55299b63e6 100644 --- a/editor/src/ui.rs +++ b/editor/src/ui.rs @@ -36,8 +36,8 @@ use plugins::warp::WarpState; use render; use render::Renderable; use sim; -use sim::{AgentID, CarID, CarState, PedestrianID, Sim}; -use std::collections::{HashMap, HashSet}; +use sim::{CarID, CarState, PedestrianID, Sim}; +use std::collections::HashMap; use std::process; // TODO ideally these would be tuned kind of dynamically based on rendering speed @@ -128,7 +128,7 @@ impl UIWrapper { hider: Hider::new(), debug_objects: DebugObjectsState::new(), - current_search_state: SearchState::Empty, + search_state: SearchState::Empty, warp: WarpState::Empty, follow: FollowState::Empty, show_route: ShowRouteState::Empty, @@ -196,7 +196,7 @@ impl UIWrapper { &mut ui.sim, ) }), - Box::new(|ui, input| ui.current_search_state.event(input)), + Box::new(|ui, input| ui.search_state.event(input)), Box::new(|ui, input| { ui.warp.event( input, @@ -206,8 +206,16 @@ impl UIWrapper { &mut ui.current_selection, ) }), - Box::new(|ui, input| ui.follow.event(input, &ui.map, &ui.sim, &mut ui.canvas)), - Box::new(|ui, input| ui.show_route.event(input, &ui.sim)), + Box::new(|ui, input| { + ui.follow.event( + input, + &ui.map, + &ui.sim, + &mut ui.canvas, + ui.current_selection, + ) + }), + Box::new(|ui, input| ui.show_route.event(input, &ui.sim, ui.current_selection)), Box::new(|ui, input| ui.color_picker.event(input, &mut ui.canvas, &mut ui.cs)), Box::new(|ui, input| ui.steepness_viz.event(input)), Box::new(|ui, input| ui.osm_classifier.event(input)), @@ -221,8 +229,11 @@ impl UIWrapper { &ui.control_map, ) }), - Box::new(|ui, input| ui.floodfiller.event(&ui.map, input)), - Box::new(|ui, input| ui.geom_validator.event(input, &mut ui.canvas, &ui.map)), + Box::new(|ui, input| ui.floodfiller.event(&ui.map, input, ui.current_selection)), + Box::new(|ui, input| { + ui.geom_validator + .event(input, &mut ui.canvas, &ui.map, &ui.draw_map) + }), Box::new(|ui, input| ui.turn_cycler.event(input, ui.current_selection)), ], } @@ -247,7 +258,7 @@ struct UI { hider: Hider, debug_objects: DebugObjectsState, - current_search_state: SearchState, + search_state: SearchState, warp: WarpState, follow: FollowState, show_route: ShowRouteState, @@ -387,7 +398,7 @@ impl UI { vec![ self.color_for_selected(ID::Lane(l.id)), self.show_route.color_l(l.id, &self.cs), - self.current_search_state.color_l(l, &self.map, &self.cs), + self.search_state.color_l(l, &self.map, &self.cs), self.floodfiller.color_l(l, &self.cs), self.steepness_viz.color_l(&self.map, l), self.osm_classifier.color_l(l, &self.map, &self.cs), @@ -438,7 +449,7 @@ impl UI { let b = self.map.get_b(id); vec![ self.color_for_selected(ID::Building(b.id)), - self.current_search_state.color_b(b, &self.cs), + self.search_state.color_b(b, &self.cs), self.osm_classifier.color_b(b, &self.cs), ].iter() .filter_map(|c| *c) @@ -561,90 +572,9 @@ impl UI { }; if layer_changed { self.current_selection = self.mouseover_something(); - // TODO is it even necessary to update this here? shouldnt all plugins potentilly need - // to know? dont we need to also do this when warp/follow/other things potentially move - // stuff? - self.debug_objects.event( - self.current_selection, - input, - &self.map, - &mut self.sim, - &self.control_map, - ); return EventLoopMode::InputOnly; } - if input.unimportant_key_pressed(Key::I, "Validate map geometry") { - self.geom_validator = Validator::start(&self.draw_map); - return EventLoopMode::InputOnly; - } - if input.unimportant_key_pressed(Key::S, "Seed the map with agents") { - self.sim.small_spawn(&self.map); - return EventLoopMode::InputOnly; - } - - match self.current_selection { - Some(ID::Car(id)) => { - // TODO not sure if we should debug like this (pushing the bit down to all the - // layers representing an entity) or by using some scary global mutable singleton - if input.unimportant_key_pressed(Key::D, "debug") { - self.sim.toggle_debug(id); - return EventLoopMode::InputOnly; - } - if input.key_pressed(Key::A, "start this parked car") { - self.sim.start_parked_car(&self.map, id); - return EventLoopMode::InputOnly; - } - if input.key_pressed(Key::F, "follow this car") { - self.follow = FollowState::FollowingCar(id); - return EventLoopMode::InputOnly; - } - if input.key_pressed(Key::R, "show this car's route") { - self.show_route = ShowRouteState::Active(AgentID::Car(id), HashSet::new()); - return EventLoopMode::InputOnly; - } - } - Some(ID::Pedestrian(id)) => { - if input.key_pressed(Key::F, "follow this pedestrian") { - self.follow = FollowState::FollowingPedestrian(id); - return EventLoopMode::InputOnly; - } - if input.key_pressed(Key::R, "show this pedestrian's route") { - self.show_route = - ShowRouteState::Active(AgentID::Pedestrian(id), HashSet::new()); - return EventLoopMode::InputOnly; - } - } - Some(ID::Lane(id)) => { - if input.key_pressed(Key::F, "start floodfilling from this lane") { - self.floodfiller = Floodfiller::start(id); - return EventLoopMode::InputOnly; - } - - if self.map.get_l(id).is_sidewalk() - && input.key_pressed(Key::A, "spawn a pedestrian here") - { - self.sim.spawn_pedestrian(&self.map, id); - return EventLoopMode::InputOnly; - } - } - Some(ID::Intersection(id)) => { - if self.control_map.traffic_signals.contains_key(&id) { - if input.key_pressed(Key::E, &format!("edit traffic signal for {:?}", id)) { - self.traffic_signal_editor = TrafficSignalEditor::start(id); - return EventLoopMode::InputOnly; - } - } - if self.control_map.stop_signs.contains_key(&id) { - if input.key_pressed(Key::E, &format!("edit stop sign for {:?}", id)) { - self.stop_sign_editor = StopSignEditor::start(id); - return EventLoopMode::InputOnly; - } - } - } - _ => {} - } - if input.unimportant_key_pressed(Key::Escape, "quit") { let state = EditorState { map_name: self.map.get_name().clone(), @@ -664,8 +594,13 @@ impl UI { } // Sim controller plugin is kind of always active? If nothing else ran, let it use keys. - self.sim_ctrl - .event(input, &self.map, &self.control_map, &mut self.sim) + self.sim_ctrl.event( + input, + &self.map, + &self.control_map, + &mut self.sim, + self.current_selection, + ) } fn draw(&self, g: &mut GfxCtx, input: UserInput) { @@ -780,7 +715,7 @@ impl UI { osd_lines.push(String::from("")); osd_lines.extend(action_lines); } - let search_lines = self.current_search_state.get_osd_lines(); + let search_lines = self.search_state.get_osd_lines(); if !search_lines.is_empty() { osd_lines.push(String::from("")); osd_lines.extend(search_lines); diff --git a/sim/src/sim.rs b/sim/src/sim.rs index 5e6b416a1b..0f5a7e12ad 100644 --- a/sim/src/sim.rs +++ b/sim/src/sim.rs @@ -296,7 +296,7 @@ impl Sim { .unwrap_or(vec![format!("{} is parked", car)]) } - pub fn toggle_debug(&mut self, id: CarID) { + pub fn debug_car(&mut self, id: CarID) { self.driving_state.toggle_debug(id); }