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