moving the stop sign editor into edit mode. figuring out how to plumb show_turn_icons and color_for

This commit is contained in:
Dustin Carlino 2019-04-24 11:16:50 -07:00
parent 2384157108
commit 81bb9c8d1b
6 changed files with 282 additions and 230 deletions

View File

@ -257,3 +257,6 @@ Forget top menu, modal menu, OSD, right-click menus, all the current GUI things.
- replace with OSD with a little summary thing.. "sidewalk of 5th Ave"
- debug mode
- stuff like tooltips, warp, search only belong here... until i make more generally usable navigation tools
persisting anything as modes change is hard to do with the borrow checker. ex: modal menus within the edit mode, soon the core components like map and drawmap. when we're processing the current state, we &mut, but then we want to take ownership of the pieces, which should sorta be safe because we're replacing the overall state. solved this for screensaver because it's an Option, and we can take() it -- replace with None.

View File

@ -3,33 +3,43 @@ use crate::objects::{DrawCtx, ID};
use crate::plugins::{apply_map_edits, load_edits, PluginCtx};
use crate::render::{RenderOptions, Renderable, MIN_ZOOM_FOR_DETAIL};
use abstutil::Timer;
use ezgui::{Color, EventCtx, EventLoopMode, GfxCtx, Key, Wizard, WrappedWizard, GUI};
use map_model::{Lane, LaneType, Map, Road};
use ezgui::{Color, EventCtx, EventLoopMode, GfxCtx, Key, Wizard, WrappedWizard};
use map_model::{IntersectionID, Lane, LaneType, Map, Road, TurnPriority};
use std::collections::HashMap;
pub enum EditMode {
ViewingDiffs,
Saving(Wizard),
Loading(Wizard),
EditingStopSign(IntersectionID),
}
impl EditMode {
pub fn event(state: &mut GameState, ctx: &mut EventCtx) -> EventLoopMode {
ctx.canvas.handle_event(ctx.input);
// TODO Display info/hints on more lines.
ctx.input.set_mode_with_prompt(
"Map Edit Mode",
format!(
"Map Edit Mode for {}",
state.ui.state.primary.map.get_edits().describe()
),
ctx.canvas,
);
// TODO Clicking this works, but the key doesn't
if ctx.input.modal_action("quit") {
// TODO Warn about unsaved edits
state.mode = Mode::SplashScreen(Wizard::new(), None);
return EventLoopMode::InputOnly;
// Common functionality
match state.mode {
Mode::Edit(EditMode::ViewingDiffs)
| Mode::Edit(EditMode::Saving(_))
| Mode::Edit(EditMode::Loading(_)) => {
// TODO Display info/hints on more lines.
ctx.input.set_mode_with_prompt(
"Map Edit Mode",
format!(
"Map Edit Mode for {}",
state.ui.state.primary.map.get_edits().describe()
),
ctx.canvas,
);
// TODO Clicking this works, but the key doesn't
if ctx.input.modal_action("quit") {
// TODO Warn about unsaved edits
state.mode = Mode::SplashScreen(Wizard::new(), None);
return EventLoopMode::InputOnly;
}
}
_ => {}
}
match state.mode {
@ -45,7 +55,7 @@ impl EditMode {
// the effects of it. Or eventually, the Option<ID> itself will live in here
// directly.
// TODO Only mouseover lanes and intersections?
state.ui.handle_mouseover(ctx);
state.ui.handle_mouseover(ctx, None);
if let Some(ID::Lane(id)) = state.ui.state.primary.current_selection {
let lane = state.ui.state.primary.map.get_l(id);
@ -76,6 +86,15 @@ impl EditMode {
}
}
}
if let Some(ID::Intersection(id)) = state.ui.state.primary.current_selection {
if state.ui.state.primary.map.maybe_get_stop_sign(id).is_some()
&& ctx
.input
.contextual_action(Key::E, &format!("edit stop signs for {}", id))
{
state.mode = Mode::Edit(EditMode::EditingStopSign(id));
}
}
}
Mode::Edit(EditMode::Saving(ref mut wizard)) => {
if save_edits(
@ -112,6 +131,70 @@ impl EditMode {
state.mode = Mode::Edit(EditMode::ViewingDiffs);
}
}
Mode::Edit(EditMode::EditingStopSign(i)) => {
state.ui.handle_mouseover(ctx, Some(i));
ctx.input.set_mode_with_prompt(
"Stop Sign Editor",
format!("Stop Sign Editor for {}", i),
&ctx.canvas,
);
if let Some(ID::Turn(t)) = state.ui.state.primary.current_selection {
let mut sign = state.ui.state.primary.map.get_stop_sign(i).clone();
let next_priority = match sign.get_priority(t) {
TurnPriority::Banned => TurnPriority::Stop,
TurnPriority::Stop => TurnPriority::Yield,
TurnPriority::Yield => {
if sign.could_be_priority_turn(t, &state.ui.state.primary.map) {
TurnPriority::Priority
} else {
TurnPriority::Banned
}
}
TurnPriority::Priority => TurnPriority::Banned,
};
if ctx
.input
.contextual_action(Key::Space, &format!("toggle to {:?}", next_priority))
{
sign.turns.insert(t, next_priority);
let mut new_edits = state.ui.state.primary.map.get_edits().clone();
new_edits.stop_sign_overrides.insert(i, sign);
apply_map_edits(
&mut PluginCtx {
primary: &mut state.ui.state.primary,
secondary: &mut None,
canvas: ctx.canvas,
cs: &mut state.ui.state.cs,
prerender: ctx.prerender,
input: ctx.input,
hints: &mut state.ui.hints,
recalculate_current_selection: &mut false,
},
new_edits,
);
}
} else if ctx.input.modal_action("quit") {
state.mode = Mode::Edit(EditMode::ViewingDiffs);
} else if ctx.input.modal_action("reset to default") {
let mut new_edits = state.ui.state.primary.map.get_edits().clone();
new_edits.stop_sign_overrides.remove(&i);
apply_map_edits(
&mut PluginCtx {
primary: &mut state.ui.state.primary,
secondary: &mut None,
canvas: ctx.canvas,
cs: &mut state.ui.state.cs,
prerender: ctx.prerender,
input: ctx.input,
hints: &mut state.ui.hints,
recalculate_current_selection: &mut false,
},
new_edits,
);
}
}
_ => unreachable!(),
}
@ -119,7 +202,8 @@ impl EditMode {
}
pub fn draw(state: &GameState, g: &mut GfxCtx) {
state.ui.draw(g);
let mut override_color: HashMap<ID, Color> = HashMap::new();
let ctx = DrawCtx {
cs: &state.ui.state.cs,
map: &state.ui.state.primary.map,
@ -130,6 +214,8 @@ impl EditMode {
match state.mode {
Mode::Edit(EditMode::ViewingDiffs) => {
state.ui.new_draw(g, None, override_color);
// TODO Similar to drawing areas with traffic or not -- would be convenient to just
// supply a set of things to highlight and have something else take care of drawing
// with detail or not.
@ -172,9 +258,38 @@ impl EditMode {
}
Mode::Edit(EditMode::Saving(ref wizard))
| Mode::Edit(EditMode::Loading(ref wizard)) => {
state.ui.new_draw(g, None, override_color);
// TODO Still draw the diffs, yo
wizard.draw(g);
}
Mode::Edit(EditMode::EditingStopSign(i)) => {
let sign = state.ui.state.primary.map.get_stop_sign(i);
for t in &state.ui.state.primary.map.get_i(i).turns {
override_color.insert(
ID::Turn(*t),
match sign.get_priority(*t) {
TurnPriority::Priority => state
.ui
.state
.cs
.get_def("priority stop sign turn", Color::GREEN),
TurnPriority::Yield => state
.ui
.state
.cs
.get_def("yield stop sign turn", Color::YELLOW),
TurnPriority::Stop => {
state.ui.state.cs.get_def("stop turn", Color::RED)
}
TurnPriority::Banned => {
state.ui.state.cs.get_def("banned turn", Color::BLACK)
}
},
);
}
state.ui.new_draw(g, Some(i), override_color);
}
_ => unreachable!(),
}
}

View File

@ -2,5 +2,4 @@ pub mod a_b_tests;
pub mod color_picker;
pub mod draw_neighborhoods;
pub mod scenarios;
pub mod stop_sign_editor;
pub mod traffic_signal_editor;

View File

@ -1,89 +0,0 @@
use crate::objects::{DrawCtx, ID};
use crate::plugins::{apply_map_edits, BlockingPlugin, PluginCtx};
use ezgui::{Color, Key};
use map_model::{IntersectionID, TurnPriority};
pub struct StopSignEditor {
i: IntersectionID,
}
impl StopSignEditor {
pub fn new(ctx: &mut PluginCtx) -> Option<StopSignEditor> {
if let Some(ID::Intersection(id)) = ctx.primary.current_selection {
if ctx.primary.sim.is_empty()
&& ctx.primary.map.maybe_get_stop_sign(id).is_some()
&& ctx
.input
.contextual_action(Key::E, &format!("edit stop signs for {}", id))
{
return Some(StopSignEditor { i: id });
}
}
None
}
pub fn show_turn_icons(&self, id: IntersectionID) -> bool {
self.i == id
}
}
impl BlockingPlugin for StopSignEditor {
fn blocking_event(&mut self, ctx: &mut PluginCtx) -> bool {
let input = &mut ctx.input;
let map = &mut ctx.primary.map;
let selected = ctx.primary.current_selection;
input.set_mode_with_prompt(
"Stop Sign Editor",
format!("Stop Sign Editor for {}", self.i),
&ctx.canvas,
);
if let Some(ID::Turn(id)) = selected {
let mut sign = map.get_stop_sign(self.i).clone();
let next_priority = match sign.get_priority(id) {
TurnPriority::Banned => TurnPriority::Stop,
TurnPriority::Stop => TurnPriority::Yield,
TurnPriority::Yield => {
if sign.could_be_priority_turn(id, map) {
TurnPriority::Priority
} else {
TurnPriority::Banned
}
}
TurnPriority::Priority => TurnPriority::Banned,
};
if input.contextual_action(Key::Space, &format!("toggle to {:?}", next_priority)) {
sign.turns.insert(id, next_priority);
let mut edits = map.get_edits().clone();
edits.stop_sign_overrides.insert(self.i, sign);
apply_map_edits(ctx, edits);
}
} else if input.modal_action("quit") {
return false;
} else if input.modal_action("reset to default") {
let mut edits = map.get_edits().clone();
edits.stop_sign_overrides.remove(&self.i);
apply_map_edits(ctx, edits);
}
true
}
fn color_for(&self, obj: ID, ctx: &DrawCtx) -> Option<Color> {
if let ID::Turn(t) = obj {
if t.parent != self.i {
return None;
}
match ctx.map.get_stop_sign(self.i).get_priority(t) {
TurnPriority::Priority => {
Some(ctx.cs.get_def("priority stop sign turn", Color::GREEN))
}
TurnPriority::Yield => Some(ctx.cs.get_def("yield stop sign turn", Color::YELLOW)),
TurnPriority::Stop => Some(ctx.cs.get_def("stop turn", Color::RED)),
TurnPriority::Banned => Some(ctx.cs.get_def("banned turn", Color::BLACK)),
}
} else {
None
}
}
}

View File

@ -116,9 +116,6 @@ impl UIState {
pub fn show_icons_for(&self, id: IntersectionID) -> bool {
if let Some(ref plugin) = self.exclusive_blocking_plugin {
if let Ok(p) = plugin.downcast_ref::<edit::stop_sign_editor::StopSignEditor>() {
return p.show_turn_icons(id);
}
if let Ok(p) = plugin.downcast_ref::<edit::traffic_signal_editor::TrafficSignalEditor>()
{
return p.show_turn_icons(id);
@ -224,8 +221,6 @@ impl UIState {
self.exclusive_blocking_plugin = Some(Box::new(p));
} else if let Some(p) = edit::scenarios::ScenarioManager::new(&mut ctx) {
self.exclusive_blocking_plugin = Some(Box::new(p));
} else if let Some(p) = edit::stop_sign_editor::StopSignEditor::new(&mut ctx) {
self.exclusive_blocking_plugin = Some(Box::new(p));
} else if let Some(p) =
edit::traffic_signal_editor::TrafficSignalEditor::new(&mut ctx)
{

View File

@ -9,9 +9,9 @@ use ezgui::{
TopMenu, BOTTOM_LEFT, GUI,
};
use geom::{Bounds, Circle, Distance, Polygon};
use map_model::{BuildingID, LaneID, Traversable};
use map_model::{BuildingID, IntersectionID, LaneID, Traversable};
use serde_derive::{Deserialize, Serialize};
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
// TODO Collapse stuff!
pub struct UI {
@ -120,10 +120,6 @@ impl GUI for UI {
"Color Picker",
vec![(Key::Backspace, "revert"), (Key::Enter, "finalize")],
),
ModalMenu::new(
"Stop Sign Editor",
vec![(Key::Enter, "quit"), (Key::R, "reset to default")],
),
ModalMenu::new("A/B Test Editor", vec![(Key::R, "run A/B test")]),
ModalMenu::new(
"Neighborhood Editor",
@ -212,6 +208,10 @@ impl GUI for UI {
(Key::L, "load different edits"),
],
),
ModalMenu::new(
"Stop Sign Editor",
vec![(Key::Enter, "quit"), (Key::R, "reset to default")],
),
]
}
@ -221,110 +221,7 @@ impl GUI for UI {
}
fn draw(&self, g: &mut GfxCtx) {
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);
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: 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);
}
self.new_draw(g, None, HashMap::new())
}
fn dump_before_abort(&self, canvas: &Canvas) {
@ -405,13 +302,13 @@ impl UI {
ctx.canvas.handle_event(ctx.input);
// Always handle mouseover
self.handle_mouseover(ctx);
self.handle_mouseover(ctx, None);
let mut recalculate_current_selection = false;
self.state
.event(ctx, &mut self.hints, &mut recalculate_current_selection);
if recalculate_current_selection {
self.state.primary.current_selection = self.mouseover_something(&ctx);
self.state.primary.current_selection = self.mouseover_something(&ctx, None);
}
ctx.input.populate_osd(&mut self.hints.osd);
@ -442,16 +339,144 @@ impl UI {
)
}
pub fn handle_mouseover(&mut self, ctx: &mut EventCtx) {
pub fn new_draw(
&self,
g: &mut GfxCtx,
show_turn_icons_for: Option<IntersectionID>,
override_color: HashMap<ID, Color>,
) {
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,
);
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);
}
}
pub fn handle_mouseover(
&mut self,
ctx: &mut EventCtx,
show_turn_icons_for: Option<IntersectionID>,
) {
if !ctx.canvas.is_dragging() && ctx.input.get_moved_mouse().is_some() {
self.state.primary.current_selection = self.mouseover_something(&ctx);
self.state.primary.current_selection =
self.mouseover_something(&ctx, show_turn_icons_for);
}
if ctx.input.window_lost_cursor() {
self.state.primary.current_selection = None;
}
}
fn mouseover_something(&self, ctx: &EventCtx) -> Option<ID> {
fn mouseover_something(
&self,
ctx: &EventCtx,
show_turn_icons_for: Option<IntersectionID>,
) -> Option<ID> {
// Unzoomed mode. Ignore when debugging areas.
if ctx.canvas.cam_zoom < MIN_ZOOM_FOR_DETAIL
&& !self.state.primary.current_flags.debug_areas
@ -466,6 +491,7 @@ impl UI {
Circle::new(pt, Distance::meters(3.0)).get_bounds(),
ctx.prerender,
&mut cache,
show_turn_icons_for,
);
objects.reverse();
@ -507,6 +533,7 @@ impl UI {
bounds: Bounds,
prerender: &Prerender,
agents: &'a mut AgentCache,
show_turn_icons_for: Option<IntersectionID>,
) -> Vec<Box<&'a Renderable>> {
let map = &self.state.primary.map;
let draw_map = &self.state.primary.draw_map;
@ -530,7 +557,9 @@ impl UI {
ID::Lane(id) => {
lanes.push(Box::new(draw_map.get_l(id)));
let lane = map.get_l(id);
if self.state.show_icons_for(lane.dst_i) {
if self.state.show_icons_for(lane.dst_i)
|| show_turn_icons_for == Some(lane.dst_i)
{
for (t, _) in map.get_next_turns_and_lanes(id, lane.dst_i) {
turn_icons.push(Box::new(draw_map.get_t(t.id)));
}
@ -548,7 +577,7 @@ impl UI {
ID::Intersection(id) => {
intersections.push(Box::new(draw_map.get_i(id)));
for t in &map.get_i(id).turns {
if !self.state.show_icons_for(id) {
if !self.state.show_icons_for(id) && show_turn_icons_for != Some(id) {
agents_on.push(Traversable::Turn(*t));
}
}