2018-06-04 03:47:46 +03:00
|
|
|
// Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0
|
2018-03-13 18:04:21 +03:00
|
|
|
|
|
|
|
// TODO this should just be a way to handle interactions between plugins
|
|
|
|
|
2018-06-22 21:01:44 +03:00
|
|
|
use abstutil;
|
2018-06-21 20:03:51 +03:00
|
|
|
use colors::{ColorScheme, Colors};
|
2018-05-11 02:12:16 +03:00
|
|
|
use control::ControlMap;
|
2018-10-18 02:57:15 +03:00
|
|
|
//use cpuprofiler;
|
2018-10-09 02:27:24 +03:00
|
|
|
use ezgui::{Canvas, Color, EventLoopMode, GfxCtx, Text, UserInput, BOTTOM_LEFT, GUI};
|
2018-09-08 23:01:36 +03:00
|
|
|
use flame;
|
2018-08-10 23:28:34 +03:00
|
|
|
use kml;
|
2018-10-08 22:55:42 +03:00
|
|
|
use map_model::{IntersectionID, Map};
|
2018-10-09 02:27:24 +03:00
|
|
|
use objects::{Ctx, ID, ROOT_MENU};
|
2018-10-06 01:44:12 +03:00
|
|
|
use piston::input::Key;
|
2018-10-08 19:10:00 +03:00
|
|
|
use plugins::a_b_tests::ABTestManager;
|
2018-10-13 21:34:00 +03:00
|
|
|
use plugins::chokepoints::ChokepointsFinder;
|
2018-03-13 18:04:21 +03:00
|
|
|
use plugins::classification::OsmClassifier;
|
2018-06-21 04:30:38 +03:00
|
|
|
use plugins::color_picker::ColorPicker;
|
2018-09-13 19:59:49 +03:00
|
|
|
use plugins::debug_objects::DebugObjectsState;
|
2018-10-16 22:38:07 +03:00
|
|
|
use plugins::diff_worlds::DiffWorldsState;
|
2018-09-25 00:17:57 +03:00
|
|
|
use plugins::draw_neighborhoods::DrawNeighborhoodState;
|
2018-03-13 18:04:21 +03:00
|
|
|
use plugins::floodfill::Floodfiller;
|
2018-08-29 21:11:51 +03:00
|
|
|
use plugins::follow::FollowState;
|
2018-07-06 01:37:35 +03:00
|
|
|
use plugins::geom_validation::Validator;
|
2018-09-13 19:43:04 +03:00
|
|
|
use plugins::hider::Hider;
|
2018-10-09 02:27:24 +03:00
|
|
|
use plugins::layers::ToggleableLayers;
|
2018-09-22 00:16:38 +03:00
|
|
|
use plugins::logs::DisplayLogs;
|
2018-10-03 02:37:50 +03:00
|
|
|
use plugins::map_edits::EditsManager;
|
2018-07-24 22:58:55 +03:00
|
|
|
use plugins::road_editor::RoadEditor;
|
2018-09-25 00:17:57 +03:00
|
|
|
use plugins::scenarios::ScenarioManager;
|
2018-03-13 18:04:21 +03:00
|
|
|
use plugins::search::SearchState;
|
2018-10-17 21:55:31 +03:00
|
|
|
use plugins::show_owner::ShowOwnerState;
|
2018-08-31 01:15:37 +03:00
|
|
|
use plugins::show_route::ShowRouteState;
|
2018-04-11 03:38:26 +03:00
|
|
|
use plugins::sim_controls::SimController;
|
2018-03-13 18:04:21 +03:00
|
|
|
use plugins::steep::SteepnessVisualizer;
|
|
|
|
use plugins::stop_sign_editor::StopSignEditor;
|
|
|
|
use plugins::traffic_signal_editor::TrafficSignalEditor;
|
2018-09-13 02:05:35 +03:00
|
|
|
use plugins::turn_cycler::TurnCyclerState;
|
2018-06-26 18:44:58 +03:00
|
|
|
use plugins::warp::WarpState;
|
2018-09-15 19:11:06 +03:00
|
|
|
use plugins::Colorizer;
|
2018-09-14 23:05:55 +03:00
|
|
|
use render::{DrawMap, RenderOptions};
|
2018-08-27 23:03:35 +03:00
|
|
|
use sim;
|
2018-10-03 17:47:59 +03:00
|
|
|
use sim::{Sim, SimFlags};
|
2018-03-13 18:04:21 +03:00
|
|
|
use std::process;
|
|
|
|
|
2018-09-14 23:23:39 +03:00
|
|
|
const MIN_ZOOM_FOR_MOUSEOVER: f64 = 4.0;
|
2018-03-13 18:04:21 +03:00
|
|
|
|
2018-09-13 08:43:58 +03:00
|
|
|
// Necessary so we can iterate over and run the plugins, which mutably borrow UI.
|
|
|
|
pub struct UIWrapper {
|
|
|
|
ui: UI,
|
2018-10-03 17:47:59 +03:00
|
|
|
plugins: Vec<Box<Fn(PluginCtx) -> bool>>,
|
2018-09-13 08:43:58 +03:00
|
|
|
}
|
2018-03-13 18:04:21 +03:00
|
|
|
|
2018-09-13 08:43:58 +03:00
|
|
|
impl GUI for UIWrapper {
|
2018-09-28 18:01:15 +03:00
|
|
|
fn event(&mut self, input: UserInput, osd: &mut Text) -> EventLoopMode {
|
2018-10-09 00:12:44 +03:00
|
|
|
self.ui.event(input, osd, &self.plugins)
|
2018-09-13 08:43:58 +03:00
|
|
|
}
|
2018-09-13 03:39:53 +03:00
|
|
|
|
2018-10-06 21:50:55 +03:00
|
|
|
fn get_mut_canvas(&mut self) -> &mut Canvas {
|
|
|
|
&mut self.ui.canvas
|
|
|
|
}
|
|
|
|
|
|
|
|
fn draw(&self, g: &mut GfxCtx, osd: Text) {
|
2018-09-21 00:37:49 +03:00
|
|
|
self.ui.draw(g, osd);
|
2018-09-13 08:43:58 +03:00
|
|
|
}
|
2018-03-13 18:04:21 +03:00
|
|
|
}
|
|
|
|
|
2018-09-13 08:43:58 +03:00
|
|
|
impl UIWrapper {
|
|
|
|
// nit: lots of this logic could live in UI, if it mattered
|
2018-10-03 02:16:25 +03:00
|
|
|
pub fn new(flags: SimFlags, kml: Option<String>) -> UIWrapper {
|
2018-09-28 00:55:59 +03:00
|
|
|
// Do this first, so anything logged by sim::load isn't lost.
|
|
|
|
let logs = DisplayLogs::new();
|
|
|
|
|
2018-03-13 18:04:21 +03:00
|
|
|
let mut ui = UI {
|
2018-10-09 00:12:44 +03:00
|
|
|
primary: PerMapUI::new(flags, &kml),
|
2018-10-08 22:55:42 +03:00
|
|
|
secondary: None,
|
2018-03-13 18:04:21 +03:00
|
|
|
|
2018-09-14 22:50:28 +03:00
|
|
|
layers: ToggleableLayers::new(),
|
2018-09-13 21:33:22 +03:00
|
|
|
search_state: SearchState::Empty,
|
2018-06-26 18:44:58 +03:00
|
|
|
warp: WarpState::Empty,
|
2018-06-22 20:26:36 +03:00
|
|
|
osm_classifier: OsmClassifier::new(),
|
2018-10-08 22:55:42 +03:00
|
|
|
sim_ctrl: SimController::new(),
|
2018-06-21 04:30:38 +03:00
|
|
|
color_picker: ColorPicker::new(),
|
2018-10-08 18:28:38 +03:00
|
|
|
ab_test_manager: ABTestManager::new(),
|
2018-09-28 00:55:59 +03:00
|
|
|
logs,
|
2018-10-16 22:38:07 +03:00
|
|
|
diff_worlds: DiffWorldsState::new(),
|
2018-03-13 18:04:21 +03:00
|
|
|
|
2018-09-13 19:21:41 +03:00
|
|
|
active_plugin: None,
|
|
|
|
|
2018-09-10 03:10:34 +03:00
|
|
|
canvas: Canvas::new(),
|
2018-06-21 20:42:30 +03:00
|
|
|
cs: ColorScheme::load("color_scheme").unwrap(),
|
2018-10-09 00:12:44 +03:00
|
|
|
|
|
|
|
kml,
|
2018-03-13 18:04:21 +03:00
|
|
|
};
|
|
|
|
|
2018-06-22 21:01:44 +03:00
|
|
|
match abstutil::read_json::<EditorState>("editor_state") {
|
2018-10-08 22:55:42 +03:00
|
|
|
Ok(ref state) if ui.primary.map.get_name().to_string() == state.map_name => {
|
2018-09-23 23:57:54 +03:00
|
|
|
info!("Loaded previous editor_state");
|
2018-03-13 18:04:21 +03:00
|
|
|
ui.canvas.cam_x = state.cam_x;
|
|
|
|
ui.canvas.cam_y = state.cam_y;
|
|
|
|
ui.canvas.cam_zoom = state.cam_zoom;
|
|
|
|
}
|
2018-09-09 23:21:53 +03:00
|
|
|
_ => {
|
2018-09-23 23:57:54 +03:00
|
|
|
warn!("Couldn't load editor_state or it's for a different map, so just centering initial view");
|
2018-10-08 22:55:42 +03:00
|
|
|
ui.canvas.center_on_map_pt(ui.primary.draw_map.center_pt);
|
2018-03-13 18:04:21 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-09 02:27:24 +03:00
|
|
|
ui.layers.handle_zoom(-1.0, ui.canvas.cam_zoom);
|
2018-03-13 18:04:21 +03:00
|
|
|
|
2018-09-13 08:43:58 +03:00
|
|
|
UIWrapper {
|
|
|
|
ui,
|
|
|
|
plugins: vec![
|
2018-10-03 17:47:59 +03:00
|
|
|
Box::new(|ctx| {
|
2018-10-09 02:27:24 +03:00
|
|
|
if ctx.ui.layers.event(ctx.input) {
|
2018-10-16 22:07:05 +03:00
|
|
|
ctx.ui.primary.recalculate_current_selection = true;
|
2018-09-13 22:01:53 +03:00
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}),
|
2018-10-03 17:47:59 +03:00
|
|
|
Box::new(|ctx| {
|
2018-10-08 22:55:42 +03:00
|
|
|
ctx.ui.primary.traffic_signal_editor.event(
|
2018-10-03 17:47:59 +03:00
|
|
|
ctx.input,
|
2018-10-08 22:55:42 +03:00
|
|
|
&ctx.ui.primary.map,
|
|
|
|
&mut ctx.ui.primary.control_map,
|
|
|
|
ctx.ui.primary.current_selection,
|
2018-09-13 08:43:58 +03:00
|
|
|
)
|
|
|
|
}),
|
2018-10-03 17:47:59 +03:00
|
|
|
Box::new(|ctx| {
|
2018-10-08 22:55:42 +03:00
|
|
|
ctx.ui.primary.stop_sign_editor.event(
|
2018-10-03 17:47:59 +03:00
|
|
|
ctx.input,
|
2018-10-08 22:55:42 +03:00
|
|
|
&ctx.ui.primary.map,
|
|
|
|
&mut ctx.ui.primary.control_map,
|
|
|
|
ctx.ui.primary.current_selection,
|
2018-09-13 08:43:58 +03:00
|
|
|
)
|
|
|
|
}),
|
2018-10-03 17:47:59 +03:00
|
|
|
Box::new(|ctx| {
|
2018-10-08 22:55:42 +03:00
|
|
|
ctx.ui.primary.road_editor.event(
|
2018-10-03 17:47:59 +03:00
|
|
|
ctx.input,
|
2018-10-08 22:55:42 +03:00
|
|
|
ctx.ui.primary.current_selection,
|
|
|
|
&mut ctx.ui.primary.map,
|
|
|
|
&mut ctx.ui.primary.draw_map,
|
|
|
|
&ctx.ui.primary.control_map,
|
|
|
|
&mut ctx.ui.primary.sim,
|
2018-09-13 08:43:58 +03:00
|
|
|
)
|
|
|
|
}),
|
2018-10-03 17:47:59 +03:00
|
|
|
Box::new(|ctx| ctx.ui.search_state.event(ctx.input)),
|
|
|
|
Box::new(|ctx| {
|
|
|
|
ctx.ui.warp.event(
|
|
|
|
ctx.input,
|
2018-10-08 22:55:42 +03:00
|
|
|
&ctx.ui.primary.map,
|
|
|
|
&ctx.ui.primary.sim,
|
2018-10-09 01:56:30 +03:00
|
|
|
&ctx.ui.primary.draw_map,
|
2018-10-03 17:47:59 +03:00
|
|
|
&mut ctx.ui.canvas,
|
2018-10-08 22:55:42 +03:00
|
|
|
&mut ctx.ui.primary.current_selection,
|
2018-09-13 08:43:58 +03:00
|
|
|
)
|
|
|
|
}),
|
2018-10-03 17:47:59 +03:00
|
|
|
Box::new(|ctx| {
|
2018-10-08 22:55:42 +03:00
|
|
|
ctx.ui.primary.follow.event(
|
2018-10-03 17:47:59 +03:00
|
|
|
ctx.input,
|
2018-10-08 22:55:42 +03:00
|
|
|
&ctx.ui.primary.map,
|
|
|
|
&ctx.ui.primary.sim,
|
2018-10-03 17:47:59 +03:00
|
|
|
&mut ctx.ui.canvas,
|
2018-10-08 22:55:42 +03:00
|
|
|
ctx.ui.primary.current_selection,
|
2018-09-13 21:33:22 +03:00
|
|
|
)
|
|
|
|
}),
|
2018-10-03 17:47:59 +03:00
|
|
|
Box::new(|ctx| {
|
2018-10-08 22:55:42 +03:00
|
|
|
ctx.ui.primary.show_route.event(
|
|
|
|
ctx.input,
|
|
|
|
&ctx.ui.primary.sim,
|
2018-10-09 23:10:24 +03:00
|
|
|
&ctx.ui.primary.map,
|
2018-10-08 22:55:42 +03:00
|
|
|
ctx.ui.primary.current_selection,
|
|
|
|
)
|
2018-10-03 17:47:59 +03:00
|
|
|
}),
|
|
|
|
Box::new(|ctx| {
|
|
|
|
ctx.ui
|
|
|
|
.color_picker
|
|
|
|
.event(ctx.input, &mut ctx.ui.canvas, &mut ctx.ui.cs)
|
|
|
|
}),
|
2018-10-08 22:55:42 +03:00
|
|
|
Box::new(|ctx| ctx.ui.primary.steepness_viz.event(ctx.input)),
|
2018-10-03 17:47:59 +03:00
|
|
|
Box::new(|ctx| ctx.ui.osm_classifier.event(ctx.input)),
|
|
|
|
Box::new(|ctx| {
|
2018-10-08 22:55:42 +03:00
|
|
|
ctx.ui
|
|
|
|
.primary
|
|
|
|
.hider
|
|
|
|
.event(ctx.input, &mut ctx.ui.primary.current_selection)
|
|
|
|
}),
|
|
|
|
Box::new(|ctx| {
|
|
|
|
ctx.ui.primary.debug_objects.event(
|
|
|
|
ctx.ui.primary.current_selection,
|
2018-10-03 17:47:59 +03:00
|
|
|
ctx.input,
|
2018-10-08 22:55:42 +03:00
|
|
|
&ctx.ui.primary.map,
|
|
|
|
&mut ctx.ui.primary.sim,
|
|
|
|
&ctx.ui.primary.control_map,
|
2018-10-03 17:47:59 +03:00
|
|
|
)
|
2018-09-21 00:37:49 +03:00
|
|
|
}),
|
2018-10-03 17:47:59 +03:00
|
|
|
Box::new(|ctx| {
|
2018-10-08 22:55:42 +03:00
|
|
|
ctx.ui.primary.floodfiller.event(
|
|
|
|
&ctx.ui.primary.map,
|
|
|
|
ctx.input,
|
|
|
|
ctx.ui.primary.current_selection,
|
|
|
|
)
|
2018-09-21 00:37:49 +03:00
|
|
|
}),
|
2018-10-03 17:47:59 +03:00
|
|
|
Box::new(|ctx| {
|
2018-10-08 22:55:42 +03:00
|
|
|
ctx.ui.primary.geom_validator.event(
|
2018-10-03 17:47:59 +03:00
|
|
|
ctx.input,
|
|
|
|
&mut ctx.ui.canvas,
|
2018-10-08 22:55:42 +03:00
|
|
|
&ctx.ui.primary.map,
|
2018-10-09 01:56:30 +03:00
|
|
|
&ctx.ui.primary.sim,
|
2018-10-08 22:55:42 +03:00
|
|
|
&ctx.ui.primary.draw_map,
|
2018-09-13 20:50:59 +03:00
|
|
|
)
|
2018-09-13 19:59:49 +03:00
|
|
|
}),
|
2018-10-03 17:47:59 +03:00
|
|
|
Box::new(|ctx| {
|
|
|
|
ctx.ui
|
2018-10-08 22:55:42 +03:00
|
|
|
.primary
|
2018-10-03 17:47:59 +03:00
|
|
|
.turn_cycler
|
2018-10-08 22:55:42 +03:00
|
|
|
.event(ctx.input, ctx.ui.primary.current_selection)
|
2018-09-21 00:37:49 +03:00
|
|
|
}),
|
2018-10-03 17:47:59 +03:00
|
|
|
Box::new(|ctx| {
|
2018-10-08 22:55:42 +03:00
|
|
|
ctx.ui.primary.draw_neighborhoods.event(
|
|
|
|
ctx.input,
|
|
|
|
&ctx.ui.canvas,
|
|
|
|
&ctx.ui.primary.map,
|
|
|
|
ctx.osd,
|
|
|
|
)
|
2018-09-13 21:33:22 +03:00
|
|
|
}),
|
2018-10-03 17:47:59 +03:00
|
|
|
Box::new(|ctx| {
|
2018-10-08 22:55:42 +03:00
|
|
|
ctx.ui.primary.scenarios.event(
|
|
|
|
ctx.input,
|
|
|
|
&ctx.ui.primary.map,
|
|
|
|
&mut ctx.ui.primary.sim,
|
|
|
|
)
|
2018-09-25 00:17:57 +03:00
|
|
|
}),
|
2018-10-03 17:47:59 +03:00
|
|
|
Box::new(|ctx| {
|
2018-10-09 00:37:55 +03:00
|
|
|
let (active, new_primary) = ctx.ui.primary.edits_manager.event(
|
2018-10-03 17:47:59 +03:00
|
|
|
ctx.input,
|
2018-10-08 22:55:42 +03:00
|
|
|
&ctx.ui.primary.map,
|
|
|
|
&ctx.ui.primary.control_map,
|
|
|
|
&ctx.ui.primary.road_editor,
|
2018-10-09 00:54:09 +03:00
|
|
|
&mut ctx.ui.primary.current_flags,
|
2018-10-09 00:37:55 +03:00
|
|
|
&ctx.ui.kml,
|
|
|
|
);
|
|
|
|
if new_primary.is_some() {
|
|
|
|
ctx.ui.primary = new_primary.unwrap();
|
|
|
|
}
|
|
|
|
active
|
2018-10-03 17:47:59 +03:00
|
|
|
}),
|
2018-10-13 21:34:00 +03:00
|
|
|
Box::new(|ctx| {
|
|
|
|
ctx.ui.primary.chokepoints.event(
|
|
|
|
ctx.input,
|
|
|
|
&ctx.ui.primary.sim,
|
|
|
|
&ctx.ui.primary.map,
|
|
|
|
)
|
|
|
|
}),
|
2018-10-09 00:56:46 +03:00
|
|
|
Box::new(|ctx| {
|
2018-10-14 03:31:30 +03:00
|
|
|
let (active, new_ui) = ctx.ui.ab_test_manager.event(
|
|
|
|
ctx.input,
|
2018-10-16 22:38:07 +03:00
|
|
|
ctx.ui.primary.current_selection,
|
2018-10-14 03:31:30 +03:00
|
|
|
&ctx.ui.primary.map,
|
|
|
|
&ctx.ui.kml,
|
|
|
|
&ctx.ui.primary.current_flags,
|
|
|
|
);
|
2018-10-09 00:56:46 +03:00
|
|
|
if let Some((new_primary, new_secondary)) = new_ui {
|
|
|
|
ctx.ui.primary = new_primary;
|
|
|
|
ctx.ui.secondary = Some(new_secondary);
|
|
|
|
}
|
|
|
|
active
|
|
|
|
}),
|
2018-10-03 17:47:59 +03:00
|
|
|
Box::new(|ctx| ctx.ui.logs.event(ctx.input)),
|
2018-10-16 22:38:07 +03:00
|
|
|
Box::new(|ctx| {
|
|
|
|
ctx.ui
|
|
|
|
.diff_worlds
|
|
|
|
.event(ctx.input, &ctx.ui.primary, &ctx.ui.secondary)
|
|
|
|
}),
|
2018-10-17 21:55:31 +03:00
|
|
|
Box::new(|ctx| {
|
|
|
|
ctx.ui
|
|
|
|
.primary
|
|
|
|
.show_owner
|
|
|
|
.event(ctx.ui.primary.current_selection, &ctx.ui.primary.sim);
|
|
|
|
// TODO This is a weird exception -- this plugin doesn't consume input, so
|
|
|
|
// never treat it as active for blocking input
|
|
|
|
false
|
|
|
|
}),
|
2018-09-13 08:43:58 +03:00
|
|
|
],
|
|
|
|
}
|
2018-03-13 18:04:21 +03:00
|
|
|
}
|
2018-09-13 08:43:58 +03:00
|
|
|
}
|
|
|
|
|
2018-10-08 22:55:42 +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?
|
2018-10-09 00:37:55 +03:00
|
|
|
pub struct PerMapUI {
|
2018-10-09 02:40:33 +03:00
|
|
|
pub map: Map,
|
2018-09-14 23:05:55 +03:00
|
|
|
draw_map: DrawMap,
|
2018-10-09 02:40:33 +03:00
|
|
|
pub control_map: ControlMap,
|
|
|
|
pub sim: Sim,
|
2018-09-13 08:43:58 +03:00
|
|
|
|
2018-10-09 02:40:33 +03:00
|
|
|
pub current_selection: Option<ID>,
|
2018-10-16 22:07:05 +03:00
|
|
|
pub recalculate_current_selection: bool,
|
2018-10-09 00:54:09 +03:00
|
|
|
current_flags: SimFlags,
|
2018-09-13 08:43:58 +03:00
|
|
|
|
2018-10-08 22:55:42 +03:00
|
|
|
// Anything that holds onto any kind of ID has to live here!
|
2018-09-13 08:43:58 +03:00
|
|
|
hider: Hider,
|
2018-09-13 19:59:49 +03:00
|
|
|
debug_objects: DebugObjectsState,
|
2018-09-13 08:43:58 +03:00
|
|
|
follow: FollowState,
|
|
|
|
show_route: ShowRouteState,
|
|
|
|
floodfiller: Floodfiller,
|
|
|
|
steepness_viz: SteepnessVisualizer,
|
|
|
|
traffic_signal_editor: TrafficSignalEditor,
|
|
|
|
stop_sign_editor: StopSignEditor,
|
|
|
|
road_editor: RoadEditor,
|
|
|
|
geom_validator: Validator,
|
|
|
|
turn_cycler: TurnCyclerState,
|
2018-10-17 21:55:31 +03:00
|
|
|
show_owner: ShowOwnerState,
|
2018-09-25 00:17:57 +03:00
|
|
|
draw_neighborhoods: DrawNeighborhoodState,
|
|
|
|
scenarios: ScenarioManager,
|
2018-10-03 02:37:50 +03:00
|
|
|
edits_manager: EditsManager,
|
2018-10-13 21:34:00 +03:00
|
|
|
chokepoints: ChokepointsFinder,
|
2018-10-08 22:55:42 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
impl PerMapUI {
|
2018-10-09 00:37:55 +03:00
|
|
|
pub fn new(flags: SimFlags, kml: &Option<String>) -> PerMapUI {
|
2018-10-08 22:55:42 +03:00
|
|
|
flame::start("setup");
|
|
|
|
let (map, control_map, sim) = sim::load(flags.clone(), Some(sim::Tick::from_seconds(30)));
|
2018-10-09 00:12:44 +03:00
|
|
|
let extra_shapes = if let Some(path) = kml {
|
2018-10-08 22:55:42 +03:00
|
|
|
kml::load(&path, &map.get_gps_bounds()).expect("Couldn't load extra KML shapes")
|
|
|
|
} else {
|
|
|
|
Vec::new()
|
|
|
|
};
|
|
|
|
|
|
|
|
flame::start("draw_map");
|
|
|
|
let draw_map = DrawMap::new(&map, &control_map, extra_shapes);
|
|
|
|
flame::end("draw_map");
|
|
|
|
|
|
|
|
flame::end("setup");
|
|
|
|
flame::dump_stdout();
|
|
|
|
|
|
|
|
let steepness_viz = SteepnessVisualizer::new(&map);
|
|
|
|
let road_editor = RoadEditor::new(map.get_road_edits().clone());
|
|
|
|
|
|
|
|
PerMapUI {
|
|
|
|
map,
|
|
|
|
draw_map,
|
|
|
|
control_map,
|
|
|
|
sim,
|
|
|
|
|
|
|
|
current_selection: None,
|
2018-10-16 22:07:05 +03:00
|
|
|
recalculate_current_selection: false,
|
2018-10-09 00:54:09 +03:00
|
|
|
current_flags: flags,
|
2018-10-08 22:55:42 +03:00
|
|
|
|
|
|
|
hider: Hider::new(),
|
|
|
|
debug_objects: DebugObjectsState::new(),
|
|
|
|
follow: FollowState::Empty,
|
|
|
|
show_route: ShowRouteState::Empty,
|
|
|
|
floodfiller: Floodfiller::new(),
|
|
|
|
steepness_viz,
|
|
|
|
traffic_signal_editor: TrafficSignalEditor::new(),
|
|
|
|
stop_sign_editor: StopSignEditor::new(),
|
|
|
|
road_editor,
|
|
|
|
geom_validator: Validator::new(),
|
|
|
|
turn_cycler: TurnCyclerState::new(),
|
2018-10-17 21:55:31 +03:00
|
|
|
show_owner: ShowOwnerState::new(),
|
2018-10-08 22:55:42 +03:00
|
|
|
draw_neighborhoods: DrawNeighborhoodState::new(),
|
|
|
|
scenarios: ScenarioManager::new(),
|
2018-10-09 00:54:09 +03:00
|
|
|
edits_manager: EditsManager::new(),
|
2018-10-13 21:34:00 +03:00
|
|
|
chokepoints: ChokepointsFinder::new(),
|
2018-10-08 22:55:42 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct UI {
|
|
|
|
primary: PerMapUI,
|
|
|
|
// When running an A/B test, this is populated too.
|
|
|
|
secondary: Option<PerMapUI>,
|
|
|
|
|
|
|
|
layers: ToggleableLayers,
|
|
|
|
search_state: SearchState,
|
|
|
|
warp: WarpState,
|
|
|
|
osm_classifier: OsmClassifier,
|
|
|
|
sim_ctrl: SimController,
|
|
|
|
color_picker: ColorPicker,
|
2018-10-08 18:28:38 +03:00
|
|
|
ab_test_manager: ABTestManager,
|
2018-09-21 23:32:47 +03:00
|
|
|
logs: DisplayLogs,
|
2018-10-16 22:38:07 +03:00
|
|
|
diff_worlds: DiffWorldsState,
|
2018-09-13 08:43:58 +03:00
|
|
|
|
2018-09-13 19:21:41 +03:00
|
|
|
// An index into UIWrapper.plugins.
|
|
|
|
active_plugin: Option<usize>,
|
|
|
|
|
2018-09-13 08:43:58 +03:00
|
|
|
canvas: Canvas,
|
|
|
|
cs: ColorScheme,
|
2018-10-09 00:12:44 +03:00
|
|
|
|
|
|
|
// Remember this to support loading a new PerMapUI
|
|
|
|
kml: Option<String>,
|
2018-09-13 08:43:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
impl UI {
|
2018-07-06 20:02:01 +03:00
|
|
|
fn mouseover_something(&self) -> Option<ID> {
|
2018-09-19 00:41:30 +03:00
|
|
|
let pt = self.canvas.get_cursor_in_map_space();
|
2018-06-23 19:01:53 +03:00
|
|
|
|
2018-10-08 22:55:42 +03:00
|
|
|
let (statics, dynamics) = self.primary.draw_map.get_objects_onscreen(
|
2018-09-14 22:16:32 +03:00
|
|
|
self.canvas.get_screen_bbox(),
|
2018-10-08 22:55:42 +03:00
|
|
|
&self.primary.hider,
|
|
|
|
&self.primary.map,
|
|
|
|
&self.primary.sim,
|
2018-09-14 22:50:28 +03:00
|
|
|
&self.layers,
|
2018-09-14 22:58:12 +03:00
|
|
|
self,
|
2018-09-14 22:16:32 +03:00
|
|
|
);
|
|
|
|
// 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
|
|
|
}
|
|
|
|
}
|
2018-09-14 22:16:32 +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
|
|
|
|
}
|
|
|
|
|
2018-09-13 08:43:58 +03:00
|
|
|
fn event(
|
|
|
|
&mut self,
|
2018-09-21 00:37:49 +03:00
|
|
|
mut input: UserInput,
|
2018-09-28 18:01:15 +03:00
|
|
|
osd: &mut Text,
|
2018-10-03 17:47:59 +03:00
|
|
|
plugins: &Vec<Box<Fn(PluginCtx) -> bool>>,
|
2018-09-13 08:43:58 +03:00
|
|
|
) -> EventLoopMode {
|
2018-07-06 21:23:53 +03:00
|
|
|
// First update the camera and handle zoom
|
|
|
|
let old_zoom = self.canvas.cam_zoom;
|
2018-10-06 01:44:12 +03:00
|
|
|
self.canvas.handle_event(&mut input);
|
2018-07-06 21:23:53 +03:00
|
|
|
let new_zoom = self.canvas.cam_zoom;
|
2018-10-09 02:27:24 +03:00
|
|
|
self.layers.handle_zoom(old_zoom, new_zoom);
|
2018-07-06 21:23:53 +03:00
|
|
|
|
|
|
|
// Always handle mouseover
|
|
|
|
if old_zoom >= MIN_ZOOM_FOR_MOUSEOVER && new_zoom < MIN_ZOOM_FOR_MOUSEOVER {
|
2018-10-08 22:55:42 +03:00
|
|
|
self.primary.current_selection = None;
|
2018-07-06 21:23:53 +03:00
|
|
|
}
|
2018-07-09 22:30:59 +03:00
|
|
|
if !self.canvas.is_dragging()
|
2018-10-06 01:44:12 +03:00
|
|
|
&& input.get_moved_mouse().is_some()
|
2018-07-06 21:23:53 +03:00
|
|
|
&& new_zoom >= MIN_ZOOM_FOR_MOUSEOVER
|
|
|
|
{
|
2018-10-08 22:55:42 +03:00
|
|
|
self.primary.current_selection = self.mouseover_something();
|
2018-07-06 21:23:53 +03:00
|
|
|
}
|
2018-03-13 18:04:21 +03:00
|
|
|
|
2018-09-13 19:43:04 +03:00
|
|
|
// 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?)
|
|
|
|
|
2018-09-13 19:21:41 +03:00
|
|
|
// If there's an active plugin, just run it.
|
|
|
|
if let Some(idx) = self.active_plugin {
|
2018-10-03 17:47:59 +03:00
|
|
|
if !plugins[idx](PluginCtx {
|
|
|
|
ui: self,
|
|
|
|
input: &mut input,
|
|
|
|
osd,
|
|
|
|
}) {
|
2018-09-13 19:21:41 +03:00
|
|
|
self.active_plugin = None;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Run each plugin, short-circuiting if the plugin claimed it was active.
|
|
|
|
for (idx, plugin) in plugins.iter().enumerate() {
|
2018-10-03 17:47:59 +03:00
|
|
|
if plugin(PluginCtx {
|
|
|
|
ui: self,
|
|
|
|
input: &mut input,
|
|
|
|
osd,
|
|
|
|
}) {
|
2018-09-13 19:21:41 +03:00
|
|
|
self.active_plugin = Some(idx);
|
|
|
|
break;
|
|
|
|
}
|
2018-09-13 08:43:58 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-21 20:47:34 +03:00
|
|
|
if input.unimportant_key_pressed(Key::Escape, ROOT_MENU, "quit") {
|
2018-06-22 21:01:44 +03:00
|
|
|
let state = EditorState {
|
2018-10-08 22:55:42 +03:00
|
|
|
map_name: self.primary.map.get_name().clone(),
|
2018-03-13 18:04:21 +03:00
|
|
|
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
|
2018-06-22 21:01:44 +03:00
|
|
|
abstutil::write_json("editor_state", &state).expect("Saving editor_state failed");
|
|
|
|
abstutil::write_json("color_scheme", &self.cs).expect("Saving color_scheme failed");
|
2018-10-03 17:47:59 +03:00
|
|
|
info!("Saved editor_state and color_scheme");
|
2018-10-18 02:57:15 +03:00
|
|
|
//cpuprofiler::PROFILER.lock().unwrap().stop().unwrap();
|
2018-03-13 18:04:21 +03:00
|
|
|
process::exit(0);
|
|
|
|
}
|
|
|
|
|
2018-07-06 21:23:53 +03:00
|
|
|
// Sim controller plugin is kind of always active? If nothing else ran, let it use keys.
|
2018-10-09 02:40:33 +03:00
|
|
|
let result = self
|
|
|
|
.sim_ctrl
|
|
|
|
.event(&mut input, &mut self.primary, &mut self.secondary, osd);
|
2018-10-16 22:07:05 +03:00
|
|
|
|
|
|
|
if self.primary.recalculate_current_selection {
|
|
|
|
self.primary.recalculate_current_selection = false;
|
|
|
|
self.primary.current_selection = self.mouseover_something();
|
|
|
|
}
|
|
|
|
|
2018-09-21 00:37:49 +03:00
|
|
|
input.populate_osd(osd);
|
|
|
|
result
|
2018-03-13 18:04:21 +03:00
|
|
|
}
|
|
|
|
|
2018-09-28 18:01:15 +03:00
|
|
|
fn draw(&self, g: &mut GfxCtx, osd: Text) {
|
2018-07-06 20:33:03 +03:00
|
|
|
g.clear(self.cs.get(Colors::Background));
|
2018-03-13 18:04:21 +03:00
|
|
|
|
2018-10-08 22:55:42 +03:00
|
|
|
let (statics, dynamics) = self.primary.draw_map.get_objects_onscreen(
|
2018-09-14 22:16:32 +03:00
|
|
|
self.canvas.get_screen_bbox(),
|
2018-10-08 22:55:42 +03:00
|
|
|
&self.primary.hider,
|
|
|
|
&self.primary.map,
|
|
|
|
&self.primary.sim,
|
2018-09-14 22:50:28 +03:00
|
|
|
&self.layers,
|
2018-09-14 22:58:12 +03:00
|
|
|
self,
|
2018-09-14 22:16:32 +03:00
|
|
|
);
|
|
|
|
for obj in statics.into_iter() {
|
2018-09-14 23:05:55 +03:00
|
|
|
let opts = RenderOptions {
|
2018-09-16 02:50:19 +03:00
|
|
|
color: self.color_obj(obj.get_id()),
|
2018-09-14 23:05:55 +03:00
|
|
|
cam_zoom: self.canvas.cam_zoom,
|
|
|
|
debug_mode: self.layers.debug_mode.is_enabled(),
|
|
|
|
};
|
2018-09-15 19:11:06 +03:00
|
|
|
obj.draw(
|
|
|
|
g,
|
|
|
|
opts,
|
|
|
|
Ctx {
|
|
|
|
cs: &self.cs,
|
2018-10-08 22:55:42 +03:00
|
|
|
map: &self.primary.map,
|
|
|
|
control_map: &self.primary.control_map,
|
2018-09-15 19:11:06 +03:00
|
|
|
canvas: &self.canvas,
|
2018-10-08 22:55:42 +03:00
|
|
|
sim: &self.primary.sim,
|
2018-09-15 19:11:06 +03:00
|
|
|
},
|
|
|
|
);
|
2018-03-13 18:04:21 +03:00
|
|
|
}
|
2018-09-14 22:16:32 +03:00
|
|
|
for obj in dynamics.into_iter() {
|
2018-09-14 23:05:55 +03:00
|
|
|
let opts = RenderOptions {
|
2018-09-16 02:50:19 +03:00
|
|
|
color: self.color_obj(obj.get_id()),
|
2018-09-14 23:05:55 +03:00
|
|
|
cam_zoom: self.canvas.cam_zoom,
|
|
|
|
debug_mode: self.layers.debug_mode.is_enabled(),
|
|
|
|
};
|
2018-09-15 19:11:06 +03:00
|
|
|
obj.draw(
|
|
|
|
g,
|
|
|
|
opts,
|
|
|
|
Ctx {
|
|
|
|
cs: &self.cs,
|
2018-10-08 22:55:42 +03:00
|
|
|
map: &self.primary.map,
|
|
|
|
control_map: &self.primary.control_map,
|
2018-09-15 19:11:06 +03:00
|
|
|
canvas: &self.canvas,
|
2018-10-08 22:55:42 +03:00
|
|
|
sim: &self.primary.sim,
|
2018-09-15 19:11:06 +03:00
|
|
|
},
|
|
|
|
);
|
2018-08-10 23:28:34 +03:00
|
|
|
}
|
2018-09-14 22:16:32 +03:00
|
|
|
|
2018-09-18 18:02:23 +03:00
|
|
|
// TODO Only if active?
|
2018-10-08 22:55:42 +03:00
|
|
|
self.primary.turn_cycler.draw(
|
|
|
|
&self.primary.map,
|
|
|
|
&self.primary.draw_map,
|
|
|
|
&self.primary.control_map,
|
|
|
|
self.primary.sim.time,
|
2018-06-21 20:03:51 +03:00
|
|
|
&self.cs,
|
2018-03-13 18:04:21 +03:00
|
|
|
g,
|
|
|
|
);
|
2018-10-08 22:55:42 +03:00
|
|
|
self.primary.debug_objects.draw(
|
|
|
|
&self.primary.map,
|
|
|
|
&self.canvas,
|
|
|
|
&self.primary.draw_map,
|
|
|
|
&self.primary.sim,
|
|
|
|
g,
|
|
|
|
);
|
2018-06-21 22:54:07 +03:00
|
|
|
self.color_picker.draw(&self.canvas, g);
|
2018-10-08 22:55:42 +03:00
|
|
|
self.primary.draw_neighborhoods.draw(g, &self.canvas);
|
|
|
|
self.primary.scenarios.draw(g, &self.canvas);
|
|
|
|
self.primary.edits_manager.draw(g, &self.canvas);
|
2018-10-08 18:28:38 +03:00
|
|
|
self.ab_test_manager.draw(g, &self.canvas);
|
2018-09-21 23:32:47 +03:00
|
|
|
self.logs.draw(g, &self.canvas);
|
2018-09-21 04:22:20 +03:00
|
|
|
self.search_state.draw(g, &self.canvas);
|
|
|
|
self.warp.draw(g, &self.canvas);
|
2018-09-28 17:28:30 +03:00
|
|
|
self.sim_ctrl.draw(g, &self.canvas);
|
2018-10-09 23:49:51 +03:00
|
|
|
self.primary.show_route.draw(g, &self.cs);
|
2018-10-16 22:38:07 +03:00
|
|
|
self.diff_worlds.draw(g, &self.cs);
|
2018-06-21 04:30:38 +03:00
|
|
|
|
2018-09-28 17:52:36 +03:00
|
|
|
self.canvas.draw_text(g, osd, BOTTOM_LEFT);
|
2018-03-13 18:04:21 +03:00
|
|
|
}
|
2018-09-15 00:41:58 +03:00
|
|
|
|
2018-09-16 02:50:19 +03:00
|
|
|
fn color_obj(&self, id: ID) -> Option<Color> {
|
2018-10-08 22:55:42 +03:00
|
|
|
if Some(id) == self.primary.current_selection {
|
2018-09-16 02:50:19 +03:00
|
|
|
return Some(self.cs.get(Colors::Selected));
|
|
|
|
}
|
|
|
|
|
2018-10-17 21:55:31 +03:00
|
|
|
let ctx = Ctx {
|
|
|
|
cs: &self.cs,
|
|
|
|
map: &self.primary.map,
|
|
|
|
control_map: &self.primary.control_map,
|
|
|
|
canvas: &self.canvas,
|
|
|
|
sim: &self.primary.sim,
|
|
|
|
};
|
2018-09-16 02:50:19 +03:00
|
|
|
if let Some(p) = self.get_active_plugin() {
|
2018-10-17 21:55:31 +03:00
|
|
|
return p.color_for(id, ctx);
|
2018-09-16 02:50:19 +03:00
|
|
|
}
|
|
|
|
|
2018-10-17 21:55:31 +03:00
|
|
|
// TODO Ew, this is a weird ambient plugin that doesn't consume input but has an opinion on
|
|
|
|
// color.
|
|
|
|
self.primary.show_owner.color_for(id, ctx)
|
2018-09-16 02:50:19 +03:00
|
|
|
}
|
|
|
|
|
2018-09-15 00:41:58 +03:00
|
|
|
fn get_active_plugin(&self) -> Option<Box<&Colorizer>> {
|
|
|
|
let idx = self.active_plugin?;
|
|
|
|
// Match instead of array, because can't move the Box out of the temporary vec. :\
|
|
|
|
// This must line up with the list of plugins in UIWrapper::new.
|
|
|
|
match idx {
|
2018-10-09 02:27:24 +03:00
|
|
|
0 => Some(Box::new(&self.layers)),
|
2018-10-08 22:55:42 +03:00
|
|
|
1 => Some(Box::new(&self.primary.traffic_signal_editor)),
|
|
|
|
2 => Some(Box::new(&self.primary.stop_sign_editor)),
|
|
|
|
3 => Some(Box::new(&self.primary.road_editor)),
|
2018-09-15 00:41:58 +03:00
|
|
|
4 => Some(Box::new(&self.search_state)),
|
|
|
|
5 => Some(Box::new(&self.warp)),
|
2018-10-08 22:55:42 +03:00
|
|
|
6 => Some(Box::new(&self.primary.follow)),
|
|
|
|
7 => Some(Box::new(&self.primary.show_route)),
|
2018-09-15 00:41:58 +03:00
|
|
|
8 => Some(Box::new(&self.color_picker)),
|
2018-10-08 22:55:42 +03:00
|
|
|
9 => Some(Box::new(&self.primary.steepness_viz)),
|
2018-09-15 00:41:58 +03:00
|
|
|
10 => Some(Box::new(&self.osm_classifier)),
|
2018-10-08 22:55:42 +03:00
|
|
|
11 => Some(Box::new(&self.primary.hider)),
|
|
|
|
12 => Some(Box::new(&self.primary.debug_objects)),
|
|
|
|
13 => Some(Box::new(&self.primary.floodfiller)),
|
|
|
|
14 => Some(Box::new(&self.primary.geom_validator)),
|
|
|
|
15 => Some(Box::new(&self.primary.turn_cycler)),
|
|
|
|
16 => Some(Box::new(&self.primary.draw_neighborhoods)),
|
|
|
|
17 => Some(Box::new(&self.primary.scenarios)),
|
|
|
|
18 => Some(Box::new(&self.primary.edits_manager)),
|
2018-10-13 21:34:00 +03:00
|
|
|
19 => Some(Box::new(&self.primary.chokepoints)),
|
|
|
|
20 => Some(Box::new(&self.ab_test_manager)),
|
|
|
|
21 => Some(Box::new(&self.logs)),
|
2018-10-16 22:38:07 +03:00
|
|
|
22 => Some(Box::new(&self.diff_worlds)),
|
2018-10-17 21:55:31 +03:00
|
|
|
23 => Some(Box::new(&self.primary.show_owner)),
|
2018-09-15 00:41:58 +03:00
|
|
|
_ => panic!("Active plugin {} is too high", idx),
|
|
|
|
}
|
|
|
|
}
|
2018-03-13 18:04:21 +03:00
|
|
|
}
|
2018-06-22 21:01:44 +03:00
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
pub struct EditorState {
|
2018-09-09 23:21:53 +03:00
|
|
|
pub map_name: String,
|
2018-06-22 21:01:44 +03:00
|
|
|
pub cam_x: f64,
|
|
|
|
pub cam_y: f64,
|
|
|
|
pub cam_zoom: f64,
|
|
|
|
}
|
2018-09-14 22:50:28 +03:00
|
|
|
|
2018-09-14 22:58:12 +03:00
|
|
|
pub trait ShowTurnIcons {
|
|
|
|
fn show_icons_for(&self, id: IntersectionID) -> bool;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ShowTurnIcons for UI {
|
|
|
|
fn show_icons_for(&self, id: IntersectionID) -> bool {
|
|
|
|
self.layers.show_all_turn_icons.is_enabled()
|
2018-10-08 22:55:42 +03:00
|
|
|
|| self.primary.stop_sign_editor.show_turn_icons(id)
|
|
|
|
|| self.primary.traffic_signal_editor.show_turn_icons(id)
|
2018-09-14 22:58:12 +03:00
|
|
|
}
|
|
|
|
}
|
2018-10-03 17:47:59 +03:00
|
|
|
|
|
|
|
struct PluginCtx<'a> {
|
|
|
|
ui: &'a mut UI,
|
|
|
|
input: &'a mut UserInput,
|
|
|
|
osd: &'a mut Text,
|
|
|
|
}
|