2018-12-06 21:18:20 +03:00
|
|
|
use crate::colors::ColorScheme;
|
2018-06-22 21:01:44 +03:00
|
|
|
use abstutil;
|
2018-10-18 02:57:15 +03:00
|
|
|
//use cpuprofiler;
|
2018-12-06 21:18:20 +03:00
|
|
|
use crate::objects::{Ctx, RenderingHints, ID, ROOT_MENU};
|
2018-12-14 02:22:37 +03:00
|
|
|
use crate::render::RenderOptions;
|
|
|
|
use crate::state::UIState;
|
2018-12-16 05:27:43 +03:00
|
|
|
use ezgui::{Canvas, Color, EventLoopMode, GfxCtx, Key, Text, UserInput, BOTTOM_LEFT, GUI};
|
2018-08-10 23:28:34 +03:00
|
|
|
use kml;
|
2018-12-14 02:22:37 +03:00
|
|
|
use map_model::{BuildingID, LaneID};
|
2018-12-06 22:05:11 +03:00
|
|
|
use serde_derive::{Deserialize, Serialize};
|
2018-12-12 23:01:11 +03:00
|
|
|
use std::borrow::Borrow;
|
2018-12-05 21:39:17 +03:00
|
|
|
use std::collections::{HashMap, HashSet};
|
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-12-14 01:26:12 +03:00
|
|
|
pub struct UI<S: UIState> {
|
|
|
|
state: S,
|
2018-10-22 05:23:47 +03:00
|
|
|
canvas: Canvas,
|
2018-12-13 04:08:15 +03:00
|
|
|
cs: ColorScheme,
|
2018-09-13 08:43:58 +03:00
|
|
|
}
|
2018-03-13 18:04:21 +03:00
|
|
|
|
2018-12-14 01:26:12 +03:00
|
|
|
impl<S: UIState> GUI<RenderingHints> for UI<S> {
|
2018-12-15 02:10:33 +03:00
|
|
|
fn event(&mut self, input: &mut UserInput) -> (EventLoopMode, RenderingHints) {
|
2018-12-13 01:01:36 +03:00
|
|
|
let mut hints = RenderingHints {
|
|
|
|
mode: EventLoopMode::InputOnly,
|
|
|
|
osd: Text::new(),
|
|
|
|
suppress_intersection_icon: None,
|
|
|
|
color_crosswalks: HashMap::new(),
|
|
|
|
hide_crosswalks: HashSet::new(),
|
|
|
|
hide_turn_icons: HashSet::new(),
|
|
|
|
};
|
|
|
|
|
|
|
|
// First update the camera and handle zoom
|
|
|
|
let old_zoom = self.canvas.cam_zoom;
|
2018-12-15 02:10:33 +03:00
|
|
|
self.canvas.handle_event(input);
|
2018-12-13 01:01:36 +03:00
|
|
|
let new_zoom = self.canvas.cam_zoom;
|
2018-12-13 22:35:29 +03:00
|
|
|
self.state.handle_zoom(old_zoom, new_zoom);
|
2018-12-13 01:01:36 +03:00
|
|
|
|
|
|
|
// Always handle mouseover
|
|
|
|
if old_zoom >= MIN_ZOOM_FOR_MOUSEOVER && new_zoom < MIN_ZOOM_FOR_MOUSEOVER {
|
2018-12-13 22:35:29 +03:00
|
|
|
self.state.set_current_selection(None);
|
2018-12-13 01:01:36 +03:00
|
|
|
}
|
|
|
|
if !self.canvas.is_dragging()
|
|
|
|
&& input.get_moved_mouse().is_some()
|
|
|
|
&& new_zoom >= MIN_ZOOM_FOR_MOUSEOVER
|
|
|
|
{
|
2018-12-13 22:35:29 +03:00
|
|
|
self.state.set_current_selection(self.mouseover_something());
|
2018-12-13 01:01:36 +03:00
|
|
|
}
|
|
|
|
|
2018-12-13 22:19:10 +03:00
|
|
|
let mut recalculate_current_selection = false;
|
2018-12-13 22:35:29 +03:00
|
|
|
self.state.event(
|
2018-12-15 02:10:33 +03:00
|
|
|
input,
|
2018-12-13 22:35:29 +03:00
|
|
|
&mut hints,
|
|
|
|
&mut recalculate_current_selection,
|
|
|
|
&mut self.cs,
|
|
|
|
&mut self.canvas,
|
|
|
|
);
|
|
|
|
if recalculate_current_selection {
|
|
|
|
self.state.set_current_selection(self.mouseover_something());
|
2018-10-22 05:23:47 +03:00
|
|
|
}
|
2018-12-13 01:01:36 +03:00
|
|
|
|
|
|
|
// Can do this at any time.
|
|
|
|
if input.unimportant_key_pressed(Key::Escape, ROOT_MENU, "quit") {
|
|
|
|
self.save_editor_state();
|
2018-12-13 04:08:15 +03:00
|
|
|
self.cs.save();
|
2018-12-13 01:01:36 +03:00
|
|
|
info!("Saved color_scheme");
|
|
|
|
//cpuprofiler::PROFILER.lock().unwrap().stop().unwrap();
|
|
|
|
process::exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
input.populate_osd(&mut hints.osd);
|
|
|
|
|
|
|
|
(hints.mode, hints)
|
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 {
|
2018-10-22 05:23:47 +03:00
|
|
|
&mut self.canvas
|
2018-10-06 21:50:55 +03:00
|
|
|
}
|
|
|
|
|
2018-12-15 23:22:00 +03:00
|
|
|
fn draw(&self, g: &mut GfxCtx, hints: &RenderingHints) {
|
2018-12-13 04:08:15 +03:00
|
|
|
g.clear(self.cs.get_def("map background", Color::rgb(242, 239, 233)));
|
2018-10-22 05:23:47 +03:00
|
|
|
|
2018-12-13 04:08:15 +03:00
|
|
|
let ctx = Ctx {
|
|
|
|
cs: &self.cs,
|
2018-12-13 22:35:29 +03:00
|
|
|
map: &self.state.primary().map,
|
|
|
|
draw_map: &self.state.primary().draw_map,
|
2018-12-08 00:27:13 +03:00
|
|
|
canvas: &self.canvas,
|
2018-12-13 22:35:29 +03:00
|
|
|
sim: &self.state.primary().sim,
|
2018-12-08 00:27:13 +03:00
|
|
|
hints: &hints,
|
|
|
|
};
|
|
|
|
|
2018-12-13 22:35:29 +03:00
|
|
|
let (statics, dynamics) = self.state.get_objects_onscreen(&self.canvas);
|
2018-12-12 23:01:11 +03:00
|
|
|
for obj in statics
|
|
|
|
.into_iter()
|
|
|
|
.chain(dynamics.iter().map(|obj| Box::new(obj.borrow())))
|
|
|
|
{
|
2018-10-22 05:23:47 +03:00
|
|
|
let opts = RenderOptions {
|
2018-12-13 04:08:15 +03:00
|
|
|
color: self.color_obj(obj.get_id(), &ctx),
|
2018-10-22 05:23:47 +03:00
|
|
|
cam_zoom: self.canvas.cam_zoom,
|
2018-12-13 22:35:29 +03:00
|
|
|
debug_mode: self.state.is_debug_mode_enabled(),
|
2018-10-22 05:23:47 +03:00
|
|
|
};
|
2018-12-13 04:08:15 +03:00
|
|
|
obj.draw(g, opts, &ctx);
|
2018-10-22 05:23:47 +03:00
|
|
|
}
|
|
|
|
|
2018-12-13 22:35:29 +03:00
|
|
|
self.state.draw(g, &ctx);
|
2018-11-06 04:48:36 +03:00
|
|
|
|
2018-12-15 23:22:00 +03:00
|
|
|
// Not happy about cloning, but probably will make the OSD a first-class ezgui concept
|
|
|
|
// soon, so meh
|
|
|
|
self.canvas.draw_text(g, hints.osd.clone(), BOTTOM_LEFT);
|
2018-10-22 05:23:47 +03:00
|
|
|
}
|
2018-12-13 01:01:36 +03:00
|
|
|
|
|
|
|
fn dump_before_abort(&self) {
|
2018-12-13 22:35:29 +03:00
|
|
|
self.state.dump_before_abort();
|
2018-12-13 01:01:36 +03:00
|
|
|
self.save_editor_state();
|
|
|
|
}
|
2018-10-22 05:23:47 +03:00
|
|
|
}
|
|
|
|
|
2018-12-14 01:26:12 +03:00
|
|
|
impl<S: UIState> UI<S> {
|
2018-12-14 01:34:21 +03:00
|
|
|
pub fn new(state: S, canvas: Canvas) -> UI<S> {
|
2018-12-13 22:35:29 +03:00
|
|
|
let mut ui = UI {
|
|
|
|
state,
|
2018-12-08 01:16:40 +03:00
|
|
|
canvas,
|
2018-12-13 04:08:15 +03:00
|
|
|
cs: ColorScheme::load().unwrap(),
|
2018-10-08 22:55:42 +03:00
|
|
|
};
|
|
|
|
|
2018-10-22 05:23:47 +03:00
|
|
|
match abstutil::read_json::<EditorState>("editor_state") {
|
2018-12-13 22:35:29 +03:00
|
|
|
Ok(ref state) if ui.state.primary().map.get_name() == &state.map_name => {
|
2018-10-22 05:23:47 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
_ => {
|
2018-11-26 22:37:19 +03:00
|
|
|
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
|
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(
|
|
|
|
&ui.state.primary().map,
|
|
|
|
&ui.state.primary().sim,
|
|
|
|
&ui.state.primary().draw_map,
|
|
|
|
)
|
2018-12-05 22:45:07 +03:00
|
|
|
.or_else(|| {
|
|
|
|
ID::Lane(LaneID(0)).canonical_point(
|
2018-12-13 22:35:29 +03:00
|
|
|
&ui.state.primary().map,
|
|
|
|
&ui.state.primary().sim,
|
|
|
|
&ui.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)");
|
2018-12-05 22:45:07 +03:00
|
|
|
ui.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
|
|
|
|
2018-10-22 05:23:47 +03:00
|
|
|
ui
|
|
|
|
}
|
2018-10-21 22:18:25 +03:00
|
|
|
|
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-12-13 22:35:29 +03:00
|
|
|
let (statics, dynamics) = self.state.get_objects_onscreen(&self.canvas);
|
2018-09-14 22:16:32 +03:00
|
|
|
// Check front-to-back
|
2018-12-12 23:01:11 +03:00
|
|
|
for obj in dynamics
|
|
|
|
.iter()
|
|
|
|
.map(|obj| Box::new(obj.borrow()))
|
|
|
|
.chain(statics.into_iter().rev())
|
|
|
|
{
|
2018-09-14 22:16:32 +03:00
|
|
|
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-12-13 04:08:15 +03:00
|
|
|
fn color_obj(&self, id: ID, ctx: &Ctx) -> Option<Color> {
|
2018-12-13 22:35:29 +03:00
|
|
|
self.state.color_obj(id, ctx)
|
2018-10-22 06:18:15 +03:00
|
|
|
}
|
2018-11-02 00:00:14 +03:00
|
|
|
|
|
|
|
fn save_editor_state(&self) {
|
|
|
|
let state = EditorState {
|
2018-12-13 22:35:29 +03:00
|
|
|
map_name: self.state.primary().map.get_name().clone(),
|
2018-11-02 00:00:14 +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
|
|
|
|
abstutil::write_json("editor_state", &state).expect("Saving editor_state failed");
|
|
|
|
info!("Saved editor_state");
|
|
|
|
}
|
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,
|
|
|
|
}
|