1use geom::{ArrowCap, Distance};
2use map_gui::render::{DrawOptions, BIG_ARROW_THICKNESS};
3use map_gui::AppLike;
4use map_model::{LaneID, PathConstraints, TurnType};
5use widgetry::tools::ColorLegend;
6use widgetry::{
7 Color, DrawBaselayer, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome,
8 Panel, State, Text, TextExt, VerticalAlignment, Widget,
9};
10
11use crate::app::{App, Transition};
12
13pub struct TurnExplorer {
15 l: LaneID,
16 idx: usize,
18 panel: Panel,
19}
20
21impl TurnExplorer {
22 pub fn new_state(ctx: &mut EventCtx, app: &App, l: LaneID) -> Box<dyn State<App>> {
23 Box::new(TurnExplorer {
24 l,
25 idx: 0,
26 panel: TurnExplorer::make_panel(ctx, app, l, 0),
27 })
28 }
29}
30
31impl State<App> for TurnExplorer {
32 fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
33 ctx.canvas_movement();
34
35 if let Outcome::Clicked(x) = self.panel.event(ctx) {
36 match x.as_ref() {
37 "close" => {
38 return Transition::Pop;
39 }
40 "previous turn" => {
41 self.idx -= 1;
42 self.panel = TurnExplorer::make_panel(ctx, app, self.l, self.idx);
43 }
44 "next turn" => {
45 self.idx += 1;
46 self.panel = TurnExplorer::make_panel(ctx, app, self.l, self.idx);
47 }
48 _ => unreachable!(),
49 }
50 }
51
52 Transition::Keep
53 }
54
55 fn draw_baselayer(&self) -> DrawBaselayer {
56 DrawBaselayer::Custom
57 }
58
59 fn draw(&self, g: &mut GfxCtx, app: &App) {
60 let mut opts = DrawOptions::new();
61 {
62 let l = app.map().get_l(self.l);
63 opts.suppress_traffic_signal_details.push(l.src_i);
64 opts.suppress_traffic_signal_details.push(l.dst_i);
65 }
66 app.draw_with_opts(g, opts);
67
68 if self.idx == 0 {
69 for turn in &app.map().get_turns_from_lane(self.l) {
70 g.draw_polygon(
71 TurnExplorer::color_turn_type(turn.turn_type).alpha(0.5),
72 turn.geom
73 .make_arrow(BIG_ARROW_THICKNESS, ArrowCap::Triangle),
74 );
75 }
76 } else {
77 let current = &app.map().get_turns_from_lane(self.l)[self.idx - 1];
78
79 let mut batch = GeomBatch::new();
80 for t in &app.map().get_i(current.id.parent).turns {
81 if current.conflicts_with(t) {
82 batch.extend(
83 CONFLICTING_TURN,
84 t.geom.dashed_arrow(
85 BIG_ARROW_THICKNESS,
86 Distance::meters(1.0),
87 Distance::meters(0.5),
88 ArrowCap::Triangle,
89 ),
90 );
91 }
92 }
93 batch.push(
94 CURRENT_TURN,
95 current
96 .geom
97 .make_arrow(BIG_ARROW_THICKNESS, ArrowCap::Triangle),
98 );
99 batch.draw(g);
100 }
101
102 self.panel.draw(g);
103 }
104}
105
106impl TurnExplorer {
107 fn make_panel(ctx: &mut EventCtx, app: &App, l: LaneID, idx: usize) -> Panel {
108 let turns = app.map().get_turns_from_lane(l);
109
110 let mut col = vec![Widget::row(vec![
111 Text::from(
112 Line(format!(
113 "Turns from {}",
114 app.map()
115 .get_parent(l)
116 .get_name(app.opts().language.as_ref())
117 ))
118 .small_heading(),
119 )
120 .into_widget(ctx),
121 Widget::vert_separator(ctx, 50.0),
122 ctx.style()
123 .btn_prev()
124 .disabled(idx == 0)
125 .hotkey(Key::LeftArrow)
126 .build_widget(ctx, "previous turn"),
127 Text::from(Line(format!("{}/{}", idx, turns.len())).secondary())
128 .into_widget(ctx)
129 .centered_vert(),
130 ctx.style()
131 .btn_next()
132 .disabled(idx == turns.len())
133 .hotkey(Key::RightArrow)
134 .build_widget(ctx, "next turn"),
135 ctx.style().btn_close_widget(ctx),
136 ])];
137 if idx == 0 {
138 if app.map().get_l(l).is_walkable() {
139 col.push(ColorLegend::row(
140 ctx,
141 TurnExplorer::color_turn_type(TurnType::Crosswalk),
142 "crosswalk",
143 ));
144 col.push(ColorLegend::row(
145 ctx,
146 TurnExplorer::color_turn_type(TurnType::UnmarkedCrossing),
147 "unmarked crossing",
148 ));
149 col.push(ColorLegend::row(
150 ctx,
151 TurnExplorer::color_turn_type(TurnType::SharedSidewalkCorner),
152 "sidewalk connection",
153 ));
154 } else {
155 col.push(ColorLegend::row(
156 ctx,
157 TurnExplorer::color_turn_type(TurnType::Straight),
158 "straight",
159 ));
160 col.push(ColorLegend::row(
161 ctx,
162 TurnExplorer::color_turn_type(TurnType::Right),
163 "right turn",
164 ));
165 col.push(ColorLegend::row(
166 ctx,
167 TurnExplorer::color_turn_type(TurnType::Left),
168 "left turn",
169 ));
170 col.push(ColorLegend::row(
171 ctx,
172 TurnExplorer::color_turn_type(TurnType::UTurn),
173 "U-turn",
174 ));
175 }
176 } else {
177 let (lt, lc, slow_lane) = turns[idx - 1].penalty(PathConstraints::Car, app.map());
178 let (vehicles, bike) = app
179 .primary
180 .sim
181 .target_lane_penalty(app.map().get_l(turns[idx - 1].id.dst));
182 col.push(
183 format!(
184 "Penalties: {} for lane types (assuming a car), {} for lane changing, {} for keeping to the slow lane, {} for vehicles, {} for slow bikes",
185 lt, lc, slow_lane, vehicles, bike
186 )
187 .text_widget(ctx),
188 );
189 col.push(ColorLegend::row(ctx, CURRENT_TURN, "current turn"));
190 col.push(ColorLegend::row(ctx, CONFLICTING_TURN, "conflicting turn"));
191 }
192
193 Panel::new_builder(Widget::col(col))
194 .aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
195 .build(ctx)
196 }
197
198 pub fn color_turn_type(t: TurnType) -> Color {
201 match t {
202 TurnType::SharedSidewalkCorner => Color::BLACK,
203 TurnType::Crosswalk => Color::WHITE,
204 TurnType::UnmarkedCrossing => Color::grey(0.5),
205 TurnType::Straight => Color::BLUE,
206 TurnType::Right => Color::GREEN,
207 TurnType::Left => Color::RED,
208 TurnType::UTurn => Color::PURPLE,
209 }
210 }
211}
212
213const CURRENT_TURN: Color = Color::GREEN;
214const CONFLICTING_TURN: Color = Color::RED.alpha(0.8);