ltn/
app.rs

1use abstio::MapName;
2use abstutil::Timer;
3use geom::{Duration, Pt2D, Time};
4use map_gui::colors::ColorScheme;
5use map_gui::load::MapLoader;
6use map_gui::options::Options;
7use map_gui::render::{DrawMap, DrawOptions};
8use map_gui::tools::CameraState;
9use map_gui::tools::DrawSimpleRoadLabels;
10use map_gui::{AppLike, ID};
11use map_model::{osm, CrossingType, FilterType, IntersectionID, Map, MapEdits, RoutingParams};
12use widgetry::tools::URLManager;
13use widgetry::{Canvas, Drawable, EventCtx, GfxCtx, SharedAppState, State, Warper};
14
15use crate::logic::Partitioning;
16use crate::{logic, pages, render, NeighbourhoodID};
17
18pub type Transition = widgetry::Transition<App>;
19
20pub struct App {
21    pub per_map: PerMap,
22    pub cs: ColorScheme,
23    pub opts: Options,
24
25    pub session: Session,
26}
27
28pub struct PerMap {
29    pub map: Map,
30    pub draw_map: DrawMap,
31
32    // The last edited neighbourhood
33    pub current_neighbourhood: Option<NeighbourhoodID>,
34
35    // These capture modal filters that exist in the map already. Whenever we pathfind in this app
36    // in the "before changes" case, we have to use these. Do NOT use the map's built-in
37    // pathfinder. (https://github.com/a-b-street/abstreet/issues/852 would make this more clear)
38    pub routing_params_before_changes: RoutingParams,
39    pub proposals: crate::save::Proposals,
40    pub impact: logic::Impact,
41
42    pub consultation: Option<NeighbourhoodID>,
43    pub consultation_id: Option<String>,
44
45    pub draw_all_filters: render::Toggle3Zoomed,
46    pub draw_major_road_labels: DrawSimpleRoadLabels,
47    pub draw_all_local_road_labels: Option<DrawSimpleRoadLabels>,
48    pub draw_poi_icons: Drawable,
49    pub draw_bus_routes: Drawable,
50    pub draw_turn_restrictions: Drawable,
51
52    pub current_trip_name: Option<String>,
53}
54
55impl PerMap {
56    fn new(
57        ctx: &mut EventCtx,
58        mut map: Map,
59        opts: &Options,
60        cs: &ColorScheme,
61        timer: &mut Timer,
62    ) -> Self {
63        // Do this before creating the default partitioning. Non-driveable roads in OSM get turned
64        // into driveable roads and a filter here, and we want the partitioning to "see" those
65        // roads.
66        logic::transform_existing(&mut map, timer);
67        let proposals = crate::save::Proposals::new(&map, timer);
68
69        let routing_params_before_changes = map.routing_params_respecting_modal_filters();
70
71        let draw_all_filters = render::render_modal_filters(ctx, &map);
72
73        // Create DrawMap after transform_existing_filters, which modifies road widths
74        let draw_map = DrawMap::new(ctx, &map, opts, cs, timer);
75        let draw_poi_icons = render::render_poi_icons(ctx, &map);
76        let draw_bus_routes = render::render_bus_routes(ctx, &map);
77        let draw_turn_restrictions = render::render_turn_restrictions(ctx, &map);
78
79        let per_map = Self {
80            map,
81            draw_map,
82
83            current_neighbourhood: None,
84
85            routing_params_before_changes,
86            proposals,
87            impact: logic::Impact::empty(ctx),
88
89            consultation: None,
90            consultation_id: None,
91
92            draw_all_filters,
93            draw_major_road_labels: DrawSimpleRoadLabels::empty(ctx),
94            draw_all_local_road_labels: None,
95            draw_poi_icons,
96            draw_bus_routes,
97            draw_turn_restrictions,
98
99            current_trip_name: None,
100        };
101
102        if !CameraState::load(ctx, per_map.map.get_name()) {
103            // If we didn't restore a previous camera position, start zoomed out, centered on the
104            // map's center.
105            ctx.canvas.cam_zoom = ctx.canvas.min_zoom();
106            ctx.canvas
107                .center_on_map_pt(per_map.map.get_boundary_polygon().center());
108        }
109        per_map
110    }
111}
112
113pub struct Session {
114    pub edit_mode: pages::EditMode,
115    pub filter_type: FilterType,
116    pub crossing_type: CrossingType,
117
118    // Remember form settings in different tabs.
119    // Pick areas:
120    pub draw_neighbourhood_style: pages::PickAreaStyle,
121    // Plan a route:
122    pub main_road_penalty: f64,
123    pub show_walking_cycling_routes: bool,
124    // Select boundary:
125    pub add_intermediate_blocks: bool,
126
127    // Shared in all modes
128    pub layers: crate::components::Layers,
129    pub manage_proposals: bool,
130}
131
132impl AppLike for App {
133    #[inline]
134    fn map(&self) -> &Map {
135        &self.per_map.map
136    }
137    #[inline]
138    fn cs(&self) -> &ColorScheme {
139        &self.cs
140    }
141    #[inline]
142    fn mut_cs(&mut self) -> &mut ColorScheme {
143        &mut self.cs
144    }
145    #[inline]
146    fn draw_map(&self) -> &DrawMap {
147        &self.per_map.draw_map
148    }
149    #[inline]
150    fn mut_draw_map(&mut self) -> &mut DrawMap {
151        &mut self.per_map.draw_map
152    }
153    #[inline]
154    fn opts(&self) -> &Options {
155        &self.opts
156    }
157    #[inline]
158    fn mut_opts(&mut self) -> &mut Options {
159        &mut self.opts
160    }
161
162    fn map_switched(&mut self, ctx: &mut EventCtx, map: Map, timer: &mut Timer) {
163        CameraState::save(ctx.canvas, self.per_map.map.get_name());
164        self.per_map = PerMap::new(ctx, map, &self.opts, &self.cs, timer);
165        self.per_map.draw_major_road_labels =
166            DrawSimpleRoadLabels::only_major_roads(ctx, self, render::colors::MAIN_ROAD_LABEL);
167        self.opts.units.metric = self.per_map.map.get_name().city.uses_metric();
168    }
169
170    fn draw_with_opts(&self, g: &mut GfxCtx, _l: DrawOptions) {
171        self.draw_with_layering(g, |_| {});
172    }
173    fn make_warper(
174        &mut self,
175        ctx: &EventCtx,
176        pt: Pt2D,
177        target_cam_zoom: Option<f64>,
178        _: Option<ID>,
179    ) -> Box<dyn State<App>> {
180        Box::new(SimpleWarper {
181            warper: Warper::new(ctx, pt, target_cam_zoom),
182        })
183    }
184
185    fn sim_time(&self) -> Time {
186        Time::START_OF_DAY
187    }
188
189    fn current_stage_and_remaining_time(&self, _: IntersectionID) -> (usize, Duration) {
190        (0, Duration::ZERO)
191    }
192}
193
194impl SharedAppState for App {
195    fn draw_default(&self, g: &mut GfxCtx) {
196        self.draw_with_opts(g, DrawOptions::new());
197    }
198
199    fn dump_before_abort(&self, canvas: &Canvas) {
200        CameraState::save(canvas, self.per_map.map.get_name());
201    }
202
203    fn before_quit(&self, canvas: &Canvas) {
204        CameraState::save(canvas, self.per_map.map.get_name());
205    }
206
207    fn free_memory(&mut self) {
208        self.per_map.draw_map.free_memory();
209    }
210}
211
212impl App {
213    pub fn new<F: 'static + Fn(&mut EventCtx, &mut App) -> Vec<Box<dyn State<App>>>>(
214        ctx: &mut EventCtx,
215        opts: Options,
216        map_name: MapName,
217        cam: Option<String>,
218        init_states: F,
219    ) -> (App, Vec<Box<dyn State<App>>>) {
220        abstutil::logger::setup();
221        ctx.canvas.settings = opts.canvas_settings.clone();
222
223        let session = Session {
224            edit_mode: pages::EditMode::Filters,
225            filter_type: FilterType::WalkCycleOnly,
226            crossing_type: CrossingType::Unsignalized,
227
228            draw_neighbourhood_style: pages::PickAreaStyle::Simple,
229            main_road_penalty: 1.0,
230            show_walking_cycling_routes: false,
231            add_intermediate_blocks: true,
232
233            layers: crate::components::Layers::new(ctx),
234            manage_proposals: false,
235        };
236
237        let cs = ColorScheme::new(ctx, opts.color_scheme);
238        let app = App {
239            // Start with a blank map
240            per_map: PerMap::new(
241                ctx,
242                Map::almost_blank(),
243                &opts,
244                &cs,
245                &mut Timer::throwaway(),
246            ),
247            cs,
248            opts,
249            session,
250        };
251
252        let states = vec![MapLoader::new_state(
253            ctx,
254            &app,
255            map_name,
256            Box::new(move |ctx, app| {
257                URLManager::change_camera(ctx, cam.as_ref(), app.map().get_gps_bounds());
258                Transition::Clear(init_states(ctx, app))
259            }),
260        )];
261        (app, states)
262    }
263
264    /// Draw unzoomed, but after the water/park areas layer, draw something custom.
265    pub fn draw_with_layering<F: Fn(&mut GfxCtx)>(&self, g: &mut GfxCtx, custom: F) {
266        g.clear(self.cs.void_background);
267        g.redraw(&self.per_map.draw_map.boundary_polygon);
268        g.redraw(&self.per_map.draw_map.draw_all_areas);
269        custom(g);
270        g.redraw(&self.per_map.draw_map.draw_all_unzoomed_parking_lots);
271        g.redraw(
272            &self
273                .per_map
274                .draw_map
275                .draw_all_unzoomed_roads_and_intersections,
276        );
277        g.redraw(&self.per_map.draw_map.draw_all_buildings);
278        g.redraw(&self.per_map.draw_map.draw_all_building_outlines);
279    }
280
281    pub fn partitioning(&self) -> &Partitioning {
282        &self.per_map.proposals.get_current().partitioning
283    }
284
285    pub fn calculate_draw_all_local_road_labels(&mut self, ctx: &mut EventCtx) {
286        if self.per_map.draw_all_local_road_labels.is_none() {
287            self.per_map.draw_all_local_road_labels = Some(DrawSimpleRoadLabels::new(
288                ctx,
289                self,
290                render::colors::LOCAL_ROAD_LABEL,
291                Box::new(|r| r.get_rank() == osm::RoadRank::Local && !r.is_light_rail()),
292            ));
293        }
294    }
295
296    pub fn apply_edits(&mut self, edits: MapEdits) {
297        // Keep the map and the current proposal synced
298        self.per_map.proposals.before_edit(edits.clone());
299        self.per_map
300            .map
301            .must_apply_edits(edits, &mut Timer::throwaway());
302    }
303}
304
305struct SimpleWarper {
306    warper: Warper,
307}
308
309impl State<App> for SimpleWarper {
310    fn event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition {
311        if self.warper.event(ctx) {
312            Transition::Keep
313        } else {
314            Transition::Pop
315        }
316    }
317
318    fn draw(&self, _: &mut GfxCtx, _: &App) {}
319}