1mod filters;
2mod freehand_filters;
3mod modals;
4mod one_ways;
5mod page;
6mod shortcuts;
7mod speed_limits;
8pub mod turn_restrictions;
9
10use map_model::{IntersectionID, Road, RoadID};
11use widgetry::mapspace::{ObjectID, World};
12use widgetry::tools::{PolyLineLasso, PopupMsg};
13use widgetry::{EventCtx, Panel};
14
15use crate::{is_private, pages, App, Neighbourhood, Transition};
16
17use crate::logic::turn_restrictions::FocusedTurns;
18pub use page::DesignLTN;
19
20pub enum EditMode {
21 Filters,
22 FreehandFilters(PolyLineLasso),
23 Oneways,
24 Shortcuts(Option<shortcuts::FocusedRoad>),
26 SpeedLimits,
27 TurnRestrictions(Option<FocusedTurns>),
28}
29
30pub struct EditNeighbourhood {
31 pub world: World<Obj>,
33}
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
36pub enum Obj {
37 Road(RoadID),
38 Intersection(IntersectionID),
39}
40impl ObjectID for Obj {}
41
42pub enum EditOutcome {
43 Nothing,
44 UpdatePanelAndWorld,
46 UpdateAll,
48 Transition(Transition),
49}
50
51impl EditOutcome {
52 fn error(ctx: &mut EventCtx, msg: &str) -> Self {
53 Self::Transition(Transition::Push(PopupMsg::new_state(
54 ctx,
55 "Error",
56 vec![msg],
57 )))
58 }
59}
60
61impl EditNeighbourhood {
62 pub fn temporary() -> Self {
63 Self {
64 world: World::new(),
65 }
66 }
67
68 pub fn new(ctx: &mut EventCtx, app: &App, neighbourhood: &Neighbourhood) -> Self {
69 Self {
70 world: match &app.session.edit_mode {
71 EditMode::Filters => filters::make_world(ctx, app, neighbourhood),
72 EditMode::FreehandFilters(_) => World::new(),
73 EditMode::Oneways => one_ways::make_world(ctx, app, neighbourhood),
74 EditMode::Shortcuts(focus) => shortcuts::make_world(ctx, app, neighbourhood, focus),
75 EditMode::SpeedLimits => speed_limits::make_world(ctx, app, neighbourhood),
76 EditMode::TurnRestrictions(focus) => {
77 turn_restrictions::make_world(ctx, app, neighbourhood, focus)
78 }
79 },
80 }
81 }
82
83 pub fn event(
84 &mut self,
85 ctx: &mut EventCtx,
86 app: &mut App,
87 neighbourhood: &Neighbourhood,
88 ) -> EditOutcome {
89 if let EditMode::FreehandFilters(_) = app.session.edit_mode {
90 return freehand_filters::event(ctx, app, neighbourhood);
91 }
92
93 let outcome = self.world.event(ctx);
94 let outcome = match &app.session.edit_mode {
95 EditMode::Filters => filters::handle_world_outcome(ctx, app, outcome),
96 EditMode::FreehandFilters(_) => unreachable!(),
97 EditMode::Oneways => one_ways::handle_world_outcome(ctx, app, outcome),
98 EditMode::Shortcuts(_) => shortcuts::handle_world_outcome(app, outcome, neighbourhood),
99 EditMode::SpeedLimits => speed_limits::handle_world_outcome(app, outcome),
100 EditMode::TurnRestrictions(_) => {
101 turn_restrictions::handle_world_outcome(ctx, app, outcome)
102 }
103 };
104 if matches!(outcome, EditOutcome::Transition(_)) {
105 self.world.hack_unset_hovering();
106 }
107 outcome
108 }
109
110 pub fn handle_panel_action(
111 &mut self,
112 ctx: &mut EventCtx,
113 app: &mut App,
114 action: &str,
115 neighbourhood: &Neighbourhood,
116 panel: &mut Panel,
117 ) -> EditOutcome {
118 let id = neighbourhood.id;
119 match action {
120 "Adjust boundary" => EditOutcome::Transition(Transition::Replace(
121 if let Some(custom) = app.partitioning().custom_boundaries.get(&id).cloned() {
122 pages::FreehandBoundary::edit_existing(
123 ctx,
124 app,
125 custom.name.clone(),
126 id,
127 custom,
128 )
129 } else {
130 pages::SelectBoundary::new_state(ctx, app, id)
131 },
132 )),
133 "Per-resident route impact" => EditOutcome::Transition(Transition::Replace(
134 pages::PerResidentImpact::new_state(ctx, app, id, None),
135 )),
136 "undo" => {
137 let mut edits = app.per_map.map.get_edits().clone();
138 edits.commands.pop().unwrap();
139 app.apply_edits(edits);
140 crate::redraw_all_icons(ctx, app);
142
143 if let EditMode::Shortcuts(ref mut maybe_focus) = app.session.edit_mode {
145 *maybe_focus = None;
146 }
147 if let EditMode::FreehandFilters(_) = app.session.edit_mode {
148 app.session.edit_mode = EditMode::Filters;
149 }
150 EditOutcome::UpdateAll
151 }
152 "Modal filter - no entry"
153 | "Modal filter -- walking/cycling only"
154 | "Bus gate"
155 | "School street" => {
156 app.session.edit_mode = EditMode::Filters;
157 EditOutcome::UpdatePanelAndWorld
158 }
159 "Change modal filter" => EditOutcome::Transition(Transition::Push(
160 modals::ChangeFilterType::new_state(ctx, app),
161 )),
162 "Freehand filters" => {
163 app.session.edit_mode = EditMode::FreehandFilters(PolyLineLasso::new());
164 EditOutcome::UpdatePanelAndWorld
165 }
166 "One-ways" => {
167 app.session.edit_mode = EditMode::Oneways;
168 EditOutcome::UpdatePanelAndWorld
169 }
170 "Shortcuts" => {
171 app.session.edit_mode = EditMode::Shortcuts(None);
172 EditOutcome::UpdatePanelAndWorld
173 }
174 "previous shortcut" => {
175 if let EditMode::Shortcuts(Some(ref mut focus)) = app.session.edit_mode {
176 focus.current_idx -= 1;
177 }
178 if let EditMode::Shortcuts(ref focus) = app.session.edit_mode {
180 let panel_piece = shortcuts::widget(ctx, app, focus.as_ref());
181 panel.replace(ctx, "edit mode contents", panel_piece);
182 self.world = shortcuts::make_world(ctx, app, neighbourhood, focus);
183 }
184 EditOutcome::Transition(Transition::Keep)
185 }
186 "next shortcut" => {
187 if let EditMode::Shortcuts(Some(ref mut focus)) = app.session.edit_mode {
188 focus.current_idx += 1;
189 }
190 if let EditMode::Shortcuts(ref focus) = app.session.edit_mode {
191 let panel_piece = shortcuts::widget(ctx, app, focus.as_ref());
192 panel.replace(ctx, "edit mode contents", panel_piece);
193 self.world = shortcuts::make_world(ctx, app, neighbourhood, focus);
194 }
195 EditOutcome::Transition(Transition::Keep)
196 }
197 "Speed limits" => {
198 app.session.edit_mode = EditMode::SpeedLimits;
199 EditOutcome::UpdatePanelAndWorld
200 }
201 "Turn restrictions" => {
202 app.session.edit_mode = EditMode::TurnRestrictions(None);
203 EditOutcome::UpdatePanelAndWorld
204 }
205 _ => EditOutcome::Nothing,
206 }
207 }
208}
209
210fn road_name(app: &App, road: &Road) -> String {
211 let mut name = road.get_name(app.opts.language.as_ref());
212 if name == "???" {
213 name = "unnamed road".to_string();
214 }
215 if is_private(road) {
216 format!("{name} (private)")
217 } else {
218 name
219 }
220}