1use geom::Distance;
2use map_model::{EditRoad, Map, RoadID};
3use osm2streets::RestrictionType;
4use widgetry::mapspace::{World, WorldOutcome};
5use widgetry::Color;
6use widgetry::{EventCtx, GeomBatch, Text, TextExt, Widget};
7
8use super::{road_name, EditMode, EditOutcome, Obj};
9use crate::logic::turn_restrictions::{
10 possible_destination_roads, restricted_destination_roads, FocusedTurns,
11};
12use crate::render::colors;
13use crate::{App, Neighbourhood};
14
15pub fn widget(ctx: &mut EventCtx, app: &App, focus: Option<&FocusedTurns>) -> Widget {
16 match focus {
17 Some(focus) => {
18 let road = app.per_map.map.get_r(focus.from_r);
19 let restricted = focus.restricted_t.len();
20 let permitted = focus.possible_t.len() - restricted;
21 Widget::col(vec![
22 format!("{} permitted and {} restricted", permitted, restricted,).text_widget(ctx),
23 format!("turns from {} ", road.get_name(app.opts.language.as_ref()))
24 .text_widget(ctx),
25 format!("at selected intersection").text_widget(ctx),
26 ])
27 }
28 None => Widget::nothing(),
29 }
30}
31
32pub fn make_world(
33 ctx: &mut EventCtx,
34 app: &App,
35 neighbourhood: &Neighbourhood,
36 focus: &Option<FocusedTurns>,
37) -> World<Obj> {
38 let mut world = World::new();
39
40 if focus.is_none() {
41 for r in [
43 &neighbourhood.perimeter_roads,
44 &neighbourhood.interior_roads,
45 &neighbourhood.connected_exterior_roads,
46 ]
47 .into_iter()
48 .flatten()
49 {
50 build_turn_restriction_hover_geom(*r, ctx, &mut world, app);
51 }
52 } else {
53 let focused_t = focus.as_ref().unwrap();
54 build_focused_turns_geom(focused_t, ctx, &mut world, app);
56 build_turn_options_geom(focused_t, ctx, &mut world, app);
58 }
59
60 world.initialize_hover(ctx);
61 world
62}
63
64fn build_turn_restriction_hover_geom(
66 r: RoadID,
67 ctx: &mut EventCtx,
68 world: &mut World<Obj>,
69 app: &App,
70) {
71 let map = &app.per_map.map;
72 let road = map.get_r(r);
73
74 let restricted_destinations = restricted_destination_roads(map, r, None);
77 let possible_destinations = possible_destination_roads(map, road.id, None);
78
79 let mut hover_batch = GeomBatch::new();
80 hover_batch.push(colors::HOVER, road.get_thick_polygon());
85
86 for possible_r in possible_destinations.clone() {
88 let possible_road = map.get_r(possible_r);
89 hover_batch.push(
90 colors::TURN_PERMITTED_DESTINATION,
91 possible_road.get_thick_polygon(),
92 );
93 }
94
95 for restricted_r in restricted_destinations.clone() {
97 let restricted_road = map.get_r(restricted_r);
98 hover_batch.push(
99 colors::TURN_RESTRICTED_DESTINATION,
100 restricted_road.get_thick_polygon(),
101 );
102 }
103
104 world
105 .add(Obj::Road(r))
106 .hitbox(road.get_thick_polygon())
107 .drawn_in_master_batch()
108 .draw_hovered(hover_batch)
109 .tooltip(Text::from(format!(
110 "Click to edit turn restrictions from {}",
111 road_name(app, road)
112 )))
113 .clickable()
114 .build(ctx);
115}
116
117fn build_focused_turns_geom(
119 focused_t: &FocusedTurns,
120 ctx: &mut EventCtx,
121 world: &mut World<Obj>,
122 app: &App,
123) {
124 let map = &app.per_map.map;
125 let mut batch = GeomBatch::new();
126 let from_road = map.get_r(focused_t.from_r);
127
128 batch.push(Color::grey(0.4).alpha(0.8), focused_t.hull.clone());
130
131 batch.push(
132 Color::grey(0.2),
133 focused_t.hull.to_outline(Distance::meters(2.0)),
134 );
135
136 batch.push(
138 colors::HOVER.alpha(1.0),
139 map.get_i(focused_t.i).polygon.clone(),
140 );
141 batch.push(
142 colors::HOVER.alpha(1.0),
143 map.get_i(focused_t.i)
144 .polygon
145 .to_outline(Distance::meters(3.0)),
146 );
147
148 world
150 .add(Obj::Intersection(focused_t.i))
151 .hitbox(focused_t.hull.clone())
152 .draw(batch.clone())
153 .draw_hovered(batch)
154 .zorder(1)
155 .tooltip(Text::from(format!(
156 "Edit restricted turn from {}",
157 road_name(app, from_road)
158 )))
159 .build(ctx);
160}
161
162fn build_turn_options_geom(
165 focused_t: &FocusedTurns,
166 ctx: &mut EventCtx,
167 world: &mut World<Obj>,
168 app: &App,
169) {
170 let map = &app.per_map.map;
171 let from_road = map.get_r(focused_t.from_r);
172 let from_road_name = road_name(app, from_road);
173
174 let mut batch = GeomBatch::new();
175 batch.push(
177 colors::HOVER.alpha(1.0),
178 from_road
179 .get_thick_polygon()
180 .to_outline(Distance::meters(3.0)),
181 );
182
183 world
184 .add(Obj::Road(focused_t.from_r))
185 .hitbox(from_road.get_thick_polygon())
186 .draw(batch.clone())
187 .draw_hovered(batch)
188 .zorder(2)
189 .tooltip(Text::from(format!(
190 "Edit restricted turn from {}",
191 road_name(app, from_road)
192 )))
193 .clickable()
194 .build(ctx);
195
196 for target_r in &focused_t.possible_t {
199 if !&focused_t.restricted_t.contains(target_r) && target_r != &focused_t.from_r {
200 build_individual_target_road_geom(
201 *target_r,
202 colors::TURN_PERMITTED_DESTINATION.alpha(1.0),
203 colors::TURN_RESTRICTED_DESTINATION.alpha(1.0),
204 3,
205 format!(
206 "Add new turn restriction from '{}' to '{}'",
207 from_road_name,
208 road_name(app, map.get_r(*target_r))
209 ),
210 focused_t,
211 ctx,
212 world,
213 map,
214 );
215 }
216 }
217
218 for target_r in &focused_t.restricted_t {
220 build_individual_target_road_geom(
221 *target_r,
222 colors::TURN_RESTRICTED_DESTINATION.alpha(1.0),
223 colors::TURN_PERMITTED_DESTINATION.alpha(1.0),
224 4,
225 format!(
226 "Remove turn restriction from '{}' to '{}'",
227 from_road_name,
228 road_name(app, map.get_r(*target_r))
229 ),
230 focused_t,
231 ctx,
232 world,
233 map,
234 );
235 }
236}
237
238fn build_individual_target_road_geom(
239 target_r: RoadID,
240 before_edit_color: Color,
241 post_edit_color: Color,
242 z_order: usize,
243 tooltip: String,
244 focused_t: &FocusedTurns,
245 ctx: &mut EventCtx,
246 world: &mut World<Obj>,
247 map: &Map,
248) {
249 if target_r == focused_t.from_r {
252 return;
253 }
254
255 let mut norm_batch = GeomBatch::new();
256 let mut hover_batch = GeomBatch::new();
257 let target_road = map.get_r(target_r);
258
259 norm_batch.push(
260 before_edit_color,
261 target_road
262 .get_thick_polygon()
263 .to_outline(Distance::meters(3.0)),
264 );
265 hover_batch.push(post_edit_color, target_road.get_thick_polygon());
266 world
267 .add(Obj::Road(target_r))
268 .hitbox(target_road.get_thick_polygon())
269 .draw(norm_batch)
270 .draw_hovered(hover_batch)
271 .zorder(z_order)
272 .tooltip(Text::from(tooltip))
273 .clickable()
274 .build(ctx);
275}
276
277pub fn handle_world_outcome(
278 ctx: &mut EventCtx,
279 app: &mut App,
280 outcome: WorldOutcome<Obj>,
281) -> EditOutcome {
282 match outcome {
283 WorldOutcome::ClickedObject(Obj::Road(r)) => {
284 let cursor_pt = ctx.canvas.get_cursor_in_map_space().unwrap();
292 debug!("click point {:?}", cursor_pt);
293
294 if let EditMode::TurnRestrictions(ref prev_selection) = app.session.edit_mode {
295 if prev_selection.is_some() {
296 let prev = prev_selection.as_ref().unwrap();
297 if r == prev.from_r {
298 debug!("The same road has been clicked on twice {:?}", r);
299 } else if prev.restricted_t.contains(&r) || prev.possible_t.contains(&r) {
300 let mut edits = app.per_map.map.get_edits().clone();
301 let erc = app.per_map.map.edit_road_cmd(prev.from_r, |new| {
303 handle_edited_turn_restrictions(new, prev, r)
304 });
305 debug!("erc={:?}", erc);
306 edits.commands.push(erc);
307 app.apply_edits(edits);
308
309 crate::redraw_all_icons(ctx, app);
311
312 app.session.edit_mode = EditMode::TurnRestrictions(None);
314 return EditOutcome::UpdateAll;
315 } else {
316 debug!(
318 "Two difference roads have been clicked on prev={:?}, new {:?}",
319 prev.from_r, r
320 );
321 }
322 } else {
323 debug!("No previous road selected. New selection {:?}", r);
324 }
325 }
326
327 app.session.edit_mode =
328 EditMode::TurnRestrictions(Some(FocusedTurns::new(r, cursor_pt, &app.per_map.map)));
329 debug!(
330 "TURN RESTRICTIONS: handle_world_outcome - Clicked on Road {:?}",
331 r
332 );
333 EditOutcome::UpdatePanelAndWorld
334 }
335 WorldOutcome::ClickedFreeSpace(_) => {
336 app.session.edit_mode = EditMode::TurnRestrictions(None);
337 debug!("TURN RESTRICTIONS: handle_world_outcome - Clicked on FreeSpace");
338 EditOutcome::UpdatePanelAndWorld
339 }
340 _ => EditOutcome::Nothing,
341 }
342}
343
344pub fn handle_edited_turn_restrictions(new: &mut EditRoad, ft: &FocusedTurns, target_r: RoadID) {
345 if ft.restricted_t.contains(&target_r) {
346 debug!(
347 "Remove existing banned turn from src={:?}, to dst {:?}",
348 ft.from_r, target_r
349 );
350 new.turn_restrictions.retain(|(_, r)| *r != target_r);
351 new.complicated_turn_restrictions
352 .retain(|(_, r)| *r != target_r);
353 } else if ft.possible_t.contains(&target_r) {
354 debug!(
355 "Create new banned turn from src={:?}, to dst {:?}",
356 ft.from_r, target_r
357 );
358 new.turn_restrictions
359 .push((RestrictionType::BanTurns, target_r));
360 } else {
361 debug!(
362 "Nothing to change src={:?}, to dst {:?}",
363 ft.from_r, target_r
364 );
365 }
366}