1use std::collections::BTreeSet;
2
3use enumset::EnumSet;
4use maplit::btreeset;
5
6use map_gui::tools::{checkbox_per_mode, intersections_from_roads, ColorDiscrete};
7use map_model::{AccessRestrictions, CommonEndpoint, PathConstraints, RoadID};
8use synthpop::TripMode;
9use widgetry::mapspace::ToggleZoomed;
10use widgetry::{
11 Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State, Text,
12 VerticalAlignment, Widget,
13};
14
15use crate::app::{App, Transition};
16use crate::common::CommonState;
17use crate::common::RoadSelector;
18use crate::edit::apply_map_edits;
19
20pub struct ZoneEditor {
21 panel: Panel,
22 selector: RoadSelector,
23 allow_through_traffic: BTreeSet<TripMode>,
24 draw: ToggleZoomed,
25
26 orig_members: BTreeSet<RoadID>,
27}
28
29impl ZoneEditor {
30 pub fn new_state(ctx: &mut EventCtx, app: &mut App, start: RoadID) -> Box<dyn State<App>> {
31 let start = app.primary.map.get_r(start);
32 let members = if let Some(z) = start.get_zone(&app.primary.map) {
33 z.members.clone()
34 } else {
35 btreeset! { start.id }
37 };
38 let allow_through_traffic = start
39 .access_restrictions
40 .allow_through_traffic
41 .into_iter()
42 .map(TripMode::from_constraints)
43 .collect();
44
45 let (draw, legend) = draw_zone(ctx, app, &members);
46 let orig_members = members.clone();
47 let selector = RoadSelector::new(ctx, app, members);
48
49 Box::new(ZoneEditor {
50 panel: Panel::new_builder(Widget::col(vec![
51 Line("Editing restricted access zone")
52 .small_heading()
53 .into_widget(ctx),
54 selector.make_controls(ctx).named("selector"),
55 legend,
56 make_instructions(ctx, &allow_through_traffic).named("instructions"),
57 checkbox_per_mode(ctx, app, &allow_through_traffic),
58 Widget::custom_row(vec![
59 ctx.style()
60 .btn_solid_primary
61 .text("Apply")
62 .hotkey(Key::Enter)
63 .build_def(ctx),
64 ctx.style()
65 .btn_solid_destructive
66 .text("Cancel")
67 .hotkey(Key::Escape)
68 .build_def(ctx),
69 ])
70 .evenly_spaced(),
71 ]))
72 .aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
73 .build(ctx),
74 orig_members,
75 selector,
76 allow_through_traffic,
77 draw,
78 })
79 }
80}
81
82impl State<App> for ZoneEditor {
84 fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
85 match self.panel.event(ctx) {
86 Outcome::Clicked(x) => match x.as_ref() {
87 "Apply" => {
88 let mut edits = app.primary.map.get_edits().clone();
89
90 for r in self.orig_members.difference(&self.selector.roads) {
92 edits
93 .commands
94 .push(app.primary.map.edit_road_cmd(*r, |new| {
95 new.access_restrictions = AccessRestrictions::new();
96 }));
97 }
98
99 let mut allow_through_traffic = self
100 .allow_through_traffic
101 .iter()
102 .map(|m| m.to_constraints())
103 .collect::<EnumSet<_>>();
104 allow_through_traffic.insert(PathConstraints::Train);
107 let new_access_restrictions = AccessRestrictions {
108 allow_through_traffic,
109 };
110 for r in &self.selector.roads {
111 let old_access_restrictions =
112 app.primary.map.get_r(*r).access_restrictions.clone();
113 if old_access_restrictions != new_access_restrictions {
114 edits
115 .commands
116 .push(app.primary.map.edit_road_cmd(*r, |new| {
117 new.access_restrictions = new_access_restrictions.clone();
118 }));
119 }
120 }
121
122 apply_map_edits(ctx, app, edits);
123 return Transition::Pop;
124 }
125 "Cancel" => {
126 return Transition::Pop;
127 }
128 x => {
129 if self.selector.event(ctx, app, Some(x)) {
130 let new_controls = self.selector.make_controls(ctx);
131 self.panel.replace(ctx, "selector", new_controls);
132 self.draw = draw_zone(ctx, app, &self.selector.roads).0;
133 }
134 }
135 },
136 Outcome::Changed(_) => {
137 let mut new_allow_through_traffic = BTreeSet::new();
138 for m in TripMode::all() {
139 if self.panel.is_checked(m.ongoing_verb()) {
140 new_allow_through_traffic.insert(m);
141 }
142 }
143 let instructions = make_instructions(ctx, &new_allow_through_traffic);
144 self.panel.replace(ctx, "instructions", instructions);
145 self.allow_through_traffic = new_allow_through_traffic;
146 }
147 _ => {
148 if self.selector.event(ctx, app, None) {
149 let new_controls = self.selector.make_controls(ctx);
150 self.panel.replace(ctx, "selector", new_controls);
151 self.draw = draw_zone(ctx, app, &self.selector.roads).0;
152 }
153 }
154 }
155
156 Transition::Keep
157 }
158
159 fn draw(&self, g: &mut GfxCtx, app: &App) {
160 self.draw.draw(g);
162 self.panel.draw(g);
163 self.selector.draw(g, app, false);
164 CommonState::draw_osd(g, app);
165 }
166}
167
168fn draw_zone(ctx: &mut EventCtx, app: &App, members: &BTreeSet<RoadID>) -> (ToggleZoomed, Widget) {
169 let mut colorer = ColorDiscrete::new(
170 app,
171 vec![
172 ("restricted road", Color::CYAN),
173 ("entrance/exit", Color::BLUE),
174 ],
175 );
176 let map = &app.primary.map;
177 for r in members {
178 let r = map.get_r(*r);
179 colorer.add_r(r.id, "restricted road");
180 for next in map.get_next_roads(r.id) {
181 if !members.contains(&next) {
182 if let CommonEndpoint::One(i) = r.common_endpoint(map.get_r(next)) {
183 colorer.add_i(i, "entrance/exit");
184 }
185 }
186 }
187 }
188 for i in intersections_from_roads(members, &app.primary.map) {
189 colorer.add_i(i, "restricted road");
190 }
191 colorer.build(ctx)
192}
193
194fn make_instructions(ctx: &mut EventCtx, allow_through_traffic: &BTreeSet<TripMode>) -> Widget {
195 if allow_through_traffic == &TripMode::all().into_iter().collect() {
196 Text::from(
197 "Through-traffic is allowed for everyone, meaning this is just a normal public road. \
198 Would you like to restrict it?",
199 )
200 .wrap_to_pct(ctx, 30)
201 .into_widget(ctx)
202 } else {
203 Line("Trips may start or end in this zone, but through-traffic is only allowed for:")
204 .into_widget(ctx)
205 }
206}