game/sandbox/gameplay/freeform/
area_spawner.rs

1use geom::{Polygon, Pt2D};
2use map_model::{BuildingID, IntersectionID};
3use widgetry::mapspace::{ObjectID, World, WorldOutcome};
4use widgetry::tools::ChooseSomething;
5use widgetry::{
6    Choice, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome,
7    Panel, State, TextExt, VerticalAlignment, Widget,
8};
9
10use crate::app::{App, Transition};
11
12pub struct AreaSpawner {
13    areas: Vec<Area>,
14    panel: Panel,
15    world: World<Obj>,
16    mode: Mode,
17}
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
20struct Obj(usize);
21impl ObjectID for Obj {}
22
23enum Mode {
24    Neutral,
25    DrawingArea(SelectRectangle),
26    PickingDestination { source: usize },
27}
28
29impl AreaSpawner {
30    pub fn new_state(ctx: &mut EventCtx) -> Box<dyn State<App>> {
31        Box::new(AreaSpawner {
32            areas: Vec::new(),
33            panel: Panel::new_builder(Widget::col(vec![
34                Widget::row(vec![
35                    Line("Specify traffic patterns")
36                        .small_heading()
37                        .into_widget(ctx),
38                    ctx.style().btn_close_widget(ctx),
39                ]),
40                ctx.style()
41                    .btn_outline
42                    .text("Draw new area")
43                    .hotkey(Key::A)
44                    .build_def(ctx),
45                "".text_widget(ctx).named("instructions"),
46            ]))
47            .aligned(HorizontalAlignment::Left, VerticalAlignment::Top)
48            .build(ctx),
49            world: World::new(),
50            mode: Mode::Neutral,
51        })
52    }
53
54    fn rebuild_world(&mut self, ctx: &mut EventCtx) {
55        let mut world = World::new();
56        let picking_destination = match self.mode {
57            Mode::PickingDestination { source } => Some(source),
58            _ => None,
59        };
60
61        for (idx, area) in self.areas.iter().enumerate() {
62            if picking_destination == Some(idx) {
63                world
64                    .add(Obj(idx))
65                    .hitbox(area.polygon.clone())
66                    .draw_color(Color::RED.alpha(0.5))
67                    .build(ctx);
68            } else {
69                world
70                    .add(Obj(idx))
71                    .hitbox(area.polygon.clone())
72                    .draw_color(Color::BLUE.alpha(0.5))
73                    .hover_alpha(0.8)
74                    .clickable()
75                    .build(ctx);
76            }
77        }
78        world.initialize_hover(ctx);
79        self.world = world;
80    }
81}
82
83impl State<App> for AreaSpawner {
84    fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
85        match self.mode {
86            Mode::Neutral => {
87                if let Outcome::Clicked(x) = self.panel.event(ctx) {
88                    match x.as_ref() {
89                        "close" => {
90                            return Transition::Pop;
91                        }
92                        "Draw new area" => {
93                            self.mode = Mode::DrawingArea(SelectRectangle::new(ctx));
94                            let label = "Click and drag to select an area".text_widget(ctx);
95                            self.panel.replace(ctx, "instructions", label);
96                        }
97                        _ => unreachable!(),
98                    }
99                }
100
101                if let WorldOutcome::ClickedObject(Obj(idx)) = self.world.event(ctx) {
102                    let area = &self.areas[idx];
103                    return Transition::Push(ChooseSomething::new_state(
104                        ctx,
105                        format!(
106                            "This area has {} buildings and {} borders",
107                            area.buildings.len(),
108                            area.borders.len()
109                        ),
110                        vec![
111                            Choice::string("spawn traffic from here"),
112                            Choice::string("delete"),
113                        ],
114                        Box::new(move |resp, _, _| {
115                            Transition::Multi(vec![
116                                Transition::Pop,
117                                Transition::ModifyState(Box::new(move |state, ctx, _| {
118                                    let state = state.downcast_mut::<AreaSpawner>().unwrap();
119                                    if resp == "delete" {
120                                        state.areas.remove(idx);
121                                        state.rebuild_world(ctx);
122                                    } else if resp == "spawn traffic from here" {
123                                        state.mode = Mode::PickingDestination { source: idx };
124                                        state.rebuild_world(ctx);
125                                        let label = "Choose where traffic will go".text_widget(ctx);
126                                        state.panel.replace(ctx, "instructions", label);
127                                    }
128                                })),
129                            ])
130                        }),
131                    ));
132                }
133            }
134            Mode::DrawingArea(ref mut select) => {
135                if select.event(ctx) {
136                    if let Some(polygon) = select.rect.take() {
137                        self.areas.push(Area::new(app, polygon));
138                        self.rebuild_world(ctx);
139                    }
140                    self.mode = Mode::Neutral;
141                    let label = "".text_widget(ctx);
142                    self.panel.replace(ctx, "instructions", label);
143                }
144            }
145            Mode::PickingDestination { .. } => {
146                if let WorldOutcome::ClickedObject(Obj(_destination)) = self.world.event(ctx) {
147                    // TODO Enter a new state to specify the traffic params
148                    self.mode = Mode::Neutral;
149                    self.rebuild_world(ctx);
150                    let label = "".text_widget(ctx);
151                    self.panel.replace(ctx, "instructions", label);
152                }
153            }
154        }
155
156        Transition::Keep
157    }
158
159    fn draw(&self, g: &mut GfxCtx, _: &App) {
160        self.panel.draw(g);
161        self.world.draw(g);
162        if let Mode::DrawingArea(ref select) = self.mode {
163            select.draw(g);
164        }
165    }
166}
167
168struct SelectRectangle {
169    pt1: Option<Pt2D>,
170    rect: Option<Polygon>,
171    preview: Drawable,
172}
173
174impl SelectRectangle {
175    fn new(ctx: &mut EventCtx) -> SelectRectangle {
176        SelectRectangle {
177            pt1: None,
178            rect: None,
179            preview: Drawable::empty(ctx),
180        }
181    }
182
183    /// True if done
184    fn event(&mut self, ctx: &mut EventCtx) -> bool {
185        let pt = if let Some(pt) = ctx.canvas.get_cursor_in_map_space() {
186            pt
187        } else {
188            return false;
189        };
190        if let Some(pt1) = self.pt1 {
191            if ctx.redo_mouseover() {
192                self.rect = Polygon::rectangle_two_corners(pt1, pt);
193                let mut batch = GeomBatch::new();
194                if let Some(ref poly) = self.rect {
195                    batch.push(Color::RED.alpha(0.5), poly.clone());
196                }
197                self.preview = batch.upload(ctx);
198            }
199            if ctx.input.left_mouse_button_released() {
200                return true;
201            }
202        } else if ctx.input.left_mouse_button_pressed() {
203            self.pt1 = Some(pt);
204        }
205        false
206    }
207
208    fn draw(&self, g: &mut GfxCtx) {
209        g.redraw(&self.preview);
210    }
211}
212
213struct Area {
214    polygon: Polygon,
215    borders: Vec<IntersectionID>,
216    buildings: Vec<BuildingID>,
217}
218
219impl Area {
220    fn new(app: &App, polygon: Polygon) -> Area {
221        let mut borders = Vec::new();
222        for i in app.primary.map.all_intersections() {
223            if i.is_border() && polygon.contains_pt(i.polygon.center()) {
224                borders.push(i.id);
225            }
226        }
227        let mut buildings = Vec::new();
228        for b in app.primary.map.all_buildings() {
229            if polygon.contains_pt(b.polygon.center()) {
230                buildings.push(b.id);
231            }
232        }
233        Area {
234            polygon,
235            borders,
236            buildings,
237        }
238    }
239}