ltn/pages/design_ltn/
filters.rs1use map_model::{DiagonalFilter, FilterType, RoadFilter};
2use widgetry::mapspace::{World, WorldOutcome};
3use widgetry::tools::open_browser;
4use widgetry::{lctrl, EventCtx, Key, Text, Transition};
5
6use super::{modals, road_name, EditOutcome, Obj};
7use crate::render::colors;
8use crate::{redraw_all_icons, App, Neighbourhood};
9
10pub fn make_world(ctx: &mut EventCtx, app: &App, neighbourhood: &Neighbourhood) -> World<Obj> {
13 let map = &app.per_map.map;
14 let mut world = World::new();
15
16 for r in &neighbourhood.interior_roads {
17 let road = map.get_r(*r);
18 world
19 .add(Obj::Road(*r))
20 .hitbox(road.get_thick_polygon())
21 .drawn_in_master_batch()
22 .hover_color(colors::HOVER)
23 .tooltip(Text::from(format!(
24 "{} possible shortcuts cross {}",
25 neighbourhood.shortcuts.count_per_road.get(*r),
26 road_name(app, road)
27 )))
28 .hotkey(lctrl(Key::D), "debug")
29 .clickable()
30 .build(ctx);
31 }
32
33 for i in &neighbourhood.interior_intersections {
34 world
35 .add(Obj::Intersection(*i))
36 .hitbox(map.get_i(*i).polygon.clone())
37 .drawn_in_master_batch()
38 .hover_color(colors::HOVER)
39 .tooltip(Text::from(format!(
40 "{} possible shortcuts cross this intersection",
41 neighbourhood.shortcuts.count_per_intersection.get(*i)
42 )))
43 .clickable()
44 .hotkey(lctrl(Key::D), "debug")
45 .build(ctx);
46 }
47
48 world.initialize_hover(ctx);
49 world
50}
51
52pub fn handle_world_outcome(
53 ctx: &mut EventCtx,
54 app: &mut App,
55 outcome: WorldOutcome<Obj>,
56) -> EditOutcome {
57 let map = &app.per_map.map;
58 match outcome {
59 WorldOutcome::ClickedObject(Obj::Road(r)) => {
60 let road = map.get_r(r);
61 if road.is_deadend_for_driving(map) {
63 return EditOutcome::error(ctx, "You can't filter a dead-end");
64 }
65
66 let cursor_pt = ctx.canvas.get_cursor_in_map_space().unwrap();
70 let pt_on_line = road.center_pts.project_pt(cursor_pt);
71 let (distance, _) = road.center_pts.dist_along_of_point(pt_on_line).unwrap();
72
73 if road.oneway_for_driving().is_some() {
74 if app.session.layers.autofix_one_ways {
75 modals::fix_oneway_and_add_filter(ctx, app, &[(r, distance)]);
76 return EditOutcome::UpdateAll;
77 }
78
79 return EditOutcome::Transition(Transition::Push(
80 modals::ResolveOneWayAndFilter::new_state(ctx, vec![(r, distance)]),
81 ));
82 }
83
84 let mut edits = map.get_edits().clone();
85 if map.get_r(r).modal_filter.is_some() {
86 edits.commands.push(map.edit_road_cmd(r, |new| {
87 new.modal_filter = None;
88 }));
89 } else {
90 let mut filter_type = app.session.filter_type;
91
92 if filter_type != FilterType::BusGate
93 && !app.per_map.map.get_bus_routes_on_road(r).is_empty()
94 {
95 if app.session.layers.autofix_bus_gates {
96 filter_type = FilterType::BusGate;
97 } else {
98 return EditOutcome::Transition(Transition::Push(
101 modals::ResolveBusGate::new_state(ctx, app, vec![(r, distance)]),
102 ));
103 }
104 }
105
106 edits.commands.push(map.edit_road_cmd(r, |new| {
107 new.modal_filter = Some(RoadFilter::new(distance, filter_type));
108 }));
109 }
110 app.apply_edits(edits);
111 redraw_all_icons(ctx, app);
112 EditOutcome::UpdateAll
113 }
114 WorldOutcome::ClickedObject(Obj::Intersection(i)) => {
115 let mut edits = map.get_edits().clone();
116 edits
117 .commands
118 .extend(DiagonalFilter::cycle_through_alternatives(
119 &app.per_map.map,
120 i,
121 app.session.filter_type,
122 ));
123 app.apply_edits(edits);
124 redraw_all_icons(ctx, app);
125 EditOutcome::UpdateAll
126 }
127 WorldOutcome::Keypress("debug", Obj::Intersection(i)) => {
128 open_browser(app.per_map.map.get_i(i).orig_id.to_string());
129 EditOutcome::Nothing
130 }
131 WorldOutcome::Keypress("debug", Obj::Road(r)) => {
132 open_browser(app.per_map.map.get_r(r).orig_id.osm_way_id.to_string());
133 EditOutcome::Nothing
134 }
135 _ => EditOutcome::Nothing,
136 }
137}