1use std::collections::BTreeSet;
2
3use geom::{Distance, Polygon};
4use map_gui::tools::grey_out_map;
5use map_model::{FilterType, RoadFilter, RoadID};
6use osm2streets::{Direction, LaneSpec};
7use widgetry::{
8 Color, ControlState, DrawBaselayer, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome, Panel,
9 RewriteColor, State, Text, Texture, Toggle, Widget,
10};
11
12use crate::{redraw_all_icons, render, App, Transition};
13
14pub struct ResolveOneWayAndFilter {
15 panel: Panel,
16 roads: Vec<(RoadID, Distance)>,
17}
18
19impl ResolveOneWayAndFilter {
20 pub fn new_state(ctx: &mut EventCtx, roads: Vec<(RoadID, Distance)>) -> Box<dyn State<App>> {
21 let mut txt = Text::new();
22 txt.add_line(Line("Warning").small_heading());
23 txt.add_line("A modal filter cannot be placed on a one-way street.");
24 txt.add_line("");
25 txt.add_line("You can make the street two-way first, then place a filter.");
26
27 let panel = Panel::new_builder(Widget::col(vec![
28 txt.into_widget(ctx),
29 Toggle::checkbox(ctx, "Don't show this warning again", None, true),
30 ctx.style().btn_solid_primary.text("OK").build_def(ctx),
31 ]))
32 .build(ctx);
33
34 Box::new(Self { panel, roads })
35 }
36}
37
38impl State<App> for ResolveOneWayAndFilter {
39 fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
40 if let Outcome::Clicked(_) = self.panel.event(ctx) {
41 app.session.layers.autofix_one_ways =
43 self.panel.is_checked("Don't show this warning again");
44
45 fix_oneway_and_add_filter(ctx, app, &self.roads);
46
47 return Transition::Multi(vec![Transition::Pop, Transition::Recreate]);
48 }
49 Transition::Keep
50 }
51
52 fn draw_baselayer(&self) -> DrawBaselayer {
53 DrawBaselayer::PreviousState
54 }
55
56 fn draw(&self, g: &mut GfxCtx, app: &App) {
57 grey_out_map(g, app);
58 self.panel.draw(g);
59 }
60}
61
62pub fn fix_oneway_and_add_filter(ctx: &mut EventCtx, app: &mut App, roads: &[(RoadID, Distance)]) {
63 let driving_side = app.per_map.map.get_config().driving_side;
64 let mut edits = app.per_map.map.get_edits().clone();
65 for (r, dist) in roads {
66 edits
67 .commands
68 .push(app.per_map.map.edit_road_cmd(*r, |new| {
69 LaneSpec::toggle_road_direction(&mut new.lanes_ltr, driving_side);
70 if LaneSpec::oneway_for_driving(&new.lanes_ltr) == Some(Direction::Back) {
73 LaneSpec::toggle_road_direction(&mut new.lanes_ltr, driving_side);
74 }
75 new.modal_filter = Some(RoadFilter::new(*dist, app.session.filter_type));
76 }));
77 }
78 app.apply_edits(edits);
79 redraw_all_icons(ctx, app);
80}
81
82pub struct ResolveBusGate {
83 panel: Panel,
84 roads: Vec<(RoadID, Distance)>,
85}
86
87impl ResolveBusGate {
88 pub fn new_state(
89 ctx: &mut EventCtx,
90 app: &mut App,
91 roads: Vec<(RoadID, Distance)>,
92 ) -> Box<dyn State<App>> {
93 app.session.layers.show_bus_routes(ctx, &app.cs, None);
96
97 let mut txt = Text::new();
98 txt.add_line(Line("Warning").small_heading());
99 txt.add_line("The following bus routes cross this road. Adding a regular modal filter would block them.");
100 txt.add_line("");
101
102 let mut routes = BTreeSet::new();
103 for (r, _) in &roads {
104 routes.extend(app.per_map.map.get_bus_routes_on_road(*r));
105 }
106 for route in routes {
107 txt.add_line(format!("- {route}"));
108 }
109
110 txt.add_line("");
111 txt.add_line("You can use a bus gate instead.");
112
113 let panel = Panel::new_builder(Widget::col(vec![
114 txt.into_widget(ctx),
115 Toggle::checkbox(ctx, "Don't show this warning again", None, true),
116 ctx.style().btn_solid_primary.text("OK").build_def(ctx),
117 ]))
118 .build(ctx);
119
120 Box::new(Self { panel, roads })
121 }
122}
123
124impl State<App> for ResolveBusGate {
125 fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
126 if let Outcome::Clicked(_) = self.panel.event(ctx) {
127 app.session.layers.autofix_bus_gates =
129 self.panel.is_checked("Don't show this warning again");
130 app.session.layers.show_bus_routes(ctx, &app.cs, None);
132
133 let mut edits = app.per_map.map.get_edits().clone();
134 for (r, dist) in self.roads.drain(..) {
135 edits.commands.push(app.per_map.map.edit_road_cmd(r, |new| {
136 new.modal_filter = Some(RoadFilter::new(dist, FilterType::BusGate));
137 }));
138 }
139 app.apply_edits(edits);
140 redraw_all_icons(ctx, app);
141
142 return Transition::Multi(vec![Transition::Pop, Transition::Recreate]);
143 }
144 Transition::Keep
145 }
146
147 fn draw_baselayer(&self) -> DrawBaselayer {
148 DrawBaselayer::PreviousState
149 }
150
151 fn draw(&self, g: &mut GfxCtx, app: &App) {
152 grey_out_map(g, app);
153 self.panel.draw(g);
154 }
155}
156
157pub struct ChangeFilterType {
158 panel: Panel,
159}
160
161impl ChangeFilterType {
162 pub fn new_state(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
163 let filter = |ft: FilterType, hotkey: Key, name: &str| {
164 ctx.style()
165 .btn_solid_primary
166 .icon_text(render::filter_svg_path(ft), name)
167 .image_color(
168 RewriteColor::Change(render::filter_hide_color(ft), Color::CLEAR),
169 ControlState::Default,
170 )
171 .image_color(
172 RewriteColor::Change(render::filter_hide_color(ft), Color::CLEAR),
173 ControlState::Disabled,
174 )
175 .disabled(app.session.filter_type == ft)
176 .hotkey(hotkey)
177 .build_def(ctx)
178 };
179
180 let panel = Panel::new_builder(Widget::col(vec![
181 Widget::row(vec![
182 Line("Choose a modal filter to place on streets")
183 .small_heading()
184 .into_widget(ctx),
185 ctx.style().btn_close_widget(ctx),
186 ]),
187 Widget::row(vec![
188 Widget::col(vec![
189 filter(
190 FilterType::WalkCycleOnly,
191 Key::Num1,
192 "Walking/cycling only",
193 ),
194 filter(FilterType::NoEntry, Key::Num2, "No entry"),
195 filter(FilterType::BusGate, Key::Num3, "Bus gate"),
196 filter(FilterType::SchoolStreet, Key::Num4, "School street"),
197 ]),
198 Widget::vertical_separator(ctx),
199 Widget::col(vec![
200 GeomBatch::from(vec![
201 (match app.session.filter_type {
202 FilterType::WalkCycleOnly => Texture(1),
203 FilterType::NoEntry => Texture(2),
204 FilterType::BusGate => Texture(3),
205 FilterType::SchoolStreet => Texture(4),
206 }, Polygon::rectangle(crate::SPRITE_WIDTH as f64, crate::SPRITE_HEIGHT as f64))
209 ]).into_widget(ctx),
210 Text::from(Line(match app.session.filter_type {
212 FilterType::WalkCycleOnly => "A physical barrier that only allows people walking, cycling, and rolling to pass. Often planters or bollards. Larger vehicles cannot enter.",
213 FilterType::NoEntry => "An alternative sign to indicate vehicles are not allowed to enter the street. Only people walking, cycling, and rolling may pass through.",
214 FilterType::BusGate => "A bus gate sign and traffic cameras are installed to allow buses, pedestrians, and cyclists to pass. There is no physical barrier.",
215 FilterType::SchoolStreet => "A closure during school hours only. The barrier usually allows teachers and staff to access the school.",
216 })).wrap_to_pixels(ctx, crate::SPRITE_WIDTH as f64).into_widget(ctx),
217 ]),
218 ]),
219 ctx.style().btn_solid_primary.text("OK").hotkey(Key::Enter).build_def(ctx).centered_horiz(),
220 ]))
221 .build(ctx);
222 Box::new(Self { panel })
223 }
224}
225
226impl State<App> for ChangeFilterType {
227 fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
228 if let Outcome::Clicked(x) = self.panel.event(ctx) {
229 return match x.as_ref() {
230 "No entry" => {
231 app.session.filter_type = FilterType::NoEntry;
232 Transition::Replace(Self::new_state(ctx, app))
233 }
234 "Walking/cycling only" => {
235 app.session.filter_type = FilterType::WalkCycleOnly;
236 Transition::Replace(Self::new_state(ctx, app))
237 }
238 "Bus gate" => {
239 app.session.filter_type = FilterType::BusGate;
240 Transition::Replace(Self::new_state(ctx, app))
241 }
242 "School street" => {
243 app.session.filter_type = FilterType::SchoolStreet;
244 Transition::Replace(Self::new_state(ctx, app))
245 }
246 "close" | "OK" => Transition::Multi(vec![Transition::Pop, Transition::Recreate]),
247 _ => unreachable!(),
248 };
249 }
250
251 Transition::Keep
252 }
253
254 fn draw_baselayer(&self) -> DrawBaselayer {
255 DrawBaselayer::PreviousState
256 }
257
258 fn draw(&self, g: &mut GfxCtx, app: &App) {
259 grey_out_map(g, app);
260 self.panel.draw(g);
261 }
262}