push current_selection state into World, simplify API

This commit is contained in:
Dustin Carlino 2019-09-11 14:55:54 -07:00
parent 548bb3b812
commit 5558d8803c
4 changed files with 87 additions and 120 deletions

View File

@ -6,7 +6,6 @@ use ezgui::{
use geom::{Circle, Distance, PolyLine, Polygon};
use map_model::raw_data::{Hint, Hints, InitialMap, Map, StableIntersectionID, StableRoadID};
use map_model::LANE_THICKNESS;
use std::collections::HashSet;
use std::{env, process};
use viewer::World;
@ -22,18 +21,9 @@ struct UI {
}
enum State {
Main {
menu: ModalMenu,
// TODO Or, if these are common things, the World could also hold this state.
selected: Option<ID>,
osd: Text,
},
Main { menu: ModalMenu, osd: Text },
BrowsingHints(WarpingItemSlider<Hint>),
BanTurnsBetween {
from: StableRoadID,
selected: Option<ID>,
osd: Text,
},
BanTurnsBetween { from: StableRoadID, osd: Text },
MovingIntersection(StableIntersectionID, Text),
}
@ -51,7 +41,6 @@ impl State {
]],
ctx,
),
selected: None,
osd: Text::new(),
}
}
@ -84,10 +73,11 @@ impl GUI for UI {
match self.state {
State::Main {
ref mut menu,
ref mut selected,
ref mut osd,
} => {
{
self.world.handle_mouseover(ctx);
let len = self.hints.hints.len();
let mut txt = Text::prompt("Fix Map Geometry");
txt.add(Line(len.to_string()).fg(Color::CYAN));
@ -96,7 +86,7 @@ impl GUI for UI {
Line(self.hints.parking_overrides.len().to_string()).fg(Color::CYAN),
);
txt.append(Line(" parking overrides"));
if let Some(ID::Road(r)) = selected {
if let Some(ID::Road(r)) = self.world.get_selection() {
txt.add(Line(r.to_string()).fg(Color::RED));
txt.append(Line(format!(
" is {} long",
@ -113,7 +103,7 @@ impl GUI for UI {
txt.append(Line(v).fg(Color::CYAN));
}
}
if let Some(ID::Intersection(i)) = selected {
if let Some(ID::Intersection(i)) = self.world.get_selection() {
txt.add(Line(i.to_string()).fg(Color::RED));
txt.append(Line(" OSM tag diffs:"));
let roads = &self.data.intersections[&i].roads;
@ -154,10 +144,6 @@ impl GUI for UI {
}
ctx.canvas.handle_event(ctx.input);
if ctx.redo_mouseover() {
*selected = self.world.mouseover_something(ctx, &HashSet::new());
}
if menu.action("quit") {
process::exit(0);
}
@ -205,7 +191,6 @@ impl GUI for UI {
false
};
if recalc {
*selected = None;
ctx.loading_screen("recalculate map from hints", |ctx, mut timer| {
self.data = InitialMap::new(
self.data.name.clone(),
@ -220,28 +205,26 @@ impl GUI for UI {
}
}
if let Some(ID::Road(r)) = selected {
if let Some(ID::Road(r)) = self.world.get_selection() {
if ctx.input.key_pressed(Key::M, "merge") {
self.hints
.hints
.push(Hint::MergeRoad(self.raw.roads[&r].orig_id));
self.data.merge_road(*r, &mut Timer::new("merge road"));
self.data.merge_road(r, &mut Timer::new("merge road"));
self.world = initial_map_to_world(&self.data, ctx);
*selected = None;
} else if ctx.input.key_pressed(Key::D, "delete") {
self.hints
.hints
.push(Hint::DeleteRoad(self.raw.roads[r].orig_id));
self.data.delete_road(*r, &mut Timer::new("delete road"));
.push(Hint::DeleteRoad(self.raw.roads[&r].orig_id));
self.data.delete_road(r, &mut Timer::new("delete road"));
self.world = initial_map_to_world(&self.data, ctx);
*selected = None;
} else if ctx.input.key_pressed(Key::P, "toggle parking") {
let has_parking = !self.data.roads[&r].has_parking();
self.hints
.parking_overrides
.insert(self.raw.roads[r].orig_id, has_parking);
.insert(self.raw.roads[&r].orig_id, has_parking);
self.data.override_parking(
*r,
r,
has_parking,
&mut Timer::new("override parking"),
);
@ -251,40 +234,38 @@ impl GUI for UI {
.key_pressed(Key::T, "ban turns between this road and another")
{
self.state = State::BanTurnsBetween {
from: *r,
selected: *selected,
from: r,
osd: Text::new(),
};
return EventLoopMode::InputOnly;
} else if ctx.input.key_pressed(Key::E, "examine") {
let road = &self.data.roads[r];
let road = &self.data.roads[&r];
println!("{} between {} and {}", road.id, road.src_i, road.dst_i);
println!("Orig pts: {}", road.original_center_pts);
println!("Trimmed pts: {}", road.trimmed_center_pts);
}
}
if let Some(ID::Intersection(i)) = selected {
if let Some(ID::Intersection(i)) = self.world.get_selection() {
if ctx.input.key_pressed(Key::LeftControl, "move intersection") {
self.state = State::MovingIntersection(*i, Text::new());
self.state = State::MovingIntersection(i, Text::new());
return EventLoopMode::InputOnly;
}
if ctx.input.key_pressed(Key::E, "examine") {
let intersection = &self.data.intersections[i];
let intersection = &self.data.intersections[&i];
println!("{} has roads: {:?}", intersection.id, intersection.roads);
println!("Points: {:?}", intersection.polygon);
}
if self.data.intersections[i].roads.len() == 2
if self.data.intersections[&i].roads.len() == 2
&& ctx.input.key_pressed(Key::M, "merge")
{
self.hints.hints.push(Hint::MergeDegenerateIntersection(
self.raw.intersections[i].orig_id,
self.raw.intersections[&i].orig_id,
));
self.data.merge_degenerate_intersection(
*i,
i,
&mut Timer::new("merge intersection"),
);
self.world = initial_map_to_world(&self.data, ctx);
*selected = None;
}
}
@ -301,32 +282,25 @@ impl GUI for UI {
EventLoopMode::InputOnly
}
}
State::BanTurnsBetween {
from,
ref mut selected,
ref mut osd,
} => {
State::BanTurnsBetween { from, ref mut osd } => {
ctx.canvas.handle_event(ctx.input);
if ctx.redo_mouseover() {
*selected = self.world.mouseover_something(ctx, &HashSet::new());
}
self.world.handle_mouseover(ctx);
if ctx.input.key_pressed(Key::Escape, "cancel") {
self.state = State::main(ctx);
return EventLoopMode::InputOnly;
} else if let Some(ID::Road(r)) = selected {
} else if let Some(ID::Road(r)) = self.world.get_selection() {
// TODO Why do we use data and not raw here?
let (i1, i2) = (self.data.roads[&from].src_i, self.data.roads[&from].dst_i);
let (i3, i4) = (self.data.roads[r].src_i, self.data.roads[r].dst_i);
let (i3, i4) = (self.data.roads[&r].src_i, self.data.roads[&r].dst_i);
if from != *r
if from != r
&& (i1 == i3 || i1 == i4 || i2 == i3 || i2 == i4)
&& ctx.input.key_pressed(Key::T, "ban turns to this road")
{
self.hints.hints.push(Hint::BanTurnsBetween(
self.raw.roads[&from].orig_id,
self.raw.roads[r].orig_id,
self.raw.roads[&r].orig_id,
));
// There's nothing to change about our model here.
self.state = State::main(ctx);
@ -370,18 +344,10 @@ impl GUI for UI {
fn draw(&self, g: &mut GfxCtx) {
g.clear(Color::WHITE);
self.world.draw(g, &HashSet::new());
self.world.draw(g);
match self.state {
State::Main {
ref selected,
ref menu,
ref osd,
} => {
if let Some(id) = selected {
self.world.draw_selected(g, *id);
}
State::Main { ref menu, ref osd } => {
menu.draw(g);
g.draw_blocking_text(osd, ezgui::BOTTOM_LEFT);
}
@ -406,19 +372,10 @@ impl GUI for UI {
slider.draw(g);
}
State::BanTurnsBetween {
ref selected,
ref osd,
..
} => {
if let Some(id) = selected {
self.world.draw_selected(g, *id);
}
State::BanTurnsBetween { ref osd, .. } => {
g.draw_blocking_text(osd, ezgui::BOTTOM_LEFT);
}
State::MovingIntersection(i, ref osd) => {
self.world.draw_selected(g, ID::Intersection(i));
State::MovingIntersection(_, ref osd) => {
g.draw_blocking_text(osd, ezgui::BOTTOM_LEFT);
}
}

View File

@ -1,18 +1,16 @@
use abstutil::{read_binary, Timer};
use viewer::World;
use ezgui::{EventCtx, Prerender, Color, GfxCtx, Text};
use geom::{Circle, Bounds, Distance, LonLat, PolyLine, Polygon, Pt2D};
use ezgui::{Color, EventCtx, GfxCtx, Prerender, Text};
use geom::{Bounds, Circle, Distance, LonLat, PolyLine, Polygon, Pt2D};
use map_model::raw_data::{StableIntersectionID, StableRoadID};
use map_model::{raw_data, IntersectionType, LaneType, RoadSpec, LANE_THICKNESS};
use std::collections::{HashSet, BTreeMap};
use std::collections::BTreeMap;
use std::mem;
use viewer::World;
const INTERSECTION_RADIUS: Distance = Distance::const_meters(5.0);
const BUILDING_LENGTH: Distance = Distance::const_meters(30.0);
const CENTER_LINE_THICKNESS: Distance = Distance::const_meters(0.5);
const HIGHLIGHT_COLOR: Color = Color::CYAN;
pub type BuildingID = usize;
pub type Direction = bool;
const FORWARDS: Direction = true;
@ -145,9 +143,7 @@ impl Model {
pub fn draw(&self, g: &mut GfxCtx) {
g.clear(Color::WHITE);
self.world.draw(g, &HashSet::new());
// TODO HIGHLIGHT_COLOR
self.world.draw(g);
// TODO Always draw labels?
/*if let Some(ref label) = i.label {
@ -155,8 +151,12 @@ impl Model {
}*/
}
pub fn mouseover_something(&self, ctx: &EventCtx) -> Option<ID> {
self.world.mouseover_something(ctx, &HashSet::new())
pub fn handle_mouseover(&mut self, ctx: &EventCtx) {
self.world.handle_mouseover(ctx);
}
pub fn get_selection(&self) -> Option<ID> {
self.world.get_selection()
}
fn compute_bounds(&self) -> Bounds {
@ -302,7 +302,8 @@ impl Model {
b.polygon(),
Color::BLUE,
// TODO Always show its label?
Text::new());
Text::new(),
);
}
for (id, i) in &self.intersections {
@ -315,17 +316,14 @@ impl Model {
IntersectionType::StopSign => Color::RED,
IntersectionType::Border => Color::BLUE,
},
Text::new());
Text::new(),
);
}
for (id, r) in &self.roads {
for (dir, idx, poly, color) in r.polygons(self) {
self.world.add_obj(
prerender,
ID::Lane(*id, dir, idx),
poly,
color,
Text::new());
self.world
.add_obj(prerender, ID::Lane(*id, dir, idx), poly, color, Text::new());
}
}
}

View File

@ -40,6 +40,8 @@ impl UI {
impl GUI for UI {
fn event(&mut self, ctx: &mut EventCtx) -> EventLoopMode {
ctx.canvas.handle_event(ctx.input);
self.model.handle_mouseover(ctx);
let cursor = {
if let Some(c) = ctx.canvas.get_cursor_in_map_space() {
c
@ -47,7 +49,6 @@ impl GUI for UI {
return EventLoopMode::InputOnly;
}
};
let selected = self.model.mouseover_something(ctx);
match self.state {
State::MovingIntersection(id) => {
@ -98,7 +99,7 @@ impl GUI for UI {
State::CreatingRoad(i1) => {
if ctx.input.key_pressed(Key::Escape, "stop defining road") {
self.state = State::Viewing;
} else if let Some(ID::Intersection(i2)) = selected {
} else if let Some(ID::Intersection(i2)) = self.model.get_selection() {
if i1 != i2 && ctx.input.key_pressed(Key::R, "finalize road") {
self.model.create_road(i1, i2);
self.state = State::Viewing;
@ -126,7 +127,7 @@ impl GUI for UI {
}
}
State::Viewing => {
if let Some(ID::Intersection(i)) = selected {
if let Some(ID::Intersection(i)) = self.model.get_selection() {
if ctx.input.key_pressed(Key::LeftControl, "move intersection") {
self.state = State::MovingIntersection(i);
} else if ctx.input.key_pressed(Key::R, "create road") {
@ -138,7 +139,7 @@ impl GUI for UI {
} else if ctx.input.key_pressed(Key::L, "label intersection") {
self.state = State::LabelingIntersection(i, Wizard::new());
}
} else if let Some(ID::Building(b)) = selected {
} else if let Some(ID::Building(b)) = self.model.get_selection() {
if ctx.input.key_pressed(Key::LeftControl, "move building") {
self.state = State::MovingBuilding(b);
} else if ctx.input.key_pressed(Key::Backspace, "delete building") {
@ -146,7 +147,7 @@ impl GUI for UI {
} else if ctx.input.key_pressed(Key::L, "label building") {
self.state = State::LabelingBuilding(b, Wizard::new());
}
} else if let Some(ID::Lane(r, dir, _)) = selected {
} else if let Some(ID::Lane(r, dir, _)) = self.model.get_selection() {
if ctx
.input
.key_pressed(Key::Backspace, &format!("delete road {}", r))
@ -210,6 +211,10 @@ impl GUI for UI {
fn main() {
let args: Vec<String> = env::args().collect();
ezgui::run("Synthetic map editor", 1024.0, 768.0, |ctx| {
UI::new(args.get(1), args.get(2) == Some(&"--nobldgs".to_string()), ctx)
UI::new(
args.get(1),
args.get(2) == Some(&"--nobldgs".to_string()),
ctx,
)
});
}

View File

@ -1,7 +1,7 @@
use aabb_quadtree::QuadTree;
use ezgui::{Color, Drawable, EventCtx, GfxCtx, Prerender, Text};
use geom::{Bounds, Circle, Distance, Polygon, Pt2D};
use std::collections::{HashMap, HashSet};
use geom::{Bounds, Circle, Distance, Polygon};
use std::collections::HashMap;
use std::fmt::Debug;
use std::hash::Hash;
@ -19,6 +19,7 @@ struct Object {
pub struct World<ID: ObjectID> {
objects: HashMap<ID, Object>,
quadtree: QuadTree<ID>,
current_selection: Option<ID>,
}
impl<ID: ObjectID> World<ID> {
@ -26,31 +27,39 @@ impl<ID: ObjectID> World<ID> {
World {
objects: HashMap::new(),
quadtree: QuadTree::default(bounds.as_bbox()),
current_selection: None,
}
}
pub fn draw(&self, g: &mut GfxCtx, hide: &HashSet<ID>) {
pub fn draw(&self, g: &mut GfxCtx) {
let mut objects: Vec<ID> = Vec::new();
for &(id, _, _) in &self.quadtree.query(g.get_screen_bounds().as_bbox()) {
if !hide.contains(id) {
objects.push(*id);
}
objects.push(*id);
}
objects.sort_by_key(|id| id.zorder());
for id in objects {
g.redraw(&self.objects[&id].draw);
}
if let Some(id) = self.current_selection {
let obj = &self.objects[&id];
g.draw_polygon(Color::BLUE, &obj.polygon);
g.draw_mouse_tooltip(&obj.info);
}
}
pub fn draw_selected(&self, g: &mut GfxCtx, id: ID) {
let obj = &self.objects[&id];
g.draw_polygon(Color::BLUE, &obj.polygon);
g.draw_mouse_tooltip(&obj.info);
}
pub fn handle_mouseover(&mut self, ctx: &EventCtx) {
if !ctx.redo_mouseover() {
return;
}
self.current_selection = None;
pub fn mouseover_something(&self, ctx: &EventCtx, hide: &HashSet<ID>) -> Option<ID> {
let cursor = ctx.canvas.get_cursor_in_map_space()?;
let cursor = if let Some(pt) = ctx.canvas.get_cursor_in_map_space() {
pt
} else {
return;
};
let mut objects: Vec<ID> = Vec::new();
for &(id, _, _) in &self.quadtree.query(
@ -58,19 +67,21 @@ impl<ID: ObjectID> World<ID> {
.get_bounds()
.as_bbox(),
) {
if !hide.contains(id) {
objects.push(*id);
}
objects.push(*id);
}
objects.sort_by_key(|id| id.zorder());
objects.reverse();
for id in objects {
if self.objects[&id].polygon.contains_pt(cursor) {
return Some(id);
self.current_selection = Some(id);
return;
}
}
None
}
pub fn get_selection(&self) -> Option<ID> {
self.current_selection
}
pub fn add_obj(
@ -93,8 +104,4 @@ impl<ID: ObjectID> World<ID> {
},
);
}
pub fn get_center(&self, id: ID) -> Pt2D {
self.objects[&id].polygon.center()
}
}