abstreet/editor/src/ui.rs

555 lines
19 KiB
Rust
Raw Normal View History

// Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0
// TODO this should just be a way to handle interactions between plugins
2018-06-22 21:01:44 +03:00
use abstutil;
2018-10-25 01:59:53 +03:00
use colors::ColorScheme;
//use cpuprofiler;
use ezgui::{Canvas, Color, EventLoopMode, GfxCtx, Text, UserInput, BOTTOM_LEFT, GUI};
use kml;
use map_model::{BuildingID, IntersectionID, Map};
use objects::{Ctx, RenderingHints, ID, ROOT_MENU};
2018-10-06 01:44:12 +03:00
use piston::input::Key;
2018-10-22 20:47:29 +03:00
use plugins;
2018-09-13 19:43:04 +03:00
use plugins::hider::Hider;
use plugins::layers::ToggleableLayers;
use plugins::stop_sign_editor::StopSignEditor;
use plugins::time_travel::TimeTravel;
2018-10-22 19:06:02 +03:00
use plugins::Plugin;
use render::{DrawMap, RenderOptions};
2018-08-27 23:03:35 +03:00
use sim;
use sim::{GetDrawAgents, Sim, SimFlags, Tick};
2018-10-25 01:59:53 +03:00
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::panic;
use std::process;
2018-09-14 23:23:39 +03:00
const MIN_ZOOM_FOR_MOUSEOVER: f64 = 4.0;
2018-10-22 05:23:47 +03:00
pub struct UI {
primary: PerMapUI,
2018-10-22 05:59:37 +03:00
primary_plugins: PluginsPerMap,
2018-10-22 05:23:47 +03:00
// When running an A/B test, this is populated too.
2018-10-22 05:59:37 +03:00
secondary: Option<(PerMapUI, PluginsPerMap)>,
2018-10-22 05:23:47 +03:00
plugins: PluginsPerUI,
// TODO describe An index into plugin_handlers.
2018-10-22 05:23:47 +03:00
active_plugin: Option<usize>,
canvas: Canvas,
2018-10-25 01:59:53 +03:00
// TODO mutable ColorScheme to slurp up defaults is NOT ideal.
cs: RefCell<ColorScheme>,
2018-10-22 05:23:47 +03:00
// Remember this to support loading a new PerMapUI
kml: Option<String>,
}
impl GUI<RenderingHints> for UI {
fn event(&mut self, input: UserInput) -> (EventLoopMode, RenderingHints) {
match panic::catch_unwind(panic::AssertUnwindSafe(|| self.inner_event(input))) {
Ok(hints) => (hints.mode, hints),
Err(err) => {
2018-11-12 22:12:14 +03:00
error!("********************************************************************************");
error!("UI broke! Primary sim:");
self.primary.sim.dump_before_abort();
if let Some((s, _)) = &self.secondary {
error!("Secondary sim:");
s.sim.dump_before_abort();
}
self.save_editor_state();
panic::resume_unwind(err);
2018-10-22 05:23:47 +03:00
}
}
}
2018-09-13 03:39:53 +03:00
fn get_mut_canvas(&mut self) -> &mut Canvas {
2018-10-22 05:23:47 +03:00
&mut self.canvas
}
fn draw(&self, g: &mut GfxCtx, hints: RenderingHints) {
2018-10-26 22:55:59 +03:00
g.clear(
self.cs
.borrow_mut()
.get("map background", Color::rgb(242, 239, 233)),
);
2018-10-22 05:23:47 +03:00
let (statics, dynamics) = self.primary.draw_map.get_objects_onscreen(
self.canvas.get_screen_bounds(),
2018-10-22 19:59:18 +03:00
self.primary_plugins.hider(),
2018-10-22 05:23:47 +03:00
&self.primary.map,
self.get_draw_agent_source(),
2018-10-22 19:59:18 +03:00
self.plugins.layers(),
2018-10-22 05:23:47 +03:00
self,
);
for obj in statics.into_iter() {
let opts = RenderOptions {
color: self.color_obj(obj.get_id(), &hints),
2018-10-22 05:23:47 +03:00
cam_zoom: self.canvas.cam_zoom,
2018-10-22 19:59:18 +03:00
debug_mode: self.plugins.layers().debug_mode.is_enabled(),
2018-10-22 05:23:47 +03:00
};
obj.draw(
g,
opts,
Ctx {
2018-10-25 01:59:53 +03:00
cs: &mut self.cs.borrow_mut(),
2018-10-22 05:23:47 +03:00
map: &self.primary.map,
2018-10-22 19:43:48 +03:00
draw_map: &self.primary.draw_map,
2018-10-22 05:23:47 +03:00
canvas: &self.canvas,
sim: &self.primary.sim,
hints: &hints,
2018-10-22 05:23:47 +03:00
},
);
}
for obj in dynamics.into_iter() {
let opts = RenderOptions {
color: self.color_obj(obj.get_id(), &hints),
2018-10-22 05:23:47 +03:00
cam_zoom: self.canvas.cam_zoom,
2018-10-22 19:59:18 +03:00
debug_mode: self.plugins.layers().debug_mode.is_enabled(),
2018-10-22 05:23:47 +03:00
};
obj.draw(
g,
opts,
Ctx {
2018-10-25 01:59:53 +03:00
cs: &mut self.cs.borrow_mut(),
2018-10-22 05:23:47 +03:00
map: &self.primary.map,
2018-10-22 19:43:48 +03:00
draw_map: &self.primary.draw_map,
2018-10-22 05:23:47 +03:00
canvas: &self.canvas,
sim: &self.primary.sim,
hints: &hints,
2018-10-22 05:23:47 +03:00
},
);
}
2018-10-22 19:43:48 +03:00
if let Some(p) = self.get_active_plugin() {
p.draw(
g,
Ctx {
2018-10-25 01:59:53 +03:00
cs: &mut self.cs.borrow_mut(),
2018-10-22 19:43:48 +03:00
map: &self.primary.map,
draw_map: &self.primary.draw_map,
canvas: &self.canvas,
sim: &self.primary.sim,
hints: &hints,
2018-10-22 19:43:48 +03:00
},
);
} else {
// TODO Ew, this is a weird ambient plugin that doesn't consume input but might want to
// draw stuff... only if another plugin isn't already active (aka, this is a hack to
// turn this off when traffic signal editor is on.)
self.primary_plugins.turn_cycler().draw(
g,
Ctx {
cs: &mut self.cs.borrow_mut(),
map: &self.primary.map,
draw_map: &self.primary.draw_map,
canvas: &self.canvas,
sim: &self.primary.sim,
hints: &hints,
},
);
2018-10-22 19:43:48 +03:00
}
self.canvas.draw_text(g, hints.osd, BOTTOM_LEFT);
2018-10-22 05:23:47 +03:00
}
}
// All of the state that's bound to a specific map+edit has to live here.
// TODO How can we arrange the code so that we statically know that we don't pass anything from UI
// to something in PerMapUI?
pub struct PerMapUI {
pub map: Map,
pub draw_map: DrawMap,
2018-10-22 05:23:47 +03:00
pub sim: Sim,
pub current_selection: Option<ID>,
pub recalculate_current_selection: bool,
pub current_flags: SimFlags,
2018-10-22 05:59:37 +03:00
}
2018-10-22 05:23:47 +03:00
2018-10-22 05:59:37 +03:00
pub struct PluginsPerMap {
2018-10-22 05:23:47 +03:00
// Anything that holds onto any kind of ID has to live here!
2018-10-22 19:59:18 +03:00
list: Vec<Box<Plugin>>,
}
impl PluginsPerMap {
fn hider(&self) -> &Hider {
self.list[0].downcast_ref::<Hider>().unwrap()
}
fn show_owner(&self) -> &Box<Plugin> {
&self.list[1]
}
fn stop_sign_editor(&self) -> &StopSignEditor {
self.list[2].downcast_ref::<StopSignEditor>().unwrap()
}
fn traffic_signal_editor(&self) -> &plugins::traffic_signal_editor::TrafficSignalEditor {
self.list[3]
.downcast_ref::<plugins::traffic_signal_editor::TrafficSignalEditor>()
.unwrap()
}
fn turn_cycler(&self) -> &Box<Plugin> {
&self.list[4]
}
fn time_travel(&self) -> &TimeTravel {
self.list[5].downcast_ref::<TimeTravel>().unwrap()
}
2018-10-22 05:23:47 +03:00
}
impl PerMapUI {
pub fn new(flags: SimFlags, kml: &Option<String>) -> (PerMapUI, PluginsPerMap) {
let mut timer = abstutil::Timer::new("setup PerMapUI");
2018-11-30 23:36:27 +03:00
let (map, sim) = sim::load(flags.clone(), Some(Tick::from_seconds(30)), &mut timer);
let extra_shapes: Vec<kml::ExtraShape> = if let Some(path) = kml {
if path.ends_with(".kml") {
kml::load(&path, &map.get_gps_bounds(), &mut timer)
.expect("Couldn't load extra KML shapes")
.shapes
} else {
let shapes: kml::ExtraShapes =
abstutil::read_binary(&path, &mut timer).expect("Couldn't load ExtraShapes");
shapes.shapes
}
2018-10-22 05:23:47 +03:00
} else {
Vec::new()
};
timer.start("draw_map");
2018-11-30 23:36:27 +03:00
let draw_map = DrawMap::new(&map, extra_shapes, &mut timer);
timer.stop("draw_map");
2018-10-22 05:23:47 +03:00
2018-10-22 20:47:29 +03:00
let steepness_viz = plugins::steep::SteepnessVisualizer::new(&map);
let neighborhood_summary =
plugins::neighborhood_summary::NeighborhoodSummary::new(&map, &draw_map, &mut timer);
timer.done();
2018-10-22 05:23:47 +03:00
2018-10-22 05:59:37 +03:00
let state = PerMapUI {
2018-10-22 05:23:47 +03:00
map,
draw_map,
sim,
current_selection: None,
recalculate_current_selection: false,
current_flags: flags,
2018-10-22 05:59:37 +03:00
};
let plugins = PluginsPerMap {
2018-10-22 19:59:18 +03:00
list: vec![
Box::new(Hider::new()),
2018-10-22 20:47:29 +03:00
Box::new(plugins::show_owner::ShowOwnerState::new()),
2018-10-22 19:59:18 +03:00
Box::new(StopSignEditor::new()),
Box::new(plugins::traffic_signal_editor::TrafficSignalEditor::new()),
Box::new(plugins::turn_cycler::TurnCyclerState::new()),
Box::new(plugins::time_travel::TimeTravel::new()),
2018-10-22 20:47:29 +03:00
Box::new(plugins::debug_objects::DebugObjectsState::new()),
Box::new(plugins::follow::FollowState::new()),
Box::new(plugins::show_route::ShowRouteState::new()),
Box::new(plugins::show_activity::ShowActivityState::new()),
2018-10-22 20:47:29 +03:00
Box::new(plugins::floodfill::Floodfiller::new()),
2018-10-22 19:59:18 +03:00
Box::new(steepness_viz),
2018-10-22 20:47:29 +03:00
Box::new(plugins::geom_validation::Validator::new()),
Box::new(plugins::draw_neighborhoods::DrawNeighborhoodState::new()),
Box::new(plugins::scenarios::ScenarioManager::new()),
Box::new(plugins::chokepoints::ChokepointsFinder::new()),
Box::new(neighborhood_summary),
2018-10-22 19:59:18 +03:00
],
2018-10-22 05:59:37 +03:00
};
(state, plugins)
}
}
2018-10-22 05:23:47 +03:00
// aka plugins that don't depend on map
struct PluginsPerUI {
2018-10-22 19:59:18 +03:00
list: Vec<Box<Plugin>>,
}
impl PluginsPerUI {
fn layers(&self) -> &ToggleableLayers {
self.list[0].downcast_ref::<ToggleableLayers>().unwrap()
}
fn layers_mut(&mut self) -> &mut ToggleableLayers {
self.list[0].downcast_mut::<ToggleableLayers>().unwrap()
}
2018-10-22 05:23:47 +03:00
}
impl UI {
pub fn new(flags: SimFlags, kml: Option<String>) -> UI {
2018-09-28 00:55:59 +03:00
// Do this first, so anything logged by sim::load isn't lost.
2018-10-22 20:47:29 +03:00
let logs = plugins::logs::DisplayLogs::new();
2018-09-28 00:55:59 +03:00
let (primary, primary_plugins) = PerMapUI::new(flags, &kml);
let mut ui = UI {
2018-10-22 05:59:37 +03:00
primary,
primary_plugins,
secondary: None,
plugins: PluginsPerUI {
2018-10-22 19:59:18 +03:00
list: vec![
Box::new(ToggleableLayers::new()),
Box::new(plugins::search::SearchState::new()),
Box::new(plugins::warp::WarpState::new()),
2018-10-22 20:47:29 +03:00
Box::new(plugins::classification::OsmClassifier::new()),
Box::new(plugins::color_picker::ColorPicker::new()),
Box::new(plugins::a_b_tests::ABTestManager::new()),
2018-10-22 19:59:18 +03:00
Box::new(logs),
2018-10-22 21:06:03 +03:00
Box::new(plugins::diff_all::DiffAllState::new()),
2018-10-22 20:47:29 +03:00
Box::new(plugins::diff_worlds::DiffWorldsState::new()),
Box::new(plugins::road_editor::RoadEditor::new()),
Box::new(plugins::map_edits::EditsManager::new()),
2018-11-23 02:15:56 +03:00
Box::new(plugins::sim_controls::SimController::new()),
2018-10-22 19:59:18 +03:00
],
},
2018-09-13 19:21:41 +03:00
active_plugin: None,
2018-09-10 03:10:34 +03:00
canvas: Canvas::new(),
cs: RefCell::new(ColorScheme::load().unwrap()),
kml,
};
2018-10-22 05:23:47 +03:00
match abstutil::read_json::<EditorState>("editor_state") {
Ok(ref state) if ui.primary.map.get_name().to_string() == state.map_name => {
info!("Loaded previous editor_state");
ui.canvas.cam_x = state.cam_x;
ui.canvas.cam_y = state.cam_y;
ui.canvas.cam_zoom = state.cam_zoom;
}
_ => {
warn!("Couldn't load editor_state or it's for a different map, so just focusing on an arbitrary building");
// TODO window_size isn't set yet, so this actually kinda breaks
ui.canvas.center_on_map_pt(
ID::Building(BuildingID(0))
.canonical_point(&ui.primary.map, &ui.primary.sim, &ui.primary.draw_map)
.expect("Can't get canonical_point of BuildingID(0)"),
);
2018-10-22 05:23:47 +03:00
}
}
2018-10-22 19:59:18 +03:00
ui.plugins
.layers_mut()
.handle_zoom(-1.0, ui.canvas.cam_zoom);
2018-10-22 05:23:47 +03:00
ui
}
fn inner_event(&mut self, mut input: UserInput) -> RenderingHints {
let mut hints = RenderingHints {
mode: EventLoopMode::InputOnly,
osd: Text::new(),
suppress_intersection_icon: None,
color_crosswalks: HashMap::new(),
2018-12-04 21:31:47 +03:00
hide_crosswalks: HashSet::new(),
hide_turn_icons: HashSet::new(),
};
// First update the camera and handle zoom
let old_zoom = self.canvas.cam_zoom;
self.canvas.handle_event(&mut input);
let new_zoom = self.canvas.cam_zoom;
self.plugins.layers_mut().handle_zoom(old_zoom, new_zoom);
// Always handle mouseover
if old_zoom >= MIN_ZOOM_FOR_MOUSEOVER && new_zoom < MIN_ZOOM_FOR_MOUSEOVER {
self.primary.current_selection = None;
}
if !self.canvas.is_dragging()
&& input.get_moved_mouse().is_some()
&& new_zoom >= MIN_ZOOM_FOR_MOUSEOVER
{
self.primary.current_selection = self.mouseover_something();
}
// TODO Normally we'd return InputOnly here if there was an active plugin, but actually, we
// want some keys to always be pressable (sim controller stuff, quitting the game?)
// If there's an active plugin, just run it.
if let Some(idx) = self.active_plugin {
if !self.run_plugin(idx, &mut input, &mut hints) {
self.active_plugin = None;
}
} else {
// Run each plugin, short-circuiting if the plugin claimed it was active.
for idx in 0..self.plugins.list.len() + self.primary_plugins.list.len() {
if self.run_plugin(idx, &mut input, &mut hints) {
self.active_plugin = Some(idx);
break;
}
}
}
if input.unimportant_key_pressed(Key::Escape, ROOT_MENU, "quit") {
self.save_editor_state();
self.cs.borrow().save();
info!("Saved color_scheme");
//cpuprofiler::PROFILER.lock().unwrap().stop().unwrap();
process::exit(0);
}
if self.primary.recalculate_current_selection {
self.primary.recalculate_current_selection = false;
self.primary.current_selection = self.mouseover_something();
}
input.populate_osd(&mut hints.osd);
hints
}
2018-07-06 20:02:01 +03:00
fn mouseover_something(&self) -> Option<ID> {
let pt = self.canvas.get_cursor_in_map_space();
2018-06-23 19:01:53 +03:00
let (statics, dynamics) = self.primary.draw_map.get_objects_onscreen(
self.canvas.get_screen_bounds(),
2018-10-22 19:59:18 +03:00
self.primary_plugins.hider(),
&self.primary.map,
self.get_draw_agent_source(),
2018-10-22 19:59:18 +03:00
self.plugins.layers(),
self,
);
// Check front-to-back
for obj in dynamics.into_iter() {
if obj.contains_pt(pt) {
return Some(obj.get_id());
2018-06-23 19:01:53 +03:00
}
}
for obj in statics.into_iter().rev() {
if obj.contains_pt(pt) {
return Some(obj.get_id());
2018-09-14 00:09:55 +03:00
}
}
2018-06-23 19:01:53 +03:00
None
}
fn color_obj(&self, id: ID, hints: &RenderingHints) -> Option<Color> {
if Some(id) == self.primary.current_selection {
2018-10-25 01:59:53 +03:00
return Some(self.cs.borrow_mut().get("selected", Color::BLUE));
}
let ctx = Ctx {
2018-10-25 01:59:53 +03:00
cs: &mut self.cs.borrow_mut(),
map: &self.primary.map,
2018-10-22 19:43:48 +03:00
draw_map: &self.primary.draw_map,
canvas: &self.canvas,
sim: &self.primary.sim,
hints,
};
if let Some(p) = self.get_active_plugin() {
return p.color_for(id, ctx);
}
// TODO Ew, this is a weird ambient plugin that doesn't consume input but has an opinion on
// color.
2018-10-22 19:59:18 +03:00
self.primary_plugins.show_owner().color_for(id, ctx)
}
2018-10-22 19:59:18 +03:00
fn get_active_plugin(&self) -> Option<&Box<Plugin>> {
let idx = self.active_plugin?;
2018-10-22 19:59:18 +03:00
let len = self.plugins.list.len();
if idx < len {
Some(&self.plugins.list[idx])
} else {
Some(&self.primary_plugins.list[idx - len])
}
}
fn run_plugin(
&mut self,
idx: usize,
input: &mut UserInput,
hints: &mut RenderingHints,
) -> bool {
let active = {
2018-11-23 02:15:56 +03:00
let mut ctx = PluginCtx {
primary: &mut self.primary,
2018-11-23 02:15:56 +03:00
primary_plugins: None,
secondary: &mut self.secondary,
canvas: &mut self.canvas,
2018-10-25 01:59:53 +03:00
cs: &mut self.cs.borrow_mut(),
input,
hints,
kml: &self.kml,
};
2018-10-22 19:59:18 +03:00
let len = self.plugins.list.len();
if idx < len {
2018-11-23 02:15:56 +03:00
ctx.primary_plugins = Some(&mut self.primary_plugins);
2018-10-22 19:59:18 +03:00
self.plugins.list[idx].event(ctx)
} else {
self.primary_plugins.list[idx - len].event(ctx)
}
};
active
}
fn save_editor_state(&self) {
let state = EditorState {
map_name: self.primary.map.get_name().clone(),
cam_x: self.canvas.cam_x,
cam_y: self.canvas.cam_y,
cam_zoom: self.canvas.cam_zoom,
};
// 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");
info!("Saved editor_state");
}
fn get_draw_agent_source(&self) -> Box<&GetDrawAgents> {
let tt = self.primary_plugins.time_travel();
if tt.is_active() {
Box::new(tt)
} else {
Box::new(&self.primary.sim)
}
}
}
2018-06-22 21:01:44 +03:00
#[derive(Serialize, Deserialize, Debug)]
pub struct EditorState {
pub map_name: String,
2018-06-22 21:01:44 +03:00
pub cam_x: f64,
pub cam_y: f64,
pub cam_zoom: f64,
}
pub trait ShowTurnIcons {
fn show_icons_for(&self, id: IntersectionID) -> bool;
}
impl ShowTurnIcons for UI {
fn show_icons_for(&self, id: IntersectionID) -> bool {
2018-10-22 19:59:18 +03:00
self.plugins.layers().show_all_turn_icons.is_enabled()
|| self.primary_plugins.stop_sign_editor().show_turn_icons(id)
|| self
.primary_plugins
.traffic_signal_editor()
.show_turn_icons(id)
|| {
if let Some(ID::Turn(t)) = self.primary.current_selection {
t.parent == id
} else {
false
}
}
}
}
2018-10-03 17:47:59 +03:00
// This mirrors many, but not all, of the fields in UI.
pub struct PluginCtx<'a> {
pub primary: &'a mut PerMapUI,
2018-11-23 02:15:56 +03:00
// Only filled out for PluginsPerUI, not for PluginsPerMap.
pub primary_plugins: Option<&'a mut PluginsPerMap>,
pub secondary: &'a mut Option<(PerMapUI, PluginsPerMap)>,
pub canvas: &'a mut Canvas,
pub cs: &'a mut ColorScheme,
pub input: &'a mut UserInput,
pub hints: &'a mut RenderingHints,
pub kml: &'a Option<String>,
2018-10-03 17:47:59 +03:00
}