mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-25 03:41:09 +03:00
grouping UI state that's bound to a certain map+edits
This commit is contained in:
parent
af2d49debb
commit
2aa47bbd51
@ -278,6 +278,16 @@ Alright, I think this is the sequence of things to do:
|
|||||||
2) make it possible to completely reload UI and everything from scratch, from a plugin. rationale: it'd be nice to switch maps from inside the editor anyway. not necessary, but useful.
|
2) make it possible to completely reload UI and everything from scratch, from a plugin. rationale: it'd be nice to switch maps from inside the editor anyway. not necessary, but useful.
|
||||||
3) make road edits propogate correctly, and somehow have a strategy for ensuring nothing is forgotten. impl today is VERY incomplete.
|
3) make road edits propogate correctly, and somehow have a strategy for ensuring nothing is forgotten. impl today is VERY incomplete.
|
||||||
|
|
||||||
|
Thinking about this again now that we need two copies of everything to be alive at a time and switch between them...
|
||||||
|
|
||||||
|
- very tied together: map, control map, draw map, sim
|
||||||
|
- current selection is UI state that has to refresh when changing maps
|
||||||
|
- which plugins have state tied to the map?
|
||||||
|
- have a method on UI to switch map+edits? no, dont want to reload all this stuff every time...
|
||||||
|
- bundle that state together, including the plugins!
|
||||||
|
- make the existing 'load new edits' thing use this new mechanism
|
||||||
|
- then go back to managing a second sim...
|
||||||
|
|
||||||
## Rendering a map differently
|
## Rendering a map differently
|
||||||
|
|
||||||
For "Project Halloween", I want to draw the map model in a very different
|
For "Project Halloween", I want to draw the map model in a very different
|
||||||
@ -298,3 +308,48 @@ So, try adding the quadtree for roads and buildings (diff quadtrees or one
|
|||||||
unified? hmm) and see what looks common. Remember we could use quadtrees in map
|
unified? hmm) and see what looks common. Remember we could use quadtrees in map
|
||||||
model construction for building/sidewalk pruning, but there's the awkwardness
|
model construction for building/sidewalk pruning, but there's the awkwardness
|
||||||
of quadtrees _kind of_ being a UI concept.
|
of quadtrees _kind of_ being a UI concept.
|
||||||
|
|
||||||
|
## Side-by-side
|
||||||
|
|
||||||
|
What should this feature do? Is it easier to watch two maps side-by-side moving
|
||||||
|
in lockstep, with the same camera? Or would a ghostly trace overlay on one map
|
||||||
|
be easier to understand? The use cases:
|
||||||
|
|
||||||
|
- Glancing at a vague overview of how the two runs are doing. Watching graphs
|
||||||
|
side-by-side might even be more useful here. But for a zoomed out view,
|
||||||
|
side-by-side with reasonably clear pockets of color (weather model style,
|
||||||
|
almost) seems nice.
|
||||||
|
- Detailed inspection of a fixed area. Side-by-side view with full detail seems
|
||||||
|
nice.
|
||||||
|
- Detailed inspection of a specific agent, following it. Side-by-side would
|
||||||
|
have to trace it in both canvases.
|
||||||
|
- Looking for differences... what are these? For a single agent, wanting to
|
||||||
|
know are they farther along their journey at some point in time? That could
|
||||||
|
be visualized nicely with a red or green thick route in front or behind them.
|
||||||
|
Maybe they're ahead of the baseline by some amount, or behind it. This could
|
||||||
|
use relative score or relative distance to goal or something. Would it trace
|
||||||
|
ahead by pure distance or by anticipated distance in a given time?
|
||||||
|
|
||||||
|
The side-by-side canvas seems incredibly difficult -- have to revamp everything
|
||||||
|
to dispatch mouse events, maybe synchronize cameras, other plugins
|
||||||
|
arbitrarily... No way.
|
||||||
|
|
||||||
|
Let's start with two concrete things:
|
||||||
|
|
||||||
|
- Start running an A/B test sets up a second optional simulation in the UI.
|
||||||
|
Some keys can toggle between showing one of the two, for now. Stepping will
|
||||||
|
step BOTH of them. Have to adjust the OSD and other sim control things.
|
||||||
|
Possibly even sim control should own the second sim?
|
||||||
|
- Q: how to prevent scenario instatiation or adding new agents while an
|
||||||
|
A/B test is in progress? need plugin groups / modes!
|
||||||
|
- road editing during an A/B test is _definitely_ not k
|
||||||
|
- argh!! wait, we need a different map, since we need different edits!
|
||||||
|
- that means also a different control map
|
||||||
|
- should the Sim own map and control_map to make it clear they're tied? I think so!
|
||||||
|
- practically all of the UI touches the map...
|
||||||
|
- wait wait draw map also needs to be duplicated then.
|
||||||
|
- we're back to the problem of loading map edits
|
||||||
|
- Make a visual trace abstraction to show something in front of or behind an
|
||||||
|
agent. It follows bends in the road, crosses intersections, etc. Could be
|
||||||
|
used for lookahead debugging right now, and this relative ghost comparison
|
||||||
|
thing next.
|
||||||
|
@ -8,7 +8,7 @@ use objects::{Ctx, ID};
|
|||||||
use piston::input::Key;
|
use piston::input::Key;
|
||||||
use plugins::Colorizer;
|
use plugins::Colorizer;
|
||||||
use render::DrawMap;
|
use render::DrawMap;
|
||||||
use sim::Sim;
|
use sim::Tick;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum TurnCyclerState {
|
pub enum TurnCyclerState {
|
||||||
@ -68,7 +68,7 @@ impl TurnCyclerState {
|
|||||||
map: &Map,
|
map: &Map,
|
||||||
draw_map: &DrawMap,
|
draw_map: &DrawMap,
|
||||||
control_map: &ControlMap,
|
control_map: &ControlMap,
|
||||||
sim: &Sim,
|
time: Tick,
|
||||||
cs: &ColorScheme,
|
cs: &ColorScheme,
|
||||||
g: &mut GfxCtx,
|
g: &mut GfxCtx,
|
||||||
) {
|
) {
|
||||||
@ -92,7 +92,7 @@ impl TurnCyclerState {
|
|||||||
}
|
}
|
||||||
TurnCyclerState::Intersection(id) => {
|
TurnCyclerState::Intersection(id) => {
|
||||||
if let Some(signal) = control_map.traffic_signals.get(&id) {
|
if let Some(signal) = control_map.traffic_signals.get(&id) {
|
||||||
let (cycle, _) = signal.current_cycle_and_remaining_time(sim.time.as_time());
|
let (cycle, _) = signal.current_cycle_and_remaining_time(time.as_time());
|
||||||
for t in &cycle.turns {
|
for t in &cycle.turns {
|
||||||
draw_map.get_t(*t).draw_full(g, cs.get(Colors::Turn));
|
draw_map.get_t(*t).draw_full(g, cs.get(Colors::Turn));
|
||||||
}
|
}
|
||||||
|
@ -35,16 +35,13 @@ pub struct DrawMap {
|
|||||||
pub bus_stops: HashMap<BusStopID, DrawBusStop>,
|
pub bus_stops: HashMap<BusStopID, DrawBusStop>,
|
||||||
pub areas: Vec<DrawArea>,
|
pub areas: Vec<DrawArea>,
|
||||||
|
|
||||||
|
pub center_pt: Pt2D,
|
||||||
|
|
||||||
quadtree: QuadTree<ID>,
|
quadtree: QuadTree<ID>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DrawMap {
|
impl DrawMap {
|
||||||
// Also returns the center of the map in map-space
|
pub fn new(map: &Map, control_map: &ControlMap, raw_extra_shapes: Vec<ExtraShape>) -> DrawMap {
|
||||||
pub fn new(
|
|
||||||
map: &Map,
|
|
||||||
control_map: &ControlMap,
|
|
||||||
raw_extra_shapes: Vec<ExtraShape>,
|
|
||||||
) -> (DrawMap, Pt2D) {
|
|
||||||
let mut lanes: Vec<DrawLane> = Vec::new();
|
let mut lanes: Vec<DrawLane> = Vec::new();
|
||||||
for l in map.all_lanes() {
|
for l in map.all_lanes() {
|
||||||
lanes.push(DrawLane::new(l, map, control_map));
|
lanes.push(DrawLane::new(l, map, control_map));
|
||||||
@ -120,21 +117,20 @@ impl DrawMap {
|
|||||||
quadtree.insert_with_box(obj.get_id(), get_bbox(obj.get_bounds()));
|
quadtree.insert_with_box(obj.get_id(), get_bbox(obj.get_bounds()));
|
||||||
}
|
}
|
||||||
|
|
||||||
(
|
DrawMap {
|
||||||
DrawMap {
|
lanes,
|
||||||
lanes,
|
intersections,
|
||||||
intersections,
|
turns,
|
||||||
turns,
|
buildings,
|
||||||
buildings,
|
parcels,
|
||||||
parcels,
|
extra_shapes,
|
||||||
extra_shapes,
|
bus_stops,
|
||||||
bus_stops,
|
areas,
|
||||||
areas,
|
|
||||||
|
|
||||||
quadtree,
|
center_pt: Pt2D::new(max_screen_pt.x() / 2.0, max_screen_pt.y() / 2.0),
|
||||||
},
|
|
||||||
Pt2D::new(max_screen_pt.x() / 2.0, max_screen_pt.y() / 2.0),
|
quadtree,
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_turn_to_lane_offset(result: &mut HashMap<TurnID, usize>, l: &Lane, map: &Map) {
|
fn compute_turn_to_lane_offset(result: &mut HashMap<TurnID, usize>, l: &Lane, map: &Map) {
|
||||||
|
357
editor/src/ui.rs
357
editor/src/ui.rs
@ -10,8 +10,7 @@ use ezgui::{
|
|||||||
};
|
};
|
||||||
use flame;
|
use flame;
|
||||||
use kml;
|
use kml;
|
||||||
use map_model;
|
use map_model::{IntersectionID, Map};
|
||||||
use map_model::IntersectionID;
|
|
||||||
use objects::{Ctx, DEBUG_LAYERS, ID, ROOT_MENU};
|
use objects::{Ctx, DEBUG_LAYERS, ID, ROOT_MENU};
|
||||||
use piston::input::Key;
|
use piston::input::Key;
|
||||||
use plugins::a_b_tests::ABTestManager;
|
use plugins::a_b_tests::ABTestManager;
|
||||||
@ -82,55 +81,16 @@ impl UIWrapper {
|
|||||||
// Do this first, so anything logged by sim::load isn't lost.
|
// Do this first, so anything logged by sim::load isn't lost.
|
||||||
let logs = DisplayLogs::new();
|
let logs = DisplayLogs::new();
|
||||||
|
|
||||||
flame::start("setup");
|
|
||||||
let (map, control_map, sim) = sim::load(flags.clone(), Some(sim::Tick::from_seconds(30)));
|
|
||||||
let extra_shapes = if let Some(path) = kml.clone() {
|
|
||||||
kml::load(&path, &map.get_gps_bounds()).expect("Couldn't load extra KML shapes")
|
|
||||||
} else {
|
|
||||||
Vec::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
flame::start("draw_map");
|
|
||||||
let (draw_map, center_pt) = 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());
|
|
||||||
|
|
||||||
let mut ui = UI {
|
let mut ui = UI {
|
||||||
// TODO organize this by section
|
primary: PerMapUI::new(flags, kml.clone()),
|
||||||
map,
|
secondary: None,
|
||||||
draw_map,
|
|
||||||
control_map,
|
|
||||||
sim,
|
|
||||||
|
|
||||||
steepness_viz,
|
|
||||||
road_editor,
|
|
||||||
sim_ctrl: SimController::new(),
|
|
||||||
|
|
||||||
layers: ToggleableLayers::new(),
|
layers: ToggleableLayers::new(),
|
||||||
|
|
||||||
current_selection: None,
|
|
||||||
|
|
||||||
hider: Hider::new(),
|
|
||||||
debug_objects: DebugObjectsState::new(),
|
|
||||||
search_state: SearchState::Empty,
|
search_state: SearchState::Empty,
|
||||||
warp: WarpState::Empty,
|
warp: WarpState::Empty,
|
||||||
follow: FollowState::Empty,
|
|
||||||
show_route: ShowRouteState::Empty,
|
|
||||||
floodfiller: Floodfiller::new(),
|
|
||||||
osm_classifier: OsmClassifier::new(),
|
osm_classifier: OsmClassifier::new(),
|
||||||
traffic_signal_editor: TrafficSignalEditor::new(),
|
sim_ctrl: SimController::new(),
|
||||||
stop_sign_editor: StopSignEditor::new(),
|
|
||||||
color_picker: ColorPicker::new(),
|
color_picker: ColorPicker::new(),
|
||||||
geom_validator: Validator::new(),
|
|
||||||
turn_cycler: TurnCyclerState::new(),
|
|
||||||
draw_neighborhoods: DrawNeighborhoodState::new(),
|
|
||||||
scenarios: ScenarioManager::new(),
|
|
||||||
edits_manager: EditsManager::new(flags),
|
|
||||||
ab_test_manager: ABTestManager::new(),
|
ab_test_manager: ABTestManager::new(),
|
||||||
logs,
|
logs,
|
||||||
|
|
||||||
@ -141,7 +101,7 @@ impl UIWrapper {
|
|||||||
};
|
};
|
||||||
|
|
||||||
match abstutil::read_json::<EditorState>("editor_state") {
|
match abstutil::read_json::<EditorState>("editor_state") {
|
||||||
Ok(ref state) if *ui.map.get_name() == state.map_name => {
|
Ok(ref state) if ui.primary.map.get_name().to_string() == state.map_name => {
|
||||||
info!("Loaded previous editor_state");
|
info!("Loaded previous editor_state");
|
||||||
ui.canvas.cam_x = state.cam_x;
|
ui.canvas.cam_x = state.cam_x;
|
||||||
ui.canvas.cam_y = state.cam_y;
|
ui.canvas.cam_y = state.cam_y;
|
||||||
@ -149,7 +109,7 @@ impl UIWrapper {
|
|||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
warn!("Couldn't load editor_state or it's for a different map, so just centering initial view");
|
warn!("Couldn't load editor_state or it's for a different map, so just centering initial view");
|
||||||
ui.canvas.center_on_map_pt(center_pt);
|
ui.canvas.center_on_map_pt(ui.primary.draw_map.center_pt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,117 +133,132 @@ impl UIWrapper {
|
|||||||
changed
|
changed
|
||||||
};
|
};
|
||||||
if layer_changed {
|
if layer_changed {
|
||||||
ctx.ui.current_selection = ctx.ui.mouseover_something();
|
ctx.ui.primary.current_selection = ctx.ui.mouseover_something();
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
Box::new(|ctx| {
|
Box::new(|ctx| {
|
||||||
ctx.ui.traffic_signal_editor.event(
|
ctx.ui.primary.traffic_signal_editor.event(
|
||||||
ctx.input,
|
ctx.input,
|
||||||
&ctx.ui.map,
|
&ctx.ui.primary.map,
|
||||||
&mut ctx.ui.control_map,
|
&mut ctx.ui.primary.control_map,
|
||||||
ctx.ui.current_selection,
|
ctx.ui.primary.current_selection,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
Box::new(|ctx| {
|
Box::new(|ctx| {
|
||||||
ctx.ui.stop_sign_editor.event(
|
ctx.ui.primary.stop_sign_editor.event(
|
||||||
ctx.input,
|
ctx.input,
|
||||||
&ctx.ui.map,
|
&ctx.ui.primary.map,
|
||||||
&mut ctx.ui.control_map,
|
&mut ctx.ui.primary.control_map,
|
||||||
ctx.ui.current_selection,
|
ctx.ui.primary.current_selection,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
Box::new(|ctx| {
|
Box::new(|ctx| {
|
||||||
ctx.ui.road_editor.event(
|
ctx.ui.primary.road_editor.event(
|
||||||
ctx.input,
|
ctx.input,
|
||||||
ctx.ui.current_selection,
|
ctx.ui.primary.current_selection,
|
||||||
&mut ctx.ui.map,
|
&mut ctx.ui.primary.map,
|
||||||
&mut ctx.ui.draw_map,
|
&mut ctx.ui.primary.draw_map,
|
||||||
&ctx.ui.control_map,
|
&ctx.ui.primary.control_map,
|
||||||
&mut ctx.ui.sim,
|
&mut ctx.ui.primary.sim,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
Box::new(|ctx| ctx.ui.search_state.event(ctx.input)),
|
Box::new(|ctx| ctx.ui.search_state.event(ctx.input)),
|
||||||
Box::new(|ctx| {
|
Box::new(|ctx| {
|
||||||
ctx.ui.warp.event(
|
ctx.ui.warp.event(
|
||||||
ctx.input,
|
ctx.input,
|
||||||
&ctx.ui.map,
|
&ctx.ui.primary.map,
|
||||||
&ctx.ui.sim,
|
&ctx.ui.primary.sim,
|
||||||
&mut ctx.ui.canvas,
|
&mut ctx.ui.canvas,
|
||||||
&mut ctx.ui.current_selection,
|
&mut ctx.ui.primary.current_selection,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
Box::new(|ctx| {
|
Box::new(|ctx| {
|
||||||
ctx.ui.follow.event(
|
ctx.ui.primary.follow.event(
|
||||||
ctx.input,
|
ctx.input,
|
||||||
&ctx.ui.map,
|
&ctx.ui.primary.map,
|
||||||
&ctx.ui.sim,
|
&ctx.ui.primary.sim,
|
||||||
&mut ctx.ui.canvas,
|
&mut ctx.ui.canvas,
|
||||||
ctx.ui.current_selection,
|
ctx.ui.primary.current_selection,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
Box::new(|ctx| {
|
Box::new(|ctx| {
|
||||||
ctx.ui
|
ctx.ui.primary.show_route.event(
|
||||||
.show_route
|
ctx.input,
|
||||||
.event(ctx.input, &ctx.ui.sim, ctx.ui.current_selection)
|
&ctx.ui.primary.sim,
|
||||||
|
ctx.ui.primary.current_selection,
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
Box::new(|ctx| {
|
Box::new(|ctx| {
|
||||||
ctx.ui
|
ctx.ui
|
||||||
.color_picker
|
.color_picker
|
||||||
.event(ctx.input, &mut ctx.ui.canvas, &mut ctx.ui.cs)
|
.event(ctx.input, &mut ctx.ui.canvas, &mut ctx.ui.cs)
|
||||||
}),
|
}),
|
||||||
Box::new(|ctx| ctx.ui.steepness_viz.event(ctx.input)),
|
Box::new(|ctx| ctx.ui.primary.steepness_viz.event(ctx.input)),
|
||||||
Box::new(|ctx| ctx.ui.osm_classifier.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| {
|
Box::new(|ctx| {
|
||||||
ctx.ui.debug_objects.event(
|
ctx.ui
|
||||||
ctx.ui.current_selection,
|
.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,
|
||||||
ctx.input,
|
ctx.input,
|
||||||
&ctx.ui.map,
|
&ctx.ui.primary.map,
|
||||||
&mut ctx.ui.sim,
|
&mut ctx.ui.primary.sim,
|
||||||
&ctx.ui.control_map,
|
&ctx.ui.primary.control_map,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
Box::new(|ctx| {
|
Box::new(|ctx| {
|
||||||
ctx.ui
|
ctx.ui.primary.floodfiller.event(
|
||||||
.floodfiller
|
&ctx.ui.primary.map,
|
||||||
.event(&ctx.ui.map, ctx.input, ctx.ui.current_selection)
|
ctx.input,
|
||||||
|
ctx.ui.primary.current_selection,
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
Box::new(|ctx| {
|
Box::new(|ctx| {
|
||||||
ctx.ui.geom_validator.event(
|
ctx.ui.primary.geom_validator.event(
|
||||||
ctx.input,
|
ctx.input,
|
||||||
&mut ctx.ui.canvas,
|
&mut ctx.ui.canvas,
|
||||||
&ctx.ui.map,
|
&ctx.ui.primary.map,
|
||||||
&ctx.ui.draw_map,
|
&ctx.ui.primary.draw_map,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
Box::new(|ctx| {
|
Box::new(|ctx| {
|
||||||
ctx.ui
|
ctx.ui
|
||||||
|
.primary
|
||||||
.turn_cycler
|
.turn_cycler
|
||||||
.event(ctx.input, ctx.ui.current_selection)
|
.event(ctx.input, ctx.ui.primary.current_selection)
|
||||||
}),
|
}),
|
||||||
Box::new(|ctx| {
|
Box::new(|ctx| {
|
||||||
ctx.ui
|
ctx.ui.primary.draw_neighborhoods.event(
|
||||||
.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.input,
|
||||||
&ctx.ui.map,
|
&ctx.ui.canvas,
|
||||||
&ctx.ui.control_map,
|
&ctx.ui.primary.map,
|
||||||
&ctx.ui.road_editor,
|
ctx.osd,
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
Box::new(|ctx| {
|
||||||
|
ctx.ui.primary.scenarios.event(
|
||||||
|
ctx.input,
|
||||||
|
&ctx.ui.primary.map,
|
||||||
|
&mut ctx.ui.primary.sim,
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
Box::new(|ctx| {
|
||||||
|
ctx.ui.primary.edits_manager.event(
|
||||||
|
ctx.input,
|
||||||
|
&ctx.ui.primary.map,
|
||||||
|
&ctx.ui.primary.control_map,
|
||||||
|
&ctx.ui.primary.road_editor,
|
||||||
ctx.new_flags,
|
ctx.new_flags,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
Box::new(|ctx| ctx.ui.ab_test_manager.event(ctx.input, &ctx.ui.map)),
|
Box::new(|ctx| ctx.ui.ab_test_manager.event(ctx.input, &ctx.ui.primary.map)),
|
||||||
Box::new(|ctx| ctx.ui.logs.event(ctx.input)),
|
Box::new(|ctx| ctx.ui.logs.event(ctx.input)),
|
||||||
],
|
],
|
||||||
|
|
||||||
@ -292,35 +267,92 @@ impl UIWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UI {
|
// All of the state that's bound to a specific map+edit has to live here.
|
||||||
map: map_model::Map,
|
// TODO How can we arrange the code so that we statically know that we don't pass anything from UI
|
||||||
|
// to something in PerMapUI?
|
||||||
|
struct PerMapUI {
|
||||||
|
map: Map,
|
||||||
draw_map: DrawMap,
|
draw_map: DrawMap,
|
||||||
control_map: ControlMap,
|
control_map: ControlMap,
|
||||||
sim: Sim,
|
sim: Sim,
|
||||||
|
|
||||||
layers: ToggleableLayers,
|
|
||||||
|
|
||||||
current_selection: Option<ID>,
|
current_selection: Option<ID>,
|
||||||
|
|
||||||
|
// Anything that holds onto any kind of ID has to live here!
|
||||||
hider: Hider,
|
hider: Hider,
|
||||||
debug_objects: DebugObjectsState,
|
debug_objects: DebugObjectsState,
|
||||||
search_state: SearchState,
|
|
||||||
warp: WarpState,
|
|
||||||
follow: FollowState,
|
follow: FollowState,
|
||||||
show_route: ShowRouteState,
|
show_route: ShowRouteState,
|
||||||
floodfiller: Floodfiller,
|
floodfiller: Floodfiller,
|
||||||
steepness_viz: SteepnessVisualizer,
|
steepness_viz: SteepnessVisualizer,
|
||||||
osm_classifier: OsmClassifier,
|
|
||||||
traffic_signal_editor: TrafficSignalEditor,
|
traffic_signal_editor: TrafficSignalEditor,
|
||||||
stop_sign_editor: StopSignEditor,
|
stop_sign_editor: StopSignEditor,
|
||||||
road_editor: RoadEditor,
|
road_editor: RoadEditor,
|
||||||
sim_ctrl: SimController,
|
|
||||||
color_picker: ColorPicker,
|
|
||||||
geom_validator: Validator,
|
geom_validator: Validator,
|
||||||
turn_cycler: TurnCyclerState,
|
turn_cycler: TurnCyclerState,
|
||||||
draw_neighborhoods: DrawNeighborhoodState,
|
draw_neighborhoods: DrawNeighborhoodState,
|
||||||
scenarios: ScenarioManager,
|
scenarios: ScenarioManager,
|
||||||
edits_manager: EditsManager,
|
edits_manager: EditsManager,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PerMapUI {
|
||||||
|
fn new(flags: SimFlags, kml: Option<String>) -> PerMapUI {
|
||||||
|
flame::start("setup");
|
||||||
|
let (map, control_map, sim) = sim::load(flags.clone(), Some(sim::Tick::from_seconds(30)));
|
||||||
|
let extra_shapes = if let Some(path) = kml.clone() {
|
||||||
|
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,
|
||||||
|
|
||||||
|
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(),
|
||||||
|
draw_neighborhoods: DrawNeighborhoodState::new(),
|
||||||
|
scenarios: ScenarioManager::new(),
|
||||||
|
edits_manager: EditsManager::new(flags),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
// TODO This one has per-sim state right now, but soon will understand how to handle two sims.
|
||||||
|
sim_ctrl: SimController,
|
||||||
|
color_picker: ColorPicker,
|
||||||
ab_test_manager: ABTestManager,
|
ab_test_manager: ABTestManager,
|
||||||
logs: DisplayLogs,
|
logs: DisplayLogs,
|
||||||
|
|
||||||
@ -347,11 +379,11 @@ impl UI {
|
|||||||
fn mouseover_something(&self) -> Option<ID> {
|
fn mouseover_something(&self) -> Option<ID> {
|
||||||
let pt = self.canvas.get_cursor_in_map_space();
|
let pt = self.canvas.get_cursor_in_map_space();
|
||||||
|
|
||||||
let (statics, dynamics) = self.draw_map.get_objects_onscreen(
|
let (statics, dynamics) = self.primary.draw_map.get_objects_onscreen(
|
||||||
self.canvas.get_screen_bbox(),
|
self.canvas.get_screen_bbox(),
|
||||||
&self.hider,
|
&self.primary.hider,
|
||||||
&self.map,
|
&self.primary.map,
|
||||||
&self.sim,
|
&self.primary.sim,
|
||||||
&self.layers,
|
&self.layers,
|
||||||
self,
|
self,
|
||||||
);
|
);
|
||||||
@ -387,13 +419,13 @@ impl UI {
|
|||||||
|
|
||||||
// Always handle mouseover
|
// Always handle mouseover
|
||||||
if old_zoom >= MIN_ZOOM_FOR_MOUSEOVER && new_zoom < MIN_ZOOM_FOR_MOUSEOVER {
|
if old_zoom >= MIN_ZOOM_FOR_MOUSEOVER && new_zoom < MIN_ZOOM_FOR_MOUSEOVER {
|
||||||
self.current_selection = None;
|
self.primary.current_selection = None;
|
||||||
}
|
}
|
||||||
if !self.canvas.is_dragging()
|
if !self.canvas.is_dragging()
|
||||||
&& input.get_moved_mouse().is_some()
|
&& input.get_moved_mouse().is_some()
|
||||||
&& new_zoom >= MIN_ZOOM_FOR_MOUSEOVER
|
&& new_zoom >= MIN_ZOOM_FOR_MOUSEOVER
|
||||||
{
|
{
|
||||||
self.current_selection = self.mouseover_something();
|
self.primary.current_selection = self.mouseover_something();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Normally we'd return InputOnly here if there was an active plugin, but actually, we
|
// TODO Normally we'd return InputOnly here if there was an active plugin, but actually, we
|
||||||
@ -426,7 +458,7 @@ impl UI {
|
|||||||
|
|
||||||
if input.unimportant_key_pressed(Key::Escape, ROOT_MENU, "quit") {
|
if input.unimportant_key_pressed(Key::Escape, ROOT_MENU, "quit") {
|
||||||
let state = EditorState {
|
let state = EditorState {
|
||||||
map_name: self.map.get_name().clone(),
|
map_name: self.primary.map.get_name().clone(),
|
||||||
cam_x: self.canvas.cam_x,
|
cam_x: self.canvas.cam_x,
|
||||||
cam_y: self.canvas.cam_y,
|
cam_y: self.canvas.cam_y,
|
||||||
cam_zoom: self.canvas.cam_zoom,
|
cam_zoom: self.canvas.cam_zoom,
|
||||||
@ -441,10 +473,10 @@ impl UI {
|
|||||||
// Sim controller plugin is kind of always active? If nothing else ran, let it use keys.
|
// Sim controller plugin is kind of always active? If nothing else ran, let it use keys.
|
||||||
let result = self.sim_ctrl.event(
|
let result = self.sim_ctrl.event(
|
||||||
&mut input,
|
&mut input,
|
||||||
&self.map,
|
&self.primary.map,
|
||||||
&self.control_map,
|
&self.primary.control_map,
|
||||||
&mut self.sim,
|
&mut self.primary.sim,
|
||||||
self.current_selection,
|
self.primary.current_selection,
|
||||||
osd,
|
osd,
|
||||||
);
|
);
|
||||||
input.populate_osd(osd);
|
input.populate_osd(osd);
|
||||||
@ -454,11 +486,11 @@ impl UI {
|
|||||||
fn draw(&self, g: &mut GfxCtx, osd: Text) {
|
fn draw(&self, g: &mut GfxCtx, osd: Text) {
|
||||||
g.clear(self.cs.get(Colors::Background));
|
g.clear(self.cs.get(Colors::Background));
|
||||||
|
|
||||||
let (statics, dynamics) = self.draw_map.get_objects_onscreen(
|
let (statics, dynamics) = self.primary.draw_map.get_objects_onscreen(
|
||||||
self.canvas.get_screen_bbox(),
|
self.canvas.get_screen_bbox(),
|
||||||
&self.hider,
|
&self.primary.hider,
|
||||||
&self.map,
|
&self.primary.map,
|
||||||
&self.sim,
|
&self.primary.sim,
|
||||||
&self.layers,
|
&self.layers,
|
||||||
self,
|
self,
|
||||||
);
|
);
|
||||||
@ -473,10 +505,10 @@ impl UI {
|
|||||||
opts,
|
opts,
|
||||||
Ctx {
|
Ctx {
|
||||||
cs: &self.cs,
|
cs: &self.cs,
|
||||||
map: &self.map,
|
map: &self.primary.map,
|
||||||
control_map: &self.control_map,
|
control_map: &self.primary.control_map,
|
||||||
canvas: &self.canvas,
|
canvas: &self.canvas,
|
||||||
sim: &self.sim,
|
sim: &self.primary.sim,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -491,29 +523,34 @@ impl UI {
|
|||||||
opts,
|
opts,
|
||||||
Ctx {
|
Ctx {
|
||||||
cs: &self.cs,
|
cs: &self.cs,
|
||||||
map: &self.map,
|
map: &self.primary.map,
|
||||||
control_map: &self.control_map,
|
control_map: &self.primary.control_map,
|
||||||
canvas: &self.canvas,
|
canvas: &self.canvas,
|
||||||
sim: &self.sim,
|
sim: &self.primary.sim,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Only if active?
|
// TODO Only if active?
|
||||||
self.turn_cycler.draw(
|
self.primary.turn_cycler.draw(
|
||||||
&self.map,
|
&self.primary.map,
|
||||||
&self.draw_map,
|
&self.primary.draw_map,
|
||||||
&self.control_map,
|
&self.primary.control_map,
|
||||||
&self.sim,
|
self.primary.sim.time,
|
||||||
&self.cs,
|
&self.cs,
|
||||||
g,
|
g,
|
||||||
);
|
);
|
||||||
self.debug_objects
|
self.primary.debug_objects.draw(
|
||||||
.draw(&self.map, &self.canvas, &self.draw_map, &self.sim, g);
|
&self.primary.map,
|
||||||
|
&self.canvas,
|
||||||
|
&self.primary.draw_map,
|
||||||
|
&self.primary.sim,
|
||||||
|
g,
|
||||||
|
);
|
||||||
self.color_picker.draw(&self.canvas, g);
|
self.color_picker.draw(&self.canvas, g);
|
||||||
self.draw_neighborhoods.draw(g, &self.canvas);
|
self.primary.draw_neighborhoods.draw(g, &self.canvas);
|
||||||
self.scenarios.draw(g, &self.canvas);
|
self.primary.scenarios.draw(g, &self.canvas);
|
||||||
self.edits_manager.draw(g, &self.canvas);
|
self.primary.edits_manager.draw(g, &self.canvas);
|
||||||
self.ab_test_manager.draw(g, &self.canvas);
|
self.ab_test_manager.draw(g, &self.canvas);
|
||||||
self.logs.draw(g, &self.canvas);
|
self.logs.draw(g, &self.canvas);
|
||||||
self.search_state.draw(g, &self.canvas);
|
self.search_state.draw(g, &self.canvas);
|
||||||
@ -524,7 +561,7 @@ impl UI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn color_obj(&self, id: ID) -> Option<Color> {
|
fn color_obj(&self, id: ID) -> Option<Color> {
|
||||||
if Some(id) == self.current_selection {
|
if Some(id) == self.primary.current_selection {
|
||||||
return Some(self.cs.get(Colors::Selected));
|
return Some(self.cs.get(Colors::Selected));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -533,10 +570,10 @@ impl UI {
|
|||||||
id,
|
id,
|
||||||
Ctx {
|
Ctx {
|
||||||
cs: &self.cs,
|
cs: &self.cs,
|
||||||
map: &self.map,
|
map: &self.primary.map,
|
||||||
control_map: &self.control_map,
|
control_map: &self.primary.control_map,
|
||||||
canvas: &self.canvas,
|
canvas: &self.canvas,
|
||||||
sim: &self.sim,
|
sim: &self.primary.sim,
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
return Some(c);
|
return Some(c);
|
||||||
@ -553,24 +590,24 @@ impl UI {
|
|||||||
match idx {
|
match idx {
|
||||||
// The first plugin is all the ToggleableLayers, which doesn't implement Colorizer.
|
// The first plugin is all the ToggleableLayers, which doesn't implement Colorizer.
|
||||||
0 => None,
|
0 => None,
|
||||||
1 => Some(Box::new(&self.traffic_signal_editor)),
|
1 => Some(Box::new(&self.primary.traffic_signal_editor)),
|
||||||
2 => Some(Box::new(&self.stop_sign_editor)),
|
2 => Some(Box::new(&self.primary.stop_sign_editor)),
|
||||||
3 => Some(Box::new(&self.road_editor)),
|
3 => Some(Box::new(&self.primary.road_editor)),
|
||||||
4 => Some(Box::new(&self.search_state)),
|
4 => Some(Box::new(&self.search_state)),
|
||||||
5 => Some(Box::new(&self.warp)),
|
5 => Some(Box::new(&self.warp)),
|
||||||
6 => Some(Box::new(&self.follow)),
|
6 => Some(Box::new(&self.primary.follow)),
|
||||||
7 => Some(Box::new(&self.show_route)),
|
7 => Some(Box::new(&self.primary.show_route)),
|
||||||
8 => Some(Box::new(&self.color_picker)),
|
8 => Some(Box::new(&self.color_picker)),
|
||||||
9 => Some(Box::new(&self.steepness_viz)),
|
9 => Some(Box::new(&self.primary.steepness_viz)),
|
||||||
10 => Some(Box::new(&self.osm_classifier)),
|
10 => Some(Box::new(&self.osm_classifier)),
|
||||||
11 => Some(Box::new(&self.hider)),
|
11 => Some(Box::new(&self.primary.hider)),
|
||||||
12 => Some(Box::new(&self.debug_objects)),
|
12 => Some(Box::new(&self.primary.debug_objects)),
|
||||||
13 => Some(Box::new(&self.floodfiller)),
|
13 => Some(Box::new(&self.primary.floodfiller)),
|
||||||
14 => Some(Box::new(&self.geom_validator)),
|
14 => Some(Box::new(&self.primary.geom_validator)),
|
||||||
15 => Some(Box::new(&self.turn_cycler)),
|
15 => Some(Box::new(&self.primary.turn_cycler)),
|
||||||
16 => Some(Box::new(&self.draw_neighborhoods)),
|
16 => Some(Box::new(&self.primary.draw_neighborhoods)),
|
||||||
17 => Some(Box::new(&self.scenarios)),
|
17 => Some(Box::new(&self.primary.scenarios)),
|
||||||
18 => Some(Box::new(&self.edits_manager)),
|
18 => Some(Box::new(&self.primary.edits_manager)),
|
||||||
19 => Some(Box::new(&self.ab_test_manager)),
|
19 => Some(Box::new(&self.ab_test_manager)),
|
||||||
20 => Some(Box::new(&self.logs)),
|
20 => Some(Box::new(&self.logs)),
|
||||||
_ => panic!("Active plugin {} is too high", idx),
|
_ => panic!("Active plugin {} is too high", idx),
|
||||||
@ -648,8 +685,8 @@ pub trait ShowTurnIcons {
|
|||||||
impl ShowTurnIcons for UI {
|
impl ShowTurnIcons for UI {
|
||||||
fn show_icons_for(&self, id: IntersectionID) -> bool {
|
fn show_icons_for(&self, id: IntersectionID) -> bool {
|
||||||
self.layers.show_all_turn_icons.is_enabled()
|
self.layers.show_all_turn_icons.is_enabled()
|
||||||
|| self.stop_sign_editor.show_turn_icons(id)
|
|| self.primary.stop_sign_editor.show_turn_icons(id)
|
||||||
|| self.traffic_signal_editor.show_turn_icons(id)
|
|| self.primary.traffic_signal_editor.show_turn_icons(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user