2019-02-09 22:50:39 +03:00
|
|
|
use crate::objects::{DrawCtx, RenderingHints, ID};
|
2019-02-17 01:26:29 +03:00
|
|
|
use crate::render::{
|
|
|
|
draw_vehicle, AgentCache, DrawPedestrian, RenderOptions, Renderable, MIN_ZOOM_FOR_DETAIL,
|
|
|
|
};
|
2019-02-02 23:49:17 +03:00
|
|
|
use crate::state::UIState;
|
2019-02-15 00:01:26 +03:00
|
|
|
use abstutil;
|
2018-12-17 05:03:46 +03:00
|
|
|
use ezgui::{
|
2019-02-02 09:41:32 +03:00
|
|
|
Canvas, Color, EventCtx, EventLoopMode, Folder, GfxCtx, Key, ModalMenu, Prerender, Text,
|
|
|
|
TopMenu, BOTTOM_LEFT, GUI,
|
2018-12-17 05:03:46 +03:00
|
|
|
};
|
2019-03-28 06:54:04 +03:00
|
|
|
use geom::{Bounds, Circle, Distance, Polygon};
|
2019-04-24 21:16:50 +03:00
|
|
|
use map_model::{BuildingID, IntersectionID, LaneID, Traversable};
|
2018-12-06 22:05:11 +03:00
|
|
|
use serde_derive::{Deserialize, Serialize};
|
2019-04-26 01:23:45 +03:00
|
|
|
use sim::GetDrawAgents;
|
2019-04-24 21:16:50 +03:00
|
|
|
use std::collections::{HashMap, HashSet};
|
2018-03-13 18:04:21 +03:00
|
|
|
|
2019-04-22 03:30:04 +03:00
|
|
|
// TODO Collapse stuff!
|
|
|
|
pub struct UI {
|
2019-04-24 01:51:05 +03:00
|
|
|
pub hints: RenderingHints,
|
2019-04-22 03:30:04 +03:00
|
|
|
pub state: UIState,
|
2018-09-13 08:43:58 +03:00
|
|
|
}
|
2018-03-13 18:04:21 +03:00
|
|
|
|
2019-04-22 03:30:04 +03:00
|
|
|
impl GUI for UI {
|
2019-02-01 03:43:09 +03:00
|
|
|
fn top_menu(&self, canvas: &Canvas) -> Option<TopMenu> {
|
2018-12-25 06:31:04 +03:00
|
|
|
let mut folders = Vec::new();
|
|
|
|
folders.push(Folder::new(
|
|
|
|
"File",
|
2019-04-22 00:21:22 +03:00
|
|
|
vec![
|
|
|
|
(Some(Key::L), "show legend"),
|
|
|
|
(Some(Key::Escape), "pause game"),
|
|
|
|
],
|
2018-12-25 06:31:04 +03:00
|
|
|
));
|
2019-04-22 03:30:04 +03:00
|
|
|
if self.state.enable_debug_controls {
|
2018-12-25 06:31:04 +03:00
|
|
|
folders.push(Folder::new(
|
|
|
|
"Debug",
|
|
|
|
vec![
|
2019-02-03 02:41:35 +03:00
|
|
|
(None, "screenshot everything"),
|
2019-04-15 19:50:00 +03:00
|
|
|
(Some(Key::F1), "screenshot just this"),
|
2019-02-03 02:41:35 +03:00
|
|
|
(Some(Key::Num1), "show/hide buildings"),
|
|
|
|
(Some(Key::Num2), "show/hide intersections"),
|
|
|
|
(Some(Key::Num3), "show/hide lanes"),
|
|
|
|
(Some(Key::Num5), "show/hide areas"),
|
|
|
|
(Some(Key::Num7), "show/hide extra shapes"),
|
|
|
|
(Some(Key::Num9), "show/hide all turn icons"),
|
|
|
|
(None, "show/hide geometry debug mode"),
|
2018-12-25 06:31:04 +03:00
|
|
|
],
|
|
|
|
));
|
|
|
|
}
|
|
|
|
folders.extend(vec![
|
|
|
|
Folder::new(
|
|
|
|
"Edit",
|
|
|
|
vec![
|
2019-02-03 02:41:35 +03:00
|
|
|
(Some(Key::B), "manage A/B tests"),
|
|
|
|
(None, "configure colors"),
|
|
|
|
(Some(Key::N), "manage neighborhoods"),
|
|
|
|
(Some(Key::W), "manage scenarios"),
|
2018-12-25 06:31:04 +03:00
|
|
|
],
|
|
|
|
),
|
2019-04-26 01:23:45 +03:00
|
|
|
Folder::new("Simulation", vec![(Some(Key::D), "diff all A/B trips")]),
|
2018-12-25 06:31:04 +03:00
|
|
|
Folder::new(
|
|
|
|
"View",
|
|
|
|
vec![
|
2019-02-19 22:55:08 +03:00
|
|
|
(None, "show neighborhood summaries"),
|
2019-02-03 02:41:35 +03:00
|
|
|
(Some(Key::Slash), "search for something"),
|
|
|
|
(Some(Key::J), "warp to an object"),
|
2018-12-25 06:31:04 +03:00
|
|
|
],
|
|
|
|
),
|
|
|
|
]);
|
2019-02-01 03:43:09 +03:00
|
|
|
Some(TopMenu::new(folders, canvas))
|
2018-12-17 05:03:46 +03:00
|
|
|
}
|
|
|
|
|
2019-04-21 04:21:07 +03:00
|
|
|
fn modal_menus(&self) -> Vec<ModalMenu> {
|
2018-12-17 23:33:44 +03:00
|
|
|
vec![
|
|
|
|
ModalMenu::new(
|
|
|
|
"Scenario Editor",
|
2019-04-03 08:39:09 +03:00
|
|
|
vec![
|
|
|
|
(Key::S, "save"),
|
|
|
|
(Key::E, "edit"),
|
|
|
|
(Key::I, "instantiate"),
|
|
|
|
(Key::V, "visualize"),
|
|
|
|
(Key::Enter, "quit"),
|
|
|
|
],
|
2018-12-17 23:33:44 +03:00
|
|
|
),
|
|
|
|
ModalMenu::new(
|
|
|
|
"Color Picker",
|
2018-12-18 03:10:09 +03:00
|
|
|
vec![(Key::Backspace, "revert"), (Key::Enter, "finalize")],
|
2018-12-17 23:33:44 +03:00
|
|
|
),
|
|
|
|
ModalMenu::new("A/B Test Editor", vec![(Key::R, "run A/B test")]),
|
|
|
|
ModalMenu::new(
|
|
|
|
"Neighborhood Editor",
|
|
|
|
vec![
|
2018-12-18 03:48:55 +03:00
|
|
|
(Key::Enter, "save"),
|
2019-04-22 22:39:09 +03:00
|
|
|
(Key::Q, "quit"),
|
2018-12-17 23:33:44 +03:00
|
|
|
(Key::X, "export as an Osmosis polygon filter"),
|
|
|
|
(Key::P, "add a new point"),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
ModalMenu::new("A/B Trip Explorer", vec![(Key::Enter, "quit")]),
|
|
|
|
ModalMenu::new("A/B All Trips Explorer", vec![(Key::Enter, "quit")]),
|
|
|
|
ModalMenu::new("Search", vec![(Key::Enter, "quit")]),
|
2019-02-19 22:55:08 +03:00
|
|
|
ModalMenu::new("Neighborhood Summaries", vec![(Key::Enter, "quit")]),
|
2018-12-27 04:16:43 +03:00
|
|
|
// TODO F1?
|
|
|
|
ModalMenu::new("Legend", vec![(Key::L, "quit")]),
|
2019-04-24 02:18:34 +03:00
|
|
|
// The new exciting things!
|
|
|
|
ModalMenu::new(
|
|
|
|
"Map Edit Mode",
|
|
|
|
vec![
|
|
|
|
(Key::Escape, "quit"),
|
|
|
|
(Key::S, "save edits"),
|
|
|
|
(Key::L, "load different edits"),
|
|
|
|
],
|
|
|
|
),
|
2019-04-24 21:16:50 +03:00
|
|
|
ModalMenu::new(
|
|
|
|
"Stop Sign Editor",
|
2019-04-25 19:41:19 +03:00
|
|
|
vec![(Key::Escape, "quit"), (Key::R, "reset to default")],
|
2019-04-24 21:16:50 +03:00
|
|
|
),
|
2019-04-24 22:36:57 +03:00
|
|
|
ModalMenu::new(
|
|
|
|
"Traffic Signal Editor",
|
|
|
|
vec![
|
|
|
|
(Key::Enter, "quit"),
|
|
|
|
(Key::D, "change cycle duration"),
|
|
|
|
(Key::P, "choose a preset signal"),
|
|
|
|
(Key::K, "move current cycle up"),
|
|
|
|
(Key::J, "move current cycle down"),
|
|
|
|
(Key::UpArrow, "select previous cycle"),
|
|
|
|
(Key::DownArrow, "select next cycle"),
|
|
|
|
(Key::Backspace, "delete current cycle"),
|
|
|
|
(Key::N, "add a new empty cycle"),
|
|
|
|
(Key::M, "add a new pedestrian scramble cycle"),
|
|
|
|
],
|
|
|
|
),
|
2019-04-25 19:41:19 +03:00
|
|
|
ModalMenu::new(
|
|
|
|
"Sandbox Mode",
|
|
|
|
vec![
|
|
|
|
(Key::Escape, "quit"),
|
|
|
|
(Key::LeftBracket, "slow down sim"),
|
|
|
|
(Key::RightBracket, "speed up sim"),
|
|
|
|
(Key::O, "save sim state"),
|
|
|
|
(Key::Y, "load previous sim state"),
|
|
|
|
(Key::U, "load next sim state"),
|
|
|
|
(Key::Space, "run/pause sim"),
|
|
|
|
(Key::M, "run one step of sim"),
|
2019-04-26 00:39:18 +03:00
|
|
|
(Key::X, "reset sim"),
|
2019-04-25 22:25:56 +03:00
|
|
|
(Key::S, "seed the sim with agents"),
|
2019-04-26 00:31:57 +03:00
|
|
|
// TODO Strange to always have this. Really it's a case of stacked modal?
|
|
|
|
(Key::F, "stop following agent"),
|
2019-04-26 00:39:18 +03:00
|
|
|
(Key::R, "stop showing agent's route"),
|
|
|
|
// TODO This should probably be a debug thing instead
|
|
|
|
(Key::L, "show/hide route for all agents"),
|
2019-04-26 00:59:26 +03:00
|
|
|
(Key::A, "show/hide active traffic"),
|
2019-04-26 01:14:35 +03:00
|
|
|
(Key::T, "start time traveling"),
|
2019-04-25 19:41:19 +03:00
|
|
|
],
|
|
|
|
),
|
2019-04-25 22:25:56 +03:00
|
|
|
ModalMenu::new("Agent Spawner", vec![(Key::Escape, "quit")]),
|
2019-04-26 01:14:35 +03:00
|
|
|
ModalMenu::new(
|
|
|
|
"Time Traveler",
|
|
|
|
vec![
|
|
|
|
(Key::Escape, "quit"),
|
|
|
|
(Key::Comma, "rewind"),
|
|
|
|
(Key::Dot, "forwards"),
|
|
|
|
],
|
|
|
|
),
|
2019-04-26 02:02:40 +03:00
|
|
|
ModalMenu::new(
|
|
|
|
"Debug Mode",
|
2019-04-26 02:16:21 +03:00
|
|
|
vec![
|
|
|
|
(Key::Escape, "quit"),
|
|
|
|
(Key::C, "show/hide chokepoints"),
|
|
|
|
(Key::O, "clear original roads shown"),
|
2019-04-26 08:07:48 +03:00
|
|
|
(Key::K, "unhide everything"),
|
2019-04-26 02:16:21 +03:00
|
|
|
],
|
2019-04-26 02:02:40 +03:00
|
|
|
),
|
2019-04-26 02:24:10 +03:00
|
|
|
ModalMenu::new(
|
|
|
|
"Polygon Debugger",
|
|
|
|
vec![
|
|
|
|
(Key::Escape, "quit"),
|
|
|
|
(Key::Dot, "next item"),
|
|
|
|
(Key::Comma, "prev item"),
|
|
|
|
(Key::F, "first item"),
|
|
|
|
(Key::L, "last item"),
|
|
|
|
],
|
|
|
|
),
|
2018-12-17 23:33:44 +03:00
|
|
|
]
|
|
|
|
}
|
|
|
|
|
2019-04-22 00:21:22 +03:00
|
|
|
// TODO This hacky wrapper will soon disappear, when UI stops implementing GUI
|
2019-04-24 07:23:47 +03:00
|
|
|
fn event(&mut self, ctx: &mut EventCtx) -> EventLoopMode {
|
2019-04-22 00:21:22 +03:00
|
|
|
self.new_event(ctx).0
|
2018-09-13 08:43:58 +03:00
|
|
|
}
|
2018-09-13 03:39:53 +03:00
|
|
|
|
2019-04-24 01:33:52 +03:00
|
|
|
fn draw(&self, g: &mut GfxCtx) {
|
2019-04-26 02:56:38 +03:00
|
|
|
self.new_draw(
|
|
|
|
g,
|
|
|
|
None,
|
|
|
|
HashMap::new(),
|
|
|
|
&self.state.primary.sim,
|
|
|
|
&ShowEverything {},
|
|
|
|
)
|
2018-10-22 05:23:47 +03:00
|
|
|
}
|
2018-12-13 01:01:36 +03:00
|
|
|
|
2019-02-01 03:43:09 +03:00
|
|
|
fn dump_before_abort(&self, canvas: &Canvas) {
|
2019-02-20 22:24:21 +03:00
|
|
|
println!(
|
|
|
|
"********************************************************************************"
|
|
|
|
);
|
|
|
|
println!("UI broke! Primary sim:");
|
2019-04-22 03:30:04 +03:00
|
|
|
self.state.primary.sim.dump_before_abort();
|
|
|
|
if let Some((s, _)) = &self.state.secondary {
|
2019-02-20 22:24:21 +03:00
|
|
|
println!("Secondary sim:");
|
2018-12-25 05:58:01 +03:00
|
|
|
s.sim.dump_before_abort();
|
|
|
|
}
|
|
|
|
|
2019-02-01 03:43:09 +03:00
|
|
|
self.save_editor_state(canvas);
|
2018-12-13 01:01:36 +03:00
|
|
|
}
|
2018-12-17 22:32:52 +03:00
|
|
|
|
2019-02-01 03:43:09 +03:00
|
|
|
fn before_quit(&self, canvas: &Canvas) {
|
|
|
|
self.save_editor_state(canvas);
|
2019-04-22 03:30:04 +03:00
|
|
|
self.state.cs.save();
|
2019-02-20 22:24:21 +03:00
|
|
|
println!("Saved color_scheme");
|
2019-02-15 00:01:26 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
fn profiling_enabled(&self) -> bool {
|
2019-04-22 03:30:04 +03:00
|
|
|
self.state.primary.current_flags.enable_profiler
|
2018-12-17 22:32:52 +03:00
|
|
|
}
|
2018-10-22 05:23:47 +03:00
|
|
|
}
|
|
|
|
|
2019-04-22 03:30:04 +03:00
|
|
|
impl UI {
|
|
|
|
pub fn new(state: UIState, canvas: &mut Canvas) -> UI {
|
2018-12-25 06:14:45 +03:00
|
|
|
match abstutil::read_json::<EditorState>("../editor_state") {
|
2019-04-22 03:30:04 +03:00
|
|
|
Ok(ref loaded) if state.primary.map.get_name() == &loaded.map_name => {
|
2019-02-20 22:24:21 +03:00
|
|
|
println!("Loaded previous editor_state");
|
2019-02-01 03:43:09 +03:00
|
|
|
canvas.cam_x = loaded.cam_x;
|
|
|
|
canvas.cam_y = loaded.cam_y;
|
|
|
|
canvas.cam_zoom = loaded.cam_zoom;
|
2018-10-22 05:23:47 +03:00
|
|
|
}
|
|
|
|
_ => {
|
2019-02-20 22:24:21 +03:00
|
|
|
println!("Couldn't load editor_state or it's for a different map, so just focusing on an arbitrary building");
|
2018-12-05 22:45:07 +03:00
|
|
|
let focus_pt = ID::Building(BuildingID(0))
|
2018-12-13 22:35:29 +03:00
|
|
|
.canonical_point(
|
2019-04-22 03:30:04 +03:00
|
|
|
&state.primary.map,
|
|
|
|
&state.primary.sim,
|
|
|
|
&state.primary.draw_map,
|
2018-12-13 22:35:29 +03:00
|
|
|
)
|
2018-12-05 22:45:07 +03:00
|
|
|
.or_else(|| {
|
|
|
|
ID::Lane(LaneID(0)).canonical_point(
|
2019-04-22 03:30:04 +03:00
|
|
|
&state.primary.map,
|
|
|
|
&state.primary.sim,
|
|
|
|
&state.primary.draw_map,
|
2018-12-05 22:45:07 +03:00
|
|
|
)
|
2018-12-06 21:05:42 +03:00
|
|
|
})
|
|
|
|
.expect("Can't get canonical_point of BuildingID(0) or Road(0)");
|
2019-02-01 03:43:09 +03:00
|
|
|
canvas.center_on_map_pt(focus_pt);
|
2018-10-22 05:23:47 +03:00
|
|
|
}
|
2018-10-08 22:55:42 +03:00
|
|
|
}
|
2018-10-22 05:22:16 +03:00
|
|
|
|
2019-04-21 23:07:00 +03:00
|
|
|
UI {
|
|
|
|
state,
|
|
|
|
hints: RenderingHints {
|
|
|
|
mode: EventLoopMode::InputOnly,
|
|
|
|
osd: Text::new(),
|
|
|
|
suppress_traffic_signal_details: None,
|
|
|
|
hide_turn_icons: HashSet::new(),
|
|
|
|
},
|
|
|
|
}
|
2018-10-22 05:23:47 +03:00
|
|
|
}
|
2018-10-21 22:18:25 +03:00
|
|
|
|
2019-04-22 00:21:22 +03:00
|
|
|
// True if we should pause.
|
2019-04-24 07:23:47 +03:00
|
|
|
pub fn new_event(&mut self, ctx: &mut EventCtx) -> (EventLoopMode, bool) {
|
2019-04-22 00:21:22 +03:00
|
|
|
self.hints = RenderingHints {
|
|
|
|
mode: EventLoopMode::InputOnly,
|
|
|
|
osd: Text::new(),
|
|
|
|
suppress_traffic_signal_details: None,
|
|
|
|
hide_turn_icons: HashSet::new(),
|
|
|
|
};
|
|
|
|
|
|
|
|
// First update the camera
|
|
|
|
ctx.canvas.handle_event(ctx.input);
|
|
|
|
|
|
|
|
// Always handle mouseover
|
2019-04-26 01:23:45 +03:00
|
|
|
self.state.primary.current_selection =
|
2019-04-26 02:56:38 +03:00
|
|
|
self.handle_mouseover(ctx, None, &self.state.primary.sim, &ShowEverything {});
|
2019-04-22 00:21:22 +03:00
|
|
|
|
|
|
|
let mut recalculate_current_selection = false;
|
2019-04-24 07:23:47 +03:00
|
|
|
self.state
|
|
|
|
.event(ctx, &mut self.hints, &mut recalculate_current_selection);
|
2019-04-22 00:21:22 +03:00
|
|
|
if recalculate_current_selection {
|
2019-04-26 01:23:45 +03:00
|
|
|
self.state.primary.current_selection =
|
2019-04-26 02:56:38 +03:00
|
|
|
self.mouseover_something(&ctx, None, &self.state.primary.sim, &ShowEverything {});
|
2019-04-22 00:21:22 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
ctx.input.populate_osd(&mut self.hints.osd);
|
|
|
|
|
|
|
|
// TODO a plugin should do this, even though it's such a tiny thing
|
2019-04-22 03:30:04 +03:00
|
|
|
if self.state.enable_debug_controls {
|
2019-04-22 00:21:22 +03:00
|
|
|
if ctx.input.action_chosen("screenshot everything") {
|
2019-04-22 03:30:04 +03:00
|
|
|
let bounds = self.state.primary.map.get_bounds();
|
2019-04-22 00:21:22 +03:00
|
|
|
assert!(bounds.min_x == 0.0 && bounds.min_y == 0.0);
|
|
|
|
self.hints.mode = EventLoopMode::ScreenCaptureEverything {
|
|
|
|
dir: format!(
|
|
|
|
"../data/screenshots/pending_{}",
|
2019-04-22 03:30:04 +03:00
|
|
|
self.state.primary.map.get_name()
|
2019-04-22 00:21:22 +03:00
|
|
|
),
|
|
|
|
zoom: 3.0,
|
|
|
|
max_x: bounds.max_x,
|
|
|
|
max_y: bounds.max_y,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
if ctx.input.action_chosen("screenshot just this") {
|
|
|
|
self.hints.mode = EventLoopMode::ScreenCaptureCurrentShot;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
(
|
|
|
|
self.hints.mode.clone(),
|
|
|
|
ctx.input.action_chosen("pause game"),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-04-24 21:16:50 +03:00
|
|
|
pub fn new_draw(
|
|
|
|
&self,
|
|
|
|
g: &mut GfxCtx,
|
|
|
|
show_turn_icons_for: Option<IntersectionID>,
|
|
|
|
override_color: HashMap<ID, Color>,
|
2019-04-26 01:23:45 +03:00
|
|
|
source: &GetDrawAgents,
|
2019-04-26 02:56:38 +03:00
|
|
|
show_objs: &ShowObject,
|
2019-04-24 21:16:50 +03:00
|
|
|
) {
|
|
|
|
let ctx = DrawCtx {
|
|
|
|
cs: &self.state.cs,
|
|
|
|
map: &self.state.primary.map,
|
|
|
|
draw_map: &self.state.primary.draw_map,
|
|
|
|
sim: &self.state.primary.sim,
|
|
|
|
hints: &self.hints,
|
|
|
|
};
|
|
|
|
let mut sample_intersection: Option<String> = None;
|
|
|
|
|
|
|
|
g.clear(self.state.cs.get_def("true background", Color::BLACK));
|
|
|
|
g.redraw(&self.state.primary.draw_map.boundary_polygon);
|
|
|
|
|
|
|
|
if g.canvas.cam_zoom < MIN_ZOOM_FOR_DETAIL && !g.is_screencap() {
|
|
|
|
// Unzoomed mode
|
|
|
|
if self.state.layers.show_areas {
|
|
|
|
g.redraw(&self.state.primary.draw_map.draw_all_areas);
|
|
|
|
}
|
|
|
|
if self.state.layers.show_lanes {
|
|
|
|
g.redraw(&self.state.primary.draw_map.draw_all_thick_roads);
|
|
|
|
}
|
|
|
|
if self.state.layers.show_intersections {
|
|
|
|
g.redraw(&self.state.primary.draw_map.draw_all_unzoomed_intersections);
|
|
|
|
}
|
|
|
|
if self.state.layers.show_buildings {
|
|
|
|
g.redraw(&self.state.primary.draw_map.draw_all_buildings);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Still show area selection when zoomed out.
|
|
|
|
if self.state.primary.current_flags.debug_areas {
|
|
|
|
if let Some(ID::Area(id)) = self.state.primary.current_selection {
|
|
|
|
g.draw_polygon(
|
|
|
|
self.state.cs.get("selected"),
|
|
|
|
&fill_to_boundary_polygon(ctx.draw_map.get_a(id).get_outline(&ctx.map)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.state
|
|
|
|
.primary
|
|
|
|
.sim
|
|
|
|
.draw_unzoomed(g, &self.state.primary.map);
|
|
|
|
} else {
|
|
|
|
let mut cache = self.state.primary.draw_map.agents.borrow_mut();
|
|
|
|
let objects = self.get_renderables_back_to_front(
|
|
|
|
g.get_screen_bounds(),
|
|
|
|
&g.prerender,
|
|
|
|
&mut cache,
|
|
|
|
show_turn_icons_for,
|
2019-04-26 01:23:45 +03:00
|
|
|
source,
|
2019-04-26 02:56:38 +03:00
|
|
|
show_objs,
|
2019-04-24 21:16:50 +03:00
|
|
|
);
|
|
|
|
|
|
|
|
let mut drawn_all_buildings = false;
|
|
|
|
let mut drawn_all_areas = false;
|
|
|
|
|
|
|
|
for obj in objects {
|
|
|
|
match obj.get_id() {
|
|
|
|
ID::Building(_) => {
|
|
|
|
if !drawn_all_buildings {
|
|
|
|
g.redraw(&self.state.primary.draw_map.draw_all_buildings);
|
|
|
|
drawn_all_buildings = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ID::Area(_) => {
|
|
|
|
if !drawn_all_areas {
|
|
|
|
g.redraw(&self.state.primary.draw_map.draw_all_areas);
|
|
|
|
drawn_all_areas = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
};
|
|
|
|
let opts = RenderOptions {
|
|
|
|
color: override_color
|
|
|
|
.get(&obj.get_id())
|
|
|
|
.cloned()
|
|
|
|
.or_else(|| self.state.color_obj(obj.get_id(), &ctx)),
|
|
|
|
debug_mode: self.state.layers.debug_mode,
|
|
|
|
};
|
|
|
|
obj.draw(g, opts, &ctx);
|
|
|
|
|
|
|
|
if self.state.primary.current_selection == Some(obj.get_id()) {
|
|
|
|
g.draw_polygon(
|
|
|
|
self.state.cs.get_def("selected", Color::YELLOW.alpha(0.4)),
|
|
|
|
&fill_to_boundary_polygon(obj.get_outline(&ctx.map)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if g.is_screencap() && sample_intersection.is_none() {
|
|
|
|
if let ID::Intersection(id) = obj.get_id() {
|
|
|
|
sample_intersection = Some(format!("_i{}", id.0));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !g.is_screencap() {
|
|
|
|
self.state.draw(g, &ctx);
|
|
|
|
|
|
|
|
// Not happy about cloning, but probably will make the OSD a first-class ezgui concept
|
|
|
|
// soon, so meh
|
|
|
|
let mut osd = self.hints.osd.clone();
|
|
|
|
// TODO Only in some kind of debug mode
|
|
|
|
osd.add_line(format!(
|
|
|
|
"{} things uploaded, {} things drawn",
|
|
|
|
abstutil::prettyprint_usize(g.get_num_uploads()),
|
|
|
|
abstutil::prettyprint_usize(g.num_draw_calls),
|
|
|
|
));
|
|
|
|
g.draw_blocking_text(&osd, BOTTOM_LEFT);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(i) = sample_intersection {
|
|
|
|
g.set_screencap_naming_hint(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-26 01:23:45 +03:00
|
|
|
// Because we have to sometimes borrow part of self for GetDrawAgents, this just returns the
|
|
|
|
// Option<ID> that the caller should assign. When this monolithic UI nonsense is dismantled,
|
|
|
|
// this weirdness goes away.
|
2019-04-24 21:16:50 +03:00
|
|
|
pub fn handle_mouseover(
|
2019-04-26 01:23:45 +03:00
|
|
|
&self,
|
2019-04-24 21:16:50 +03:00
|
|
|
ctx: &mut EventCtx,
|
|
|
|
show_turn_icons_for: Option<IntersectionID>,
|
2019-04-26 01:23:45 +03:00
|
|
|
source: &GetDrawAgents,
|
2019-04-26 02:56:38 +03:00
|
|
|
show_objs: &ShowObject,
|
2019-04-26 01:23:45 +03:00
|
|
|
) -> Option<ID> {
|
2019-04-24 03:26:10 +03:00
|
|
|
if !ctx.canvas.is_dragging() && ctx.input.get_moved_mouse().is_some() {
|
2019-04-26 02:56:38 +03:00
|
|
|
return self.mouseover_something(&ctx, show_turn_icons_for, source, show_objs);
|
2019-04-24 03:26:10 +03:00
|
|
|
}
|
|
|
|
if ctx.input.window_lost_cursor() {
|
2019-04-26 01:23:45 +03:00
|
|
|
return None;
|
2019-04-24 03:26:10 +03:00
|
|
|
}
|
2019-04-26 01:23:45 +03:00
|
|
|
self.state.primary.current_selection
|
2019-04-24 03:26:10 +03:00
|
|
|
}
|
|
|
|
|
2019-04-24 21:16:50 +03:00
|
|
|
fn mouseover_something(
|
|
|
|
&self,
|
|
|
|
ctx: &EventCtx,
|
|
|
|
show_turn_icons_for: Option<IntersectionID>,
|
2019-04-26 01:23:45 +03:00
|
|
|
source: &GetDrawAgents,
|
2019-04-26 02:56:38 +03:00
|
|
|
show_objs: &ShowObject,
|
2019-04-24 21:16:50 +03:00
|
|
|
) -> Option<ID> {
|
2019-02-19 02:21:43 +03:00
|
|
|
// Unzoomed mode. Ignore when debugging areas.
|
|
|
|
if ctx.canvas.cam_zoom < MIN_ZOOM_FOR_DETAIL
|
2019-04-22 03:30:04 +03:00
|
|
|
&& !self.state.primary.current_flags.debug_areas
|
2019-02-19 02:21:43 +03:00
|
|
|
{
|
2019-02-15 04:00:27 +03:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
2019-02-01 03:43:09 +03:00
|
|
|
let pt = ctx.canvas.get_cursor_in_map_space()?;
|
2019-01-22 01:04:07 +03:00
|
|
|
|
2019-04-22 03:30:04 +03:00
|
|
|
let mut cache = self.state.primary.draw_map.agents.borrow_mut();
|
2019-02-02 23:35:50 +03:00
|
|
|
let mut objects = self.get_renderables_back_to_front(
|
2019-02-01 03:36:45 +03:00
|
|
|
Circle::new(pt, Distance::meters(3.0)).get_bounds(),
|
2019-02-02 09:41:32 +03:00
|
|
|
ctx.prerender,
|
2019-02-02 23:35:50 +03:00
|
|
|
&mut cache,
|
2019-04-24 21:16:50 +03:00
|
|
|
show_turn_icons_for,
|
2019-04-26 01:23:45 +03:00
|
|
|
source,
|
2019-04-26 02:56:38 +03:00
|
|
|
show_objs,
|
2019-02-01 03:36:45 +03:00
|
|
|
);
|
2019-02-02 23:35:50 +03:00
|
|
|
objects.reverse();
|
2019-02-02 09:41:32 +03:00
|
|
|
|
2019-04-22 03:30:04 +03:00
|
|
|
let debug_areas = self.state.primary.current_flags.debug_areas;
|
2019-02-02 23:35:50 +03:00
|
|
|
for obj in objects {
|
2019-04-11 21:10:40 +03:00
|
|
|
// Don't mouseover areas.
|
2019-02-02 09:41:32 +03:00
|
|
|
// TODO Might get fancier rules in the future, so we can't mouseover irrelevant things
|
|
|
|
// in intersection editor mode, for example.
|
|
|
|
match obj.get_id() {
|
2019-02-15 05:42:24 +03:00
|
|
|
ID::Area(_) if !debug_areas => {}
|
2019-02-15 04:00:27 +03:00
|
|
|
// Thick roads are only shown when unzoomed, when we don't mouseover at all.
|
|
|
|
ID::Road(_) => {}
|
2019-02-02 09:41:32 +03:00
|
|
|
_ => {
|
2019-04-22 03:30:04 +03:00
|
|
|
if obj.get_outline(&self.state.primary.map).contains_pt(pt) {
|
2019-02-02 09:41:32 +03:00
|
|
|
return Some(obj.get_id());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
None
|
2018-12-25 05:58:01 +03:00
|
|
|
}
|
|
|
|
|
2019-02-01 03:43:09 +03:00
|
|
|
fn save_editor_state(&self, canvas: &Canvas) {
|
2018-11-02 00:00:14 +03:00
|
|
|
let state = EditorState {
|
2019-04-22 03:30:04 +03:00
|
|
|
map_name: self.state.primary.map.get_name().clone(),
|
2019-02-01 03:43:09 +03:00
|
|
|
cam_x: canvas.cam_x,
|
|
|
|
cam_y: canvas.cam_y,
|
|
|
|
cam_zoom: canvas.cam_zoom,
|
2018-11-02 00:00:14 +03:00
|
|
|
};
|
|
|
|
// TODO maybe make state line up with the map, so loading from a new map doesn't break
|
2018-12-25 06:14:45 +03:00
|
|
|
abstutil::write_json("../editor_state", &state).expect("Saving editor_state failed");
|
2019-02-20 22:24:21 +03:00
|
|
|
println!("Saved editor_state");
|
2018-11-02 00:00:14 +03:00
|
|
|
}
|
2019-02-02 09:41:32 +03:00
|
|
|
|
2019-02-02 23:49:17 +03:00
|
|
|
// TODO This could probably belong to DrawMap again, but it's annoying to plumb things that
|
|
|
|
// State does, like show_icons_for() and show().
|
2019-02-02 23:35:50 +03:00
|
|
|
fn get_renderables_back_to_front<'a>(
|
|
|
|
&'a self,
|
2019-02-02 09:41:32 +03:00
|
|
|
bounds: Bounds,
|
|
|
|
prerender: &Prerender,
|
2019-02-02 23:35:50 +03:00
|
|
|
agents: &'a mut AgentCache,
|
2019-04-24 21:16:50 +03:00
|
|
|
show_turn_icons_for: Option<IntersectionID>,
|
2019-04-26 01:23:45 +03:00
|
|
|
source: &GetDrawAgents,
|
2019-04-26 02:56:38 +03:00
|
|
|
show_objs: &ShowObject,
|
2019-02-02 23:35:50 +03:00
|
|
|
) -> Vec<Box<&'a Renderable>> {
|
2019-04-22 03:30:04 +03:00
|
|
|
let map = &self.state.primary.map;
|
|
|
|
let draw_map = &self.state.primary.draw_map;
|
2019-02-02 09:41:32 +03:00
|
|
|
|
2019-02-10 06:28:26 +03:00
|
|
|
let mut areas: Vec<Box<&Renderable>> = Vec::new();
|
2019-02-02 09:41:32 +03:00
|
|
|
let mut lanes: Vec<Box<&Renderable>> = Vec::new();
|
2019-02-12 19:26:23 +03:00
|
|
|
let mut roads: Vec<Box<&Renderable>> = Vec::new();
|
2019-02-02 09:41:32 +03:00
|
|
|
let mut intersections: Vec<Box<&Renderable>> = Vec::new();
|
|
|
|
let mut buildings: Vec<Box<&Renderable>> = Vec::new();
|
|
|
|
let mut extra_shapes: Vec<Box<&Renderable>> = Vec::new();
|
|
|
|
let mut bus_stops: Vec<Box<&Renderable>> = Vec::new();
|
|
|
|
let mut turn_icons: Vec<Box<&Renderable>> = Vec::new();
|
|
|
|
let mut agents_on: Vec<Traversable> = Vec::new();
|
|
|
|
|
|
|
|
for id in draw_map.get_matching_objects(bounds) {
|
2019-04-26 02:56:38 +03:00
|
|
|
if !show_objs.show(id) {
|
2019-02-02 09:41:32 +03:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
match id {
|
2019-02-10 06:28:26 +03:00
|
|
|
ID::Area(id) => areas.push(Box::new(draw_map.get_a(id))),
|
2019-02-02 09:41:32 +03:00
|
|
|
ID::Lane(id) => {
|
2019-02-15 04:00:27 +03:00
|
|
|
lanes.push(Box::new(draw_map.get_l(id)));
|
|
|
|
let lane = map.get_l(id);
|
2019-04-26 02:56:38 +03:00
|
|
|
if show_turn_icons_for == Some(lane.dst_i) {
|
2019-04-01 05:19:36 +03:00
|
|
|
for (t, _) in map.get_next_turns_and_lanes(id, lane.dst_i) {
|
|
|
|
turn_icons.push(Box::new(draw_map.get_t(t.id)));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// TODO Bug: pedestrians on front paths aren't selectable.
|
2019-02-15 04:00:27 +03:00
|
|
|
agents_on.push(Traversable::Lane(id));
|
|
|
|
}
|
|
|
|
for bs in &lane.bus_stops {
|
|
|
|
bus_stops.push(Box::new(draw_map.get_bs(*bs)));
|
2019-02-02 09:41:32 +03:00
|
|
|
}
|
|
|
|
}
|
2019-02-12 19:26:23 +03:00
|
|
|
ID::Road(id) => {
|
|
|
|
roads.push(Box::new(draw_map.get_r(id)));
|
|
|
|
}
|
2019-02-02 09:41:32 +03:00
|
|
|
ID::Intersection(id) => {
|
|
|
|
intersections.push(Box::new(draw_map.get_i(id)));
|
|
|
|
for t in &map.get_i(id).turns {
|
2019-04-26 02:56:38 +03:00
|
|
|
if show_turn_icons_for != Some(id) {
|
2019-02-15 04:00:27 +03:00
|
|
|
agents_on.push(Traversable::Turn(*t));
|
2019-02-02 09:41:32 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// TODO front paths will get drawn over buildings, depending on quadtree order.
|
|
|
|
// probably just need to make them go around other buildings instead of having
|
|
|
|
// two passes through buildings.
|
|
|
|
ID::Building(id) => buildings.push(Box::new(draw_map.get_b(id))),
|
|
|
|
ID::ExtraShape(id) => extra_shapes.push(Box::new(draw_map.get_es(id))),
|
|
|
|
|
2019-02-13 21:21:12 +03:00
|
|
|
ID::BusStop(_) | ID::Turn(_) | ID::Car(_) | ID::Pedestrian(_) | ID::Trip(_) => {
|
2019-02-02 09:41:32 +03:00
|
|
|
panic!("{:?} shouldn't be in the quadtree", id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// From background to foreground Z-order
|
|
|
|
let mut borrows: Vec<Box<&Renderable>> = Vec::new();
|
2019-02-10 06:28:26 +03:00
|
|
|
borrows.extend(areas);
|
2019-02-02 09:41:32 +03:00
|
|
|
borrows.extend(lanes);
|
2019-02-12 19:26:23 +03:00
|
|
|
borrows.extend(roads);
|
2019-02-02 09:41:32 +03:00
|
|
|
borrows.extend(intersections);
|
|
|
|
borrows.extend(buildings);
|
|
|
|
borrows.extend(extra_shapes);
|
|
|
|
borrows.extend(bus_stops);
|
|
|
|
borrows.extend(turn_icons);
|
|
|
|
|
2019-02-02 23:35:50 +03:00
|
|
|
// Expand all of the Traversables into agents, populating the cache if needed.
|
2019-02-02 09:41:32 +03:00
|
|
|
{
|
2019-02-26 23:50:43 +03:00
|
|
|
let time = source.time();
|
2019-02-02 09:41:32 +03:00
|
|
|
|
|
|
|
for on in &agents_on {
|
2019-02-26 23:50:43 +03:00
|
|
|
if !agents.has(time, *on) {
|
2019-02-02 09:41:32 +03:00
|
|
|
let mut list: Vec<Box<Renderable>> = Vec::new();
|
2019-02-10 00:07:00 +03:00
|
|
|
for c in source.get_draw_cars(*on, map).into_iter() {
|
2019-04-22 03:30:04 +03:00
|
|
|
list.push(draw_vehicle(c, map, prerender, &self.state.cs));
|
2019-02-02 09:41:32 +03:00
|
|
|
}
|
2019-02-10 00:07:00 +03:00
|
|
|
for p in source.get_draw_peds(*on, map).into_iter() {
|
2019-04-22 03:30:04 +03:00
|
|
|
list.push(Box::new(DrawPedestrian::new(
|
|
|
|
p,
|
|
|
|
map,
|
|
|
|
prerender,
|
|
|
|
&self.state.cs,
|
|
|
|
)));
|
2019-02-02 09:41:32 +03:00
|
|
|
}
|
2019-02-26 23:50:43 +03:00
|
|
|
agents.put(time, *on, list);
|
2019-02-02 09:41:32 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-02 23:35:50 +03:00
|
|
|
for on in agents_on {
|
|
|
|
for obj in agents.get(on) {
|
|
|
|
borrows.push(obj);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is a stable sort.
|
|
|
|
borrows.sort_by_key(|r| r.get_zorder());
|
|
|
|
|
|
|
|
borrows
|
2019-02-02 09:41:32 +03:00
|
|
|
}
|
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,
|
|
|
|
}
|
2019-03-28 06:54:04 +03:00
|
|
|
|
|
|
|
fn fill_to_boundary_polygon(poly: Polygon) -> Polygon {
|
|
|
|
// TODO This looks awful for lanes, oops.
|
|
|
|
//PolyLine::make_polygons_for_boundary(poly.points().clone(), Distance::meters(0.5))
|
|
|
|
poly
|
|
|
|
}
|
2019-04-26 02:56:38 +03:00
|
|
|
|
|
|
|
pub trait ShowObject {
|
|
|
|
fn show(&self, obj: ID) -> bool;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct ShowEverything {}
|
|
|
|
|
|
|
|
impl ShowObject for ShowEverything {
|
|
|
|
fn show(&self, _: ID) -> bool {
|
|
|
|
true
|
|
|
|
}
|
|
|
|
}
|