From b1ef4058926b9858cef66a906e7ee0f44d73065b Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Wed, 3 Oct 2018 07:47:59 -0700 Subject: [PATCH] filling out edits manager --- abstutil/src/io.rs | 12 +- abstutil/src/lib.rs | 4 +- editor/src/plugins/map_edits.rs | 128 +++++++++++++++++-- editor/src/ui.rs | 212 +++++++++++++++++++------------- import.sh | 6 + 5 files changed, 265 insertions(+), 97 deletions(-) diff --git a/abstutil/src/io.rs b/abstutil/src/io.rs index 3ad6e56c02..d001348139 100644 --- a/abstutil/src/io.rs +++ b/abstutil/src/io.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_cbor; use serde_json; use std; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use std::fs::File; use std::hash::Hash; use std::io::{Error, ErrorKind, Read, Write}; @@ -101,6 +101,16 @@ pub fn deserialize_multimap< Ok(map) } +// Just list all things from a directory, return sorted by name. +pub fn list_all_objects(dir: &str, map_name: &str) -> Vec { + let mut results: BTreeSet = BTreeSet::new(); + for entry in std::fs::read_dir(format!("../data/{}/{}/", dir, map_name)).unwrap() { + let name = entry.unwrap().file_name().into_string().unwrap(); + results.insert(name); + } + results.into_iter().collect() +} + // Load all serialized things from a directory, return sorted by name. pub fn load_all_objects(dir: &str, map_name: &str) -> Vec<(String, T)> { let mut tree: BTreeMap = BTreeMap::new(); diff --git a/abstutil/src/lib.rs b/abstutil/src/lib.rs index 6d39c65867..f763c0eec3 100644 --- a/abstutil/src/lib.rs +++ b/abstutil/src/lib.rs @@ -10,6 +10,6 @@ mod io; pub use abst_multimap::MultiMap; pub use clone::Cloneable; pub use io::{ - deserialize_btreemap, deserialize_multimap, load_all_objects, read_binary, read_json, - serialize_btreemap, serialize_multimap, to_json, write_binary, write_json, + deserialize_btreemap, deserialize_multimap, list_all_objects, load_all_objects, read_binary, + read_json, serialize_btreemap, serialize_multimap, to_json, write_binary, write_json, }; diff --git a/editor/src/plugins/map_edits.rs b/editor/src/plugins/map_edits.rs index fad36539c6..c4e76b437f 100644 --- a/editor/src/plugins/map_edits.rs +++ b/editor/src/plugins/map_edits.rs @@ -1,22 +1,134 @@ +use abstutil; use control::ControlMap; -use ezgui::{Canvas, GfxCtx, UserInput}; +use ezgui::{Canvas, GfxCtx, UserInput, Wizard, WrappedWizard}; use map_model::Map; +use objects::SIM_SETUP; +use piston::input::Key; +use plugins::road_editor::RoadEditor; use plugins::Colorizer; +use sim::{MapEdits, SimFlags}; -// TODO ahh, something needs to remember edits_name. +pub struct EditsManager { + current_flags: SimFlags, + state: State, +} -pub struct EditsManager {} +enum State { + Inactive, + ManageEdits(Wizard), +} impl EditsManager { - pub fn new() -> EditsManager { - EditsManager {} + pub fn new(current_flags: SimFlags) -> EditsManager { + EditsManager { + current_flags, + state: State::Inactive, + } } - pub fn event(&mut self, input: &mut UserInput, map: &Map, control_map: &ControlMap) -> bool { - false + pub fn event( + &mut self, + input: &mut UserInput, + map: &Map, + control_map: &ControlMap, + road_editor: &RoadEditor, + new_flags: &mut Option, + ) -> bool { + let mut new_state: Option = None; + match self.state { + State::Inactive => { + if input.unimportant_key_pressed(Key::Q, SIM_SETUP, "manage map edits") { + new_state = Some(State::ManageEdits(Wizard::new())); + } + } + State::ManageEdits(ref mut wizard) => { + if manage_edits( + &mut self.current_flags, + map, + control_map, + road_editor, + new_flags, + wizard.wrap(input), + ).is_some() + { + } else if wizard.aborted() { + new_state = Some(State::Inactive); + } + } + } + if let Some(s) = new_state { + self.state = s; + } + match self.state { + State::Inactive => false, + _ => true, + } } - pub fn draw(&self, g: &mut GfxCtx, canvas: &Canvas) {} + pub fn draw(&self, g: &mut GfxCtx, canvas: &Canvas) { + match self.state { + State::ManageEdits(ref wizard) => { + wizard.draw(g, canvas); + } + _ => {} + } + } } impl Colorizer for EditsManager {} + +fn manage_edits( + current_flags: &mut SimFlags, + map: &Map, + control_map: &ControlMap, + road_editor: &RoadEditor, + new_flags: &mut Option, + mut wizard: WrappedWizard, +) -> Option<()> { + // TODO Indicate how many edits are there / if there are any unsaved edits + let load = "Load other map edits"; + let save_new = "Save these new map edits"; + let save_existing = &format!("Save {}", current_flags.edits_name); + let choices: Vec<&str> = if current_flags.edits_name == "no_edits" { + vec![save_new, load] + } else { + vec![save_existing, load] + }; + match wizard.choose_string("Manage map edits", choices)?.as_str() { + x if x == save_new => { + let name = wizard.input_string("Name the map edits")?; + save(&name, map, control_map, road_editor); + // No need to reload everything + current_flags.edits_name = name; + Some(()) + } + x if x == save_existing => { + save(¤t_flags.edits_name, map, control_map, road_editor); + Some(()) + } + x if x == load => { + let map_name = map.get_name().to_string(); + let edits = abstutil::list_all_objects("edits", &map_name); + let edit_refs = edits.iter().map(|s| s.as_str()).collect(); + let load_name = wizard.choose_string("Load which map edits?", edit_refs)?; + let mut flags = current_flags.clone(); + flags.edits_name = load_name; + *new_flags = Some(flags); + Some(()) + } + _ => unreachable!(), + } +} + +fn save(edits_name: &str, map: &Map, control_map: &ControlMap, road_editor: &RoadEditor) { + abstutil::write_json( + &format!("../data/edits/{}/{}.json", map.get_name(), edits_name), + &MapEdits { + edits_name: edits_name.to_string(), + map_name: map.get_name().to_string(), + road_edits: road_editor.get_edits().clone(), + stop_signs: control_map.get_stop_signs_savestate(), + traffic_signals: control_map.get_traffic_signals_savestate(), + }, + ).expect("Saving map edits failed"); +} diff --git a/editor/src/ui.rs b/editor/src/ui.rs index af93debd77..97b0ca113e 100644 --- a/editor/src/ui.rs +++ b/editor/src/ui.rs @@ -37,7 +37,7 @@ use plugins::warp::WarpState; use plugins::Colorizer; use render::{DrawMap, RenderOptions}; use sim; -use sim::{MapEdits, Sim, SimFlags}; +use sim::{Sim, SimFlags}; use std::process; // TODO ideally these would be tuned kind of dynamically based on rendering speed @@ -48,16 +48,22 @@ const MIN_ZOOM_FOR_MOUSEOVER: f64 = 4.0; // Necessary so we can iterate over and run the plugins, which mutably borrow UI. pub struct UIWrapper { ui: UI, - plugins: Vec bool>>, + plugins: Vec bool>>, - // Remember these to support loading a new UIWrapper - flags: SimFlags, + // Remember this to support loading a new UIWrapper kml: Option, } impl GUI for UIWrapper { fn event(&mut self, input: UserInput, osd: &mut Text) -> EventLoopMode { - self.ui.event(input, osd, &self.plugins) + // If we should start over and load something new, fill this out. + let mut new_flags: Option = None; + let result = self.ui.event(input, osd, &self.plugins, &mut new_flags); + if let Some(flags) = new_flags { + info!("Reloading everything..."); + *self = UIWrapper::new(flags, self.kml.clone()); + } + result } fn draw(&mut self, g: &mut GfxCtx, osd: Text, window_size: Size) { @@ -122,7 +128,7 @@ impl UIWrapper { turn_cycler: TurnCyclerState::new(), draw_neighborhoods: DrawNeighborhoodState::new(), scenarios: ScenarioManager::new(), - edits_manager: EditsManager::new(), + edits_manager: EditsManager::new(flags), logs, active_plugin: None, @@ -152,11 +158,11 @@ impl UIWrapper { UIWrapper { ui, plugins: vec![ - Box::new(|ui, input, _osd| { + Box::new(|ctx| { let layer_changed = { let mut changed = false; - for layer in ui.toggleable_layers().into_iter() { - if layer.event(input) { + for layer in ctx.ui.toggleable_layers().into_iter() { + if layer.event(ctx.input) { changed = true; break; } @@ -164,92 +170,119 @@ impl UIWrapper { changed }; if layer_changed { - ui.current_selection = ui.mouseover_something(); + ctx.ui.current_selection = ctx.ui.mouseover_something(); true } else { false } }), - Box::new(|ui, input, _osd| { - ui.traffic_signal_editor.event( - input, - &ui.map, - &mut ui.control_map, - ui.current_selection, + Box::new(|ctx| { + ctx.ui.traffic_signal_editor.event( + ctx.input, + &ctx.ui.map, + &mut ctx.ui.control_map, + ctx.ui.current_selection, ) }), - Box::new(|ui, input, _osd| { - ui.stop_sign_editor.event( - input, - &ui.map, - &mut ui.control_map, - ui.current_selection, + Box::new(|ctx| { + ctx.ui.stop_sign_editor.event( + ctx.input, + &ctx.ui.map, + &mut ctx.ui.control_map, + ctx.ui.current_selection, ) }), - Box::new(|ui, input, _osd| { - ui.road_editor.event( - input, - ui.current_selection, - &mut ui.map, - &mut ui.draw_map, - &ui.control_map, - &mut ui.sim, + Box::new(|ctx| { + ctx.ui.road_editor.event( + ctx.input, + ctx.ui.current_selection, + &mut ctx.ui.map, + &mut ctx.ui.draw_map, + &ctx.ui.control_map, + &mut ctx.ui.sim, ) }), - Box::new(|ui, input, _osd| ui.search_state.event(input)), - Box::new(|ui, input, _osd| { - ui.warp.event( - input, - &ui.map, - &ui.sim, - &mut ui.canvas, - &mut ui.current_selection, + Box::new(|ctx| ctx.ui.search_state.event(ctx.input)), + Box::new(|ctx| { + ctx.ui.warp.event( + ctx.input, + &ctx.ui.map, + &ctx.ui.sim, + &mut ctx.ui.canvas, + &mut ctx.ui.current_selection, ) }), - Box::new(|ui, input, _osd| { - ui.follow.event( - input, - &ui.map, - &ui.sim, - &mut ui.canvas, - ui.current_selection, + Box::new(|ctx| { + ctx.ui.follow.event( + ctx.input, + &ctx.ui.map, + &ctx.ui.sim, + &mut ctx.ui.canvas, + ctx.ui.current_selection, ) }), - Box::new(|ui, input, _osd| { - ui.show_route.event(input, &ui.sim, ui.current_selection) + Box::new(|ctx| { + ctx.ui + .show_route + .event(ctx.input, &ctx.ui.sim, ctx.ui.current_selection) }), - Box::new(|ui, input, _osd| { - ui.color_picker.event(input, &mut ui.canvas, &mut ui.cs) + Box::new(|ctx| { + ctx.ui + .color_picker + .event(ctx.input, &mut ctx.ui.canvas, &mut ctx.ui.cs) }), - Box::new(|ui, input, _osd| ui.steepness_viz.event(input)), - Box::new(|ui, input, _osd| ui.osm_classifier.event(input)), - Box::new(|ui, input, _osd| ui.hider.event(input, &mut ui.current_selection)), - Box::new(|ui, input, _osd| { - ui.debug_objects.event( - ui.current_selection, - input, - &ui.map, - &mut ui.sim, - &ui.control_map, + Box::new(|ctx| ctx.ui.steepness_viz.event(ctx.input)), + Box::new(|ctx| ctx.ui.osm_classifier.event(ctx.input)), + Box::new(|ctx| ctx.ui.hider.event(ctx.input, &mut ctx.ui.current_selection)), + Box::new(|ctx| { + ctx.ui.debug_objects.event( + ctx.ui.current_selection, + ctx.input, + &ctx.ui.map, + &mut ctx.ui.sim, + &ctx.ui.control_map, ) }), - Box::new(|ui, input, _osd| { - ui.floodfiller.event(&ui.map, input, ui.current_selection) + Box::new(|ctx| { + ctx.ui + .floodfiller + .event(&ctx.ui.map, ctx.input, ctx.ui.current_selection) }), - Box::new(|ui, input, _osd| { - ui.geom_validator - .event(input, &mut ui.canvas, &ui.map, &ui.draw_map) + Box::new(|ctx| { + ctx.ui.geom_validator.event( + ctx.input, + &mut ctx.ui.canvas, + &ctx.ui.map, + &ctx.ui.draw_map, + ) }), - Box::new(|ui, input, _osd| ui.turn_cycler.event(input, ui.current_selection)), - Box::new(|ui, input, osd| { - ui.draw_neighborhoods.event(input, &ui.canvas, &ui.map, osd) + Box::new(|ctx| { + ctx.ui + .turn_cycler + .event(ctx.input, ctx.ui.current_selection) }), - Box::new(|ui, input, _osd| ui.scenarios.event(input, &ui.map, &mut ui.sim)), - Box::new(|ui, input, _osd| ui.edits_manager.event(input, &ui.map, &ui.control_map)), - Box::new(|ui, input, _osd| ui.logs.event(input)), + Box::new(|ctx| { + ctx.ui + .draw_neighborhoods + .event(ctx.input, &ctx.ui.canvas, &ctx.ui.map, ctx.osd) + }), + Box::new(|ctx| { + ctx.ui + .scenarios + .event(ctx.input, &ctx.ui.map, &mut ctx.ui.sim) + }), + Box::new(|ctx| { + ctx.ui.edits_manager.event( + ctx.input, + &ctx.ui.map, + &ctx.ui.control_map, + &ctx.ui.road_editor, + ctx.new_flags, + ) + }), + Box::new(|ctx| ctx.ui.logs.event(ctx.input)), ], - flags, kml, } } @@ -336,7 +369,8 @@ impl UI { &mut self, mut input: UserInput, osd: &mut Text, - plugins: &Vec bool>>, + plugins: &Vec bool>>, + new_flags: &mut Option, ) -> EventLoopMode { // First update the camera and handle zoom let old_zoom = self.canvas.cam_zoom; @@ -362,13 +396,23 @@ impl UI { // If there's an active plugin, just run it. if let Some(idx) = self.active_plugin { - if !plugins[idx](self, &mut input, osd) { + if !plugins[idx](PluginCtx { + ui: self, + input: &mut input, + osd, + new_flags, + }) { self.active_plugin = None; } } else { // Run each plugin, short-circuiting if the plugin claimed it was active. for (idx, plugin) in plugins.iter().enumerate() { - if plugin(self, &mut input, osd) { + if plugin(PluginCtx { + ui: self, + input: &mut input, + osd, + new_flags, + }) { self.active_plugin = Some(idx); break; } @@ -385,18 +429,7 @@ impl UI { // TODO maybe make state line up with the map, so loading from a new map doesn't break abstutil::write_json("editor_state", &state).expect("Saving editor_state failed"); abstutil::write_json("color_scheme", &self.cs).expect("Saving color_scheme failed"); - // TODO do this from a plugin! - abstutil::write_json( - &format!("../data/edits/{}/ui.json", self.map.get_name()), - &MapEdits { - edits_name: "ui".to_string(), - map_name: self.map.get_name().to_string(), - road_edits: self.road_editor.get_edits().clone(), - stop_signs: self.control_map.get_stop_signs_savestate(), - traffic_signals: self.control_map.get_traffic_signals_savestate(), - }, - ).expect("Saving map_edits.json failed"); - info!("Saved editor_state, color_scheme, and map_edits.json"); + info!("Saved editor_state and color_scheme"); process::exit(0); } @@ -612,3 +645,10 @@ impl ShowTurnIcons for UI { || self.traffic_signal_editor.show_turn_icons(id) } } + +struct PluginCtx<'a> { + ui: &'a mut UI, + input: &'a mut UserInput, + osd: &'a mut Text, + new_flags: &'a mut Option, +} diff --git a/import.sh b/import.sh index 19207ace59..ee6c2fc851 100755 --- a/import.sh +++ b/import.sh @@ -2,6 +2,12 @@ set -e +function get_if_needed { + if [ ! -f $2 ]; then + wget $1 -O $2; + fi +} + # Download raw data if needed. mkdir -p data/input data/maps # TODO Fill these out: