mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-27 00:12:55 +03:00
solidifying synthetic editor: model/ui split, explicit enum of UI state,
standard controls for the 3 objects
This commit is contained in:
parent
c51171fc4c
commit
99fbde1d4d
@ -7,7 +7,7 @@ mod model;
|
||||
|
||||
use ezgui::{Canvas, Color, GfxCtx, Text, UserInput, GUI};
|
||||
use geom::Line;
|
||||
use model::{Intersection, IntersectionID, Model};
|
||||
use model::{BuildingID, IntersectionID, Model};
|
||||
use piston::input::Key;
|
||||
use std::process;
|
||||
|
||||
@ -16,9 +16,14 @@ const KEY_CATEGORY: &str = "";
|
||||
struct UI {
|
||||
canvas: Canvas,
|
||||
model: Model,
|
||||
state: State,
|
||||
}
|
||||
|
||||
moving_intersection: Option<IntersectionID>,
|
||||
creating_road: Option<IntersectionID>,
|
||||
enum State {
|
||||
Viewing,
|
||||
MovingIntersection(IntersectionID),
|
||||
MovingBuilding(BuildingID),
|
||||
CreatingRoad(IntersectionID),
|
||||
}
|
||||
|
||||
impl UI {
|
||||
@ -27,78 +32,103 @@ impl UI {
|
||||
UI {
|
||||
canvas: Canvas::new(),
|
||||
model: Model::new(),
|
||||
|
||||
moving_intersection: None,
|
||||
creating_road: None,
|
||||
state: State::Viewing,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GUI for UI {
|
||||
fn event(&mut self, mut input: UserInput, _osd: &mut Text) {
|
||||
fn event(&mut self, mut input: UserInput, osd: &mut Text) {
|
||||
self.canvas.handle_event(&mut input);
|
||||
let cursor = self.canvas.get_cursor_in_map_space();
|
||||
|
||||
if let Some(id) = self.moving_intersection {
|
||||
if input.key_released(Key::LCtrl) {
|
||||
self.moving_intersection = None;
|
||||
match self.state {
|
||||
State::MovingIntersection(id) => {
|
||||
self.model.move_i(id, cursor);
|
||||
if input.key_released(Key::LCtrl) {
|
||||
self.state = State::Viewing;
|
||||
}
|
||||
}
|
||||
self.model.intersections.get_mut(&id).unwrap().center = cursor;
|
||||
} else if let Some(i1) = self.creating_road {
|
||||
if input.unimportant_key_pressed(Key::Escape, KEY_CATEGORY, "stop defining road") {
|
||||
self.creating_road = None;
|
||||
} else if input.unimportant_key_pressed(Key::R, KEY_CATEGORY, "finalize road") {
|
||||
if let Some(i2) = self.model.mouseover_intersection(cursor) {
|
||||
State::MovingBuilding(id) => {
|
||||
self.model.move_b(id, cursor);
|
||||
if input.key_released(Key::LCtrl) {
|
||||
self.state = State::Viewing;
|
||||
}
|
||||
}
|
||||
State::CreatingRoad(i1) => {
|
||||
if input.key_pressed(Key::Escape, "stop defining road") {
|
||||
self.state = State::Viewing;
|
||||
} else if let Some(i2) = self.model.mouseover_intersection(cursor) {
|
||||
if i1 != i2 {
|
||||
self.model.create_road(i1, i2);
|
||||
self.creating_road = None;
|
||||
if input.key_pressed(Key::R, "finalize road") {
|
||||
self.model.create_road(i1, i2);
|
||||
self.state = State::Viewing;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if input.unimportant_key_pressed(Key::Escape, KEY_CATEGORY, "quit") {
|
||||
process::exit(0);
|
||||
}
|
||||
State::Viewing => {
|
||||
if input.unimportant_key_pressed(Key::Escape, KEY_CATEGORY, "quit") {
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
if input.unimportant_key_pressed(Key::I, KEY_CATEGORY, "create intersection") {
|
||||
let id = self.model.intersections.len();
|
||||
self.model
|
||||
.intersections
|
||||
.insert(id, Intersection { center: cursor });
|
||||
}
|
||||
if input.key_pressed(Key::I, "create intersection") {
|
||||
self.model.create_i(cursor);
|
||||
}
|
||||
if input.key_pressed(Key::B, "create building") {
|
||||
self.model.create_b(cursor);
|
||||
}
|
||||
|
||||
if input.unimportant_key_pressed(Key::LCtrl, KEY_CATEGORY, "move intersection") {
|
||||
self.moving_intersection = self.model.mouseover_intersection(cursor);
|
||||
}
|
||||
if input.unimportant_key_pressed(Key::R, KEY_CATEGORY, "create road") {
|
||||
self.creating_road = self.model.mouseover_intersection(cursor);
|
||||
}
|
||||
if input.unimportant_key_pressed(Key::Backspace, KEY_CATEGORY, "delete something") {
|
||||
if let Some(i) = self.model.mouseover_intersection(cursor) {
|
||||
// TODO No references
|
||||
self.model.intersections.remove(&i);
|
||||
if input.key_pressed(Key::LCtrl, "move intersection") {
|
||||
self.state = State::MovingIntersection(i);
|
||||
}
|
||||
|
||||
if input.key_pressed(Key::R, "create road") {
|
||||
self.state = State::CreatingRoad(i);
|
||||
}
|
||||
|
||||
if input.key_pressed(Key::Backspace, "delete intersection") {
|
||||
self.model.remove_i(i);
|
||||
}
|
||||
} else if let Some(b) = self.model.mouseover_building(cursor) {
|
||||
if input.key_pressed(Key::LCtrl, "move building") {
|
||||
self.state = State::MovingBuilding(b);
|
||||
}
|
||||
|
||||
if input.key_pressed(Key::Backspace, "delete building") {
|
||||
self.model.remove_b(b);
|
||||
}
|
||||
} else if let Some(r) = self.model.mouseover_road(cursor) {
|
||||
if input.key_pressed(Key::Backspace, "delete road") {
|
||||
self.model.remove_road(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input.populate_osd(osd);
|
||||
}
|
||||
|
||||
fn get_mut_canvas(&mut self) -> &mut Canvas {
|
||||
&mut self.canvas
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, _osd: Text) {
|
||||
fn draw(&self, g: &mut GfxCtx, osd: Text) {
|
||||
self.model.draw(g);
|
||||
|
||||
if let Some(i1) = self.creating_road {
|
||||
if let State::CreatingRoad(i1) = self.state {
|
||||
g.draw_line(
|
||||
Color::GREEN,
|
||||
5.0,
|
||||
model::ROAD_WIDTH,
|
||||
&Line::new(
|
||||
self.model.intersections[&i1].center,
|
||||
self.model.get_i_center(i1),
|
||||
self.canvas.get_cursor_in_map_space(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
self.canvas.draw_text(g, osd, ezgui::BOTTOM_LEFT);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,65 +1,112 @@
|
||||
use ezgui::{Color, GfxCtx};
|
||||
use geom::{Circle, Line, Pt2D};
|
||||
use geom::{Circle, PolyLine, Polygon, Pt2D};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub const ROAD_WIDTH: f64 = 5.0;
|
||||
const INTERSECTION_RADIUS: f64 = 10.0;
|
||||
const BUILDING_LENGTH: f64 = 30.0;
|
||||
|
||||
pub type BuildingID = usize;
|
||||
pub type IntersectionID = usize;
|
||||
pub type RoadID = (IntersectionID, IntersectionID);
|
||||
|
||||
pub struct Model {
|
||||
pub intersections: BTreeMap<IntersectionID, Intersection>,
|
||||
pub roads: Vec<Road>,
|
||||
buildings: Vec<Building>,
|
||||
intersections: BTreeMap<IntersectionID, Intersection>,
|
||||
roads: BTreeMap<RoadID, Road>,
|
||||
buildings: BTreeMap<BuildingID, Building>,
|
||||
}
|
||||
|
||||
pub struct Intersection {
|
||||
pub center: Pt2D,
|
||||
center: Pt2D,
|
||||
}
|
||||
|
||||
impl Intersection {
|
||||
fn circle(&self) -> Circle {
|
||||
Circle::new(self.center, 10.0)
|
||||
Circle::new(self.center, INTERSECTION_RADIUS)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Road {
|
||||
pub i1: IntersectionID,
|
||||
pub i2: IntersectionID,
|
||||
i1: IntersectionID,
|
||||
i2: IntersectionID,
|
||||
}
|
||||
|
||||
impl Road {
|
||||
fn polygon(&self, model: &Model) -> Polygon {
|
||||
PolyLine::new(vec![
|
||||
model.intersections[&self.i1].center,
|
||||
model.intersections[&self.i2].center,
|
||||
]).make_polygons(ROAD_WIDTH)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Building {
|
||||
top_left: Pt2D,
|
||||
}
|
||||
|
||||
impl Building {
|
||||
fn polygon(&self) -> Polygon {
|
||||
let (x, y) = (self.top_left.x(), self.top_left.y());
|
||||
|
||||
Polygon::new(&vec![
|
||||
Pt2D::new(x, y),
|
||||
Pt2D::new(x + BUILDING_LENGTH, y),
|
||||
Pt2D::new(x + BUILDING_LENGTH, y + BUILDING_LENGTH),
|
||||
Pt2D::new(x, y + BUILDING_LENGTH),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn new() -> Model {
|
||||
Model {
|
||||
intersections: BTreeMap::new(),
|
||||
roads: Vec::new(),
|
||||
buildings: Vec::new(),
|
||||
roads: BTreeMap::new(),
|
||||
buildings: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&self, g: &mut GfxCtx) {
|
||||
g.clear(Color::WHITE);
|
||||
|
||||
for r in &self.roads {
|
||||
g.draw_line(
|
||||
Color::BLACK,
|
||||
5.0,
|
||||
&Line::new(
|
||||
self.intersections[&r.i1].center,
|
||||
self.intersections[&r.i2].center,
|
||||
),
|
||||
);
|
||||
for r in self.roads.values() {
|
||||
g.draw_polygon(Color::BLACK, &r.polygon(self));
|
||||
}
|
||||
|
||||
for i in self.intersections.values() {
|
||||
g.draw_circle(Color::RED, &i.circle());
|
||||
}
|
||||
|
||||
for b in &self.buildings {
|
||||
g.draw_rectangle(Color::BLUE, [b.top_left.x(), b.top_left.y(), 5.0, 5.0]);
|
||||
for b in self.buildings.values() {
|
||||
g.draw_polygon(Color::BLUE, &b.polygon());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn create_i(&mut self, center: Pt2D) {
|
||||
let id = self.intersections.len();
|
||||
self.intersections.insert(id, Intersection { center });
|
||||
}
|
||||
|
||||
pub fn move_i(&mut self, id: IntersectionID, center: Pt2D) {
|
||||
self.intersections.get_mut(&id).unwrap().center = center;
|
||||
}
|
||||
|
||||
pub fn remove_i(&mut self, id: IntersectionID) {
|
||||
for (i1, i2) in self.roads.keys() {
|
||||
if *i1 == id || *i2 == id {
|
||||
println!("Can't delete intersection used by roads");
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.intersections.remove(&id);
|
||||
}
|
||||
|
||||
pub fn get_i_center(&self, id: IntersectionID) -> Pt2D {
|
||||
self.intersections[&id].center
|
||||
}
|
||||
|
||||
pub fn mouseover_intersection(&self, pt: Pt2D) -> Option<IntersectionID> {
|
||||
for (id, i) in &self.intersections {
|
||||
@ -73,7 +120,48 @@ impl Model {
|
||||
|
||||
impl Model {
|
||||
pub fn create_road(&mut self, i1: IntersectionID, i2: IntersectionID) {
|
||||
// TODO No duplicates
|
||||
self.roads.push(Road { i1, i2 });
|
||||
let id = if i1 < i2 { (i1, i2) } else { (i2, i1) };
|
||||
if self.roads.contains_key(&id) {
|
||||
println!("Road already exists");
|
||||
return;
|
||||
}
|
||||
self.roads.insert(id, Road { i1, i2 });
|
||||
}
|
||||
|
||||
pub fn remove_road(&mut self, id: RoadID) {
|
||||
self.roads.remove(&id);
|
||||
}
|
||||
|
||||
pub fn mouseover_road(&self, pt: Pt2D) -> Option<RoadID> {
|
||||
for (id, r) in &self.roads {
|
||||
if r.polygon(self).contains_pt(pt) {
|
||||
return Some(*id);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn create_b(&mut self, top_left: Pt2D) {
|
||||
let id = self.buildings.len();
|
||||
self.buildings.insert(id, Building { top_left });
|
||||
}
|
||||
|
||||
pub fn move_b(&mut self, id: IntersectionID, top_left: Pt2D) {
|
||||
self.buildings.get_mut(&id).unwrap().top_left = top_left;
|
||||
}
|
||||
|
||||
pub fn remove_b(&mut self, id: BuildingID) {
|
||||
self.buildings.remove(&id);
|
||||
}
|
||||
|
||||
pub fn mouseover_building(&self, pt: Pt2D) -> Option<BuildingID> {
|
||||
for (id, b) in &self.buildings {
|
||||
if b.polygon().contains_pt(pt) {
|
||||
return Some(*id);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user