game/edit/
stop_signs.rs

1use std::collections::HashMap;
2
3use maplit::btreeset;
4
5use geom::Polygon;
6use map_gui::render::DrawIntersection;
7use map_model::{
8    ControlStopSign, ControlTrafficSignal, EditIntersectionControl, IntersectionID, RoadID,
9};
10use widgetry::{
11    EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Panel, SimpleState, State, Text,
12    VerticalAlignment, Widget,
13};
14
15use crate::app::App;
16use crate::app::Transition;
17use crate::common::CommonState;
18use crate::edit::{apply_map_edits, check_sidewalk_connectivity, TrafficSignalEditor};
19use crate::sandbox::GameplayMode;
20
21// TODO For now, individual turns can't be manipulated. Banning turns could be useful, but I'm not
22// sure what to do about the player orphaning a section of the map.
23pub struct StopSignEditor {
24    id: IntersectionID,
25    mode: GameplayMode,
26    // (octagon, pole)
27    geom: HashMap<RoadID, (Polygon, Polygon)>,
28    selected_sign: Option<RoadID>,
29}
30
31impl StopSignEditor {
32    pub fn new_state(
33        ctx: &mut EventCtx,
34        app: &mut App,
35        id: IntersectionID,
36        mode: GameplayMode,
37    ) -> Box<dyn State<App>> {
38        app.primary.current_selection = None;
39        let geom = app
40            .primary
41            .map
42            .get_stop_sign(id)
43            .roads
44            .iter()
45            .filter_map(|(r, ss)| {
46                DrawIntersection::stop_sign_geom(ss, &app.primary.map)
47                    .map(|(octagon, pole, _)| (*r, (octagon, pole)))
48            })
49            .collect();
50
51        let panel = Panel::new_builder(Widget::col(vec![
52            Line("Stop sign editor").small_heading().into_widget(ctx),
53            Widget::row(vec![
54                ctx.style()
55                    .btn_solid_primary
56                    .text("Finish")
57                    .hotkey(Key::Escape)
58                    .build_def(ctx),
59                ctx.style()
60                    .btn_outline
61                    .text("reset to default")
62                    .hotkey(Key::R)
63                    .disabled(
64                        &ControlStopSign::new(&app.primary.map, id)
65                            == app.primary.map.get_stop_sign(id),
66                    )
67                    .build_def(ctx),
68                ctx.style()
69                    .btn_outline
70                    .text("Change crosswalks")
71                    .hotkey(Key::C)
72                    .build_def(ctx),
73            ]),
74            Widget::row(vec![
75                ctx.style()
76                    .btn_outline
77                    .text("close intersection for construction")
78                    .build_def(ctx),
79                ctx.style()
80                    .btn_outline
81                    .text("convert to traffic signal")
82                    .build_def(ctx),
83            ]),
84        ]))
85        .aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
86        .build(ctx);
87
88        <dyn SimpleState<_>>::new_state(
89            panel,
90            Box::new(StopSignEditor {
91                id,
92                mode,
93                geom,
94                selected_sign: None,
95            }),
96        )
97    }
98}
99
100impl SimpleState<App> for StopSignEditor {
101    fn on_click(
102        &mut self,
103        ctx: &mut EventCtx,
104        app: &mut App,
105        x: &str,
106        _: &mut Panel,
107    ) -> Transition {
108        match x {
109            "Finish" => Transition::Pop,
110            "reset to default" => {
111                let mut edits = app.primary.map.get_edits().clone();
112                edits
113                    .commands
114                    .push(app.primary.map.edit_intersection_cmd(self.id, |new| {
115                        new.control = EditIntersectionControl::StopSign(ControlStopSign::new(
116                            &app.primary.map,
117                            self.id,
118                        ));
119                    }));
120                apply_map_edits(ctx, app, edits);
121                Transition::Replace(StopSignEditor::new_state(
122                    ctx,
123                    app,
124                    self.id,
125                    self.mode.clone(),
126                ))
127            }
128            "close intersection for construction" => {
129                let cmd = app.primary.map.edit_intersection_cmd(self.id, |new| {
130                    new.control = EditIntersectionControl::Closed;
131                });
132                if let Some(err) = check_sidewalk_connectivity(ctx, app, cmd.clone()) {
133                    Transition::Push(err)
134                } else {
135                    let mut edits = app.primary.map.get_edits().clone();
136                    edits.commands.push(cmd);
137                    apply_map_edits(ctx, app, edits);
138
139                    Transition::Pop
140                }
141            }
142            "convert to traffic signal" => {
143                let mut edits = app.primary.map.get_edits().clone();
144                edits
145                    .commands
146                    .push(app.primary.map.edit_intersection_cmd(self.id, |new| {
147                        new.control = EditIntersectionControl::TrafficSignal(
148                            ControlTrafficSignal::new(&app.primary.map, self.id)
149                                .export(&app.primary.map),
150                        );
151                    }));
152                apply_map_edits(ctx, app, edits);
153                app.primary
154                    .sim
155                    .handle_live_edited_traffic_signals(&app.primary.map);
156                Transition::Replace(TrafficSignalEditor::new_state(
157                    ctx,
158                    app,
159                    btreeset! {self.id},
160                    self.mode.clone(),
161                ))
162            }
163            "Change crosswalks" => Transition::Replace(
164                super::crosswalks::CrosswalkEditor::new_state(ctx, app, self.id),
165            ),
166            _ => unreachable!(),
167        }
168    }
169
170    fn on_mouseover(&mut self, ctx: &mut EventCtx, _: &mut App) {
171        self.selected_sign = None;
172        if let Some(pt) = ctx.canvas.get_cursor_in_map_space() {
173            for (r, (octagon, _)) in &self.geom {
174                if octagon.contains_pt(pt) {
175                    self.selected_sign = Some(*r);
176                    break;
177                }
178            }
179        }
180    }
181
182    fn other_event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
183        ctx.canvas_movement();
184
185        if let Some(r) = self.selected_sign {
186            let mut sign = app.primary.map.get_stop_sign(self.id).clone();
187            let label = if sign.roads[&r].must_stop {
188                "remove stop sign"
189            } else {
190                "add stop sign"
191            };
192            if app.per_obj.left_click(ctx, label) {
193                sign.flip_sign(r);
194
195                let mut edits = app.primary.map.get_edits().clone();
196                edits
197                    .commands
198                    .push(app.primary.map.edit_intersection_cmd(self.id, |new| {
199                        new.control = EditIntersectionControl::StopSign(sign);
200                    }));
201                apply_map_edits(ctx, app, edits);
202                return Transition::Replace(StopSignEditor::new_state(
203                    ctx,
204                    app,
205                    self.id,
206                    self.mode.clone(),
207                ));
208            }
209        }
210
211        Transition::Keep
212    }
213
214    fn draw(&self, g: &mut GfxCtx, app: &App) {
215        let map = &app.primary.map;
216        let sign = map.get_stop_sign(self.id);
217
218        let mut batch = GeomBatch::new();
219
220        for (r, (octagon, pole)) in &self.geom {
221            // The intersection will already draw enabled stop signs
222            if Some(*r) == self.selected_sign {
223                batch.push(app.cs.perma_selected_object, octagon.clone());
224                if !sign.roads[r].must_stop {
225                    batch.push(app.cs.stop_sign_pole.alpha(0.6), pole.clone());
226                }
227            } else if !sign.roads[r].must_stop {
228                batch.push(app.cs.stop_sign.alpha(0.6), octagon.clone());
229                batch.push(app.cs.stop_sign_pole.alpha(0.6), pole.clone());
230            }
231        }
232
233        batch.draw(g);
234
235        if let Some(r) = self.selected_sign {
236            let mut osd = Text::new();
237            osd.add_appended(vec![
238                Line("Stop sign for "),
239                Line(
240                    app.primary
241                        .map
242                        .get_r(r)
243                        .get_name(app.opts.language.as_ref()),
244                )
245                .underlined(),
246            ]);
247            CommonState::draw_custom_osd(g, app, osd);
248        } else {
249            CommonState::draw_osd(g, app);
250        }
251    }
252}