ltn/pages/design_ltn/
shortcuts.rs

1use geom::Distance;
2use map_model::{PathV2, RoadID};
3use widgetry::mapspace::{World, WorldOutcome};
4use widgetry::{Color, EventCtx, GeomBatch, Key, Line, Text, TextExt, Widget};
5
6use super::{road_name, EditMode, EditOutcome, Obj};
7use crate::render::colors;
8use crate::{App, Neighbourhood};
9
10pub struct FocusedRoad {
11    pub r: RoadID,
12    pub paths: Vec<PathV2>,
13    pub current_idx: usize,
14}
15
16pub fn widget(ctx: &mut EventCtx, app: &App, focus: Option<&FocusedRoad>) -> Widget {
17    match focus {
18        Some(focus) => Widget::col(vec![
19            format!(
20                "{} possible shortcuts cross {}",
21                focus.paths.len(),
22                app.per_map
23                    .map
24                    .get_r(focus.r)
25                    .get_name(app.opts.language.as_ref()),
26            )
27            .text_widget(ctx),
28            Widget::row(vec![
29                ctx.style()
30                    .btn_prev()
31                    .disabled(focus.current_idx == 0)
32                    .hotkey(Key::LeftArrow)
33                    .build_widget(ctx, "previous shortcut"),
34                Text::from(
35                    Line(format!("{}/{}", focus.current_idx + 1, focus.paths.len())).secondary(),
36                )
37                .into_widget(ctx)
38                .centered_vert(),
39                ctx.style()
40                    .btn_next()
41                    .disabled(focus.current_idx == focus.paths.len() - 1)
42                    .hotkey(Key::RightArrow)
43                    .build_widget(ctx, "next shortcut"),
44            ]),
45        ]),
46        None => Widget::nothing(),
47    }
48}
49
50pub fn make_world(
51    ctx: &mut EventCtx,
52    app: &App,
53    neighbourhood: &Neighbourhood,
54    focus: &Option<FocusedRoad>,
55) -> World<Obj> {
56    let map = &app.per_map.map;
57    let mut world = World::new();
58    let focused_road = focus.as_ref().map(|f| f.r);
59
60    for r in &neighbourhood.interior_roads {
61        let road = map.get_r(*r);
62        if focused_road == Some(*r) {
63            let mut batch = GeomBatch::new();
64            batch.push(
65                Color::RED,
66                road.get_thick_polygon().to_outline(Distance::meters(3.0)),
67            );
68
69            world
70                .add(Obj::Road(*r))
71                .hitbox(road.get_thick_polygon())
72                .draw(batch)
73                .build(ctx);
74        } else {
75            world
76                .add(Obj::Road(*r))
77                .hitbox(road.get_thick_polygon())
78                .drawn_in_master_batch()
79                .hover_color(colors::HOVER)
80                .tooltip(Text::from(format!(
81                    "{} possible shortcuts cross {}",
82                    neighbourhood.shortcuts.count_per_road.get(*r),
83                    road_name(app, road)
84                )))
85                .clickable()
86                .build(ctx);
87        }
88    }
89
90    if let Some(ref focus) = focus {
91        let mut draw_path = GeomBatch::new();
92        let path = &focus.paths[focus.current_idx];
93        let color = app.cs.good_to_bad_red.0.last().unwrap().alpha(0.8);
94
95        match path.trace_v2(&app.per_map.map) {
96            Ok(poly) => {
97                draw_path.push(color, poly);
98            }
99            Err(_) => {
100                draw_path.extend(color, path.trace_all_polygons(&app.per_map.map));
101            }
102        }
103
104        let first_pt = path.get_req().start.pt(&app.per_map.map);
105        let last_pt = path.get_req().end.pt(&app.per_map.map);
106        draw_path.append(map_gui::tools::start_marker(ctx, first_pt, 2.0));
107        draw_path.append(map_gui::tools::goal_marker(ctx, last_pt, 2.0));
108
109        world.draw_master_batch(ctx, draw_path);
110    } else {
111        world.draw_master_batch(ctx, neighbourhood.shortcuts.draw_heatmap(app));
112    }
113
114    world.initialize_hover(ctx);
115    world
116}
117
118pub fn handle_world_outcome(
119    app: &mut App,
120    outcome: WorldOutcome<Obj>,
121    neighbourhood: &Neighbourhood,
122) -> EditOutcome {
123    match outcome {
124        WorldOutcome::ClickedObject(Obj::Road(r)) => {
125            let subset = neighbourhood.shortcuts.subset(neighbourhood, r);
126            if subset.paths.is_empty() {
127                EditOutcome::Nothing
128            } else {
129                app.session.edit_mode = EditMode::Shortcuts(Some(FocusedRoad {
130                    r,
131                    paths: subset.paths,
132                    current_idx: 0,
133                }));
134                EditOutcome::UpdatePanelAndWorld
135            }
136        }
137        WorldOutcome::ClickedFreeSpace(_) => {
138            app.session.edit_mode = EditMode::Shortcuts(None);
139            EditOutcome::UpdatePanelAndWorld
140        }
141        _ => EditOutcome::Nothing,
142    }
143}