mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-01 02:33:54 +03:00
push current_selection state into World, simplify API
This commit is contained in:
parent
548bb3b812
commit
5558d8803c
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user