solidifying synthetic editor: model/ui split, explicit enum of UI state,

standard controls for the 3 objects
This commit is contained in:
Dustin Carlino 2018-11-14 08:59:10 -08:00
parent c51171fc4c
commit 99fbde1d4d
2 changed files with 182 additions and 64 deletions

View File

@ -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);
}
}

View File

@ -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
}
}