ltn/pages/design_ltn/
shortcuts.rs1use 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}