game/sandbox/
turn_explorer.rs

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
13/// A tool to explore all of the turns from a single lane.
14pub struct TurnExplorer {
15    l: LaneID,
16    // 0 means all turns, otherwise one particular turn
17    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    // Since this is extremely localized and probably changing, not going to put this in
199    // ColorScheme.
200    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);