1use std::collections::BTreeSet;
2
3use crate::ID;
4use geom::ArrowCap;
5use map_gui::render::{DrawOptions, BIG_ARROW_THICKNESS};
6use map_model::{IntersectionCluster, IntersectionID};
7use widgetry::tools::PopupMsg;
8use widgetry::{
9 Color, DrawBaselayer, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line,
10 Panel, SimpleState, State, Text, TextExt, Toggle, VerticalAlignment, Widget,
11};
12
13use crate::app::{App, ShowEverything, Transition};
14use crate::common::CommonState;
15
16pub struct UberTurnPicker {
17 members: BTreeSet<IntersectionID>,
18}
19
20impl UberTurnPicker {
21 pub fn new_state(ctx: &mut EventCtx, app: &App, i: IntersectionID) -> Box<dyn State<App>> {
22 let mut members = BTreeSet::new();
23 if let Some(list) = IntersectionCluster::autodetect(i, &app.primary.map) {
24 members.extend(list);
25 } else {
26 members.insert(i);
27 }
28
29 let panel = Panel::new_builder(Widget::col(vec![
30 Widget::row(vec![
31 Line("Select multiple intersections")
32 .small_heading()
33 .into_widget(ctx),
34 ctx.style().btn_close_widget(ctx),
35 ]),
36 ctx.style()
37 .btn_outline
38 .text("View uber-turns")
39 .hotkey(Key::Enter)
40 .build_def(ctx),
41 ctx.style()
42 .btn_outline
43 .text("Detect all clusters")
44 .hotkey(Key::D)
45 .build_def(ctx),
46 ]))
47 .aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
48 .build(ctx);
49 <dyn SimpleState<_>>::new_state(panel, Box::new(UberTurnPicker { members }))
50 }
51}
52
53impl SimpleState<App> for UberTurnPicker {
54 fn on_click(
55 &mut self,
56 ctx: &mut EventCtx,
57 app: &mut App,
58 x: &str,
59 _: &mut Panel,
60 ) -> Transition {
61 match x {
62 "close" => Transition::Pop,
63 "View uber-turns" => {
64 if self.members.len() < 2 {
65 return Transition::Push(PopupMsg::new_state(
66 ctx,
67 "Error",
68 vec!["Select at least two intersections"],
69 ));
70 }
71 Transition::Replace(UberTurnViewer::new_state(
72 ctx,
73 app,
74 self.members.clone(),
75 0,
76 true,
77 ))
78 }
79 "Detect all clusters" => {
80 self.members.clear();
81 for ic in IntersectionCluster::find_all(&app.primary.map) {
82 self.members.extend(ic.members);
83 }
84 Transition::Keep
85 }
86 _ => unreachable!(),
87 }
88 }
89
90 fn on_mouseover(&mut self, ctx: &mut EventCtx, app: &mut App) {
91 app.primary.current_selection = app.mouseover_unzoomed_intersections(ctx);
92 }
93 fn other_event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
94 ctx.canvas_movement();
95 if let Some(ID::Intersection(i)) = app.primary.current_selection {
96 if !self.members.contains(&i) && app.per_obj.left_click(ctx, "add this intersection") {
97 self.members.insert(i);
98 } else if self.members.contains(&i)
99 && app.per_obj.left_click(ctx, "remove this intersection")
100 {
101 self.members.remove(&i);
102 }
103 }
104 Transition::Keep
105 }
106
107 fn draw(&self, g: &mut GfxCtx, app: &App) {
108 CommonState::draw_osd(g, app);
109
110 let mut batch = GeomBatch::new();
111 for i in &self.members {
112 batch.push(
113 Color::RED.alpha(0.8),
114 app.primary.map.get_i(*i).polygon.clone(),
115 );
116 }
117 let draw = g.upload(batch);
118 g.redraw(&draw);
119 }
120}
121
122struct UberTurnViewer {
123 draw: Drawable,
124 ic: IntersectionCluster,
125 idx: usize,
126 legal_turns: bool,
127}
128
129impl UberTurnViewer {
130 pub fn new_state(
131 ctx: &mut EventCtx,
132 app: &mut App,
133 members: BTreeSet<IntersectionID>,
134 idx: usize,
135 legal_turns: bool,
136 ) -> Box<dyn State<App>> {
137 app.primary.current_selection = None;
138 let map = &app.primary.map;
139
140 let (ic1, ic2) = IntersectionCluster::new(members, map);
141 let ic = if legal_turns { ic1 } else { ic2 };
142
143 let mut batch = GeomBatch::new();
144 for i in &ic.members {
145 batch.push(Color::BLUE.alpha(0.5), map.get_i(*i).polygon.clone());
146 }
147 if !ic.uber_turns.is_empty() {
148 let ut = &ic.uber_turns[idx];
149 batch.push(
150 Color::RED,
151 ut.geom(map)
152 .make_arrow(BIG_ARROW_THICKNESS, ArrowCap::Triangle),
153 );
154 }
155
156 let panel = Panel::new_builder(Widget::col(vec![
157 Widget::row(vec![
158 Line("Uber-turn viewer").small_heading().into_widget(ctx),
159 Widget::vert_separator(ctx, 50.0),
160 ctx.style()
161 .btn_prev()
162 .disabled(idx == 0)
163 .hotkey(Key::LeftArrow)
164 .build_widget(ctx, "previous uber-turn"),
165 Text::from(Line(format!("{}/{}", idx + 1, ic.uber_turns.len())).secondary())
166 .into_widget(ctx)
167 .centered_vert(),
168 ctx.style()
169 .btn_next()
170 .disabled(ic.uber_turns.is_empty() || idx == ic.uber_turns.len() - 1)
171 .hotkey(Key::RightArrow)
172 .build_widget(ctx, "next uber-turn"),
173 ctx.style().btn_close_widget(ctx),
174 ]),
175 Widget::row(vec![
176 Toggle::choice(
177 ctx,
178 "legal / illegal movements",
179 "legal",
180 "illegal",
181 None,
182 legal_turns,
183 ),
184 "movements".text_widget(ctx),
185 ]),
186 ]))
187 .aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
188 .build(ctx);
189 <dyn SimpleState<_>>::new_state(
190 panel,
191 Box::new(UberTurnViewer {
192 draw: ctx.upload(batch),
193 ic,
194 idx,
195 legal_turns,
196 }),
197 )
198 }
199}
200
201impl SimpleState<App> for UberTurnViewer {
202 fn on_click(
203 &mut self,
204 ctx: &mut EventCtx,
205 app: &mut App,
206 x: &str,
207 _: &mut Panel,
208 ) -> Transition {
209 match x {
210 "close" => Transition::Pop,
211 "previous uber-turn" => Transition::Replace(UberTurnViewer::new_state(
212 ctx,
213 app,
214 self.ic.members.clone(),
215 self.idx - 1,
216 self.legal_turns,
217 )),
218 "next uber-turn" => Transition::Replace(UberTurnViewer::new_state(
219 ctx,
220 app,
221 self.ic.members.clone(),
222 self.idx + 1,
223 self.legal_turns,
224 )),
225 _ => unreachable!(),
226 }
227 }
228 fn panel_changed(
229 &mut self,
230 ctx: &mut EventCtx,
231 app: &mut App,
232 panel: &mut Panel,
233 ) -> Option<Transition> {
234 Some(Transition::Replace(UberTurnViewer::new_state(
235 ctx,
236 app,
237 self.ic.members.clone(),
238 0,
239 panel.is_checked("legal / illegal movements"),
240 )))
241 }
242
243 fn other_event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition {
244 ctx.canvas_movement();
245 Transition::Keep
246 }
247
248 fn draw_baselayer(&self) -> DrawBaselayer {
249 DrawBaselayer::Custom
250 }
251
252 fn draw(&self, g: &mut GfxCtx, app: &App) {
253 let mut opts = DrawOptions::new();
254 opts.suppress_traffic_signal_details
255 .extend(self.ic.members.clone());
256 app.draw(g, opts, &ShowEverything::new());
257
258 g.redraw(&self.draw);
259 }
260}