ltn/pages/design_ltn/
mod.rs

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    // Is a road clicked on right now?
25    Shortcuts(Option<shortcuts::FocusedRoad>),
26    SpeedLimits,
27    TurnRestrictions(Option<FocusedTurns>),
28}
29
30pub struct EditNeighbourhood {
31    // Only pub for drawing
32    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    /// Don't recreate the Neighbourhood
45    UpdatePanelAndWorld,
46    /// Update the panel, world, and neighbourhood (cells and shortcuts only)
47    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                // Redraw the relevant icons
141                crate::redraw_all_icons(ctx, app);
142
143                // TODO Ideally, preserve panel state (checkboxes and dropdowns)
144                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                // Logically we could do UpdatePanelAndWorld, but we need to be more efficient
179                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}