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
21pub struct StopSignEditor {
24 id: IntersectionID,
25 mode: GameplayMode,
26 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 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}