game/layer/
mod.rs

1use map_gui::tools::{grey_out_map, HeatmapOptions};
2use sim::AgentType;
3use widgetry::{
4    DrawBaselayer, EventCtx, GfxCtx, HorizontalAlignment, Image, Key, Line, Outcome, Panel, State,
5    TextExt, VerticalAlignment, Widget,
6};
7
8use crate::app::{App, Transition};
9use crate::sandbox::dashboards;
10
11pub mod elevation;
12pub mod favorites;
13pub mod map;
14mod pandemic;
15mod parking;
16mod population;
17mod problems;
18mod problems_diff;
19pub mod traffic;
20pub mod transit;
21
22// TODO Good ideas in
23// https://towardsdatascience.com/top-10-map-types-in-data-visualization-b3a80898ea70
24
25pub trait Layer {
26    fn name(&self) -> Option<&'static str>;
27    fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Option<LayerOutcome>;
28    // Draw both controls and, if zoomed, the layer contents
29    fn draw(&self, g: &mut GfxCtx, app: &App);
30    // Just draw contents and do it always
31    fn draw_minimap(&self, g: &mut GfxCtx);
32}
33
34impl dyn Layer {
35    fn simple_event(ctx: &mut EventCtx, panel: &mut Panel) -> Option<LayerOutcome> {
36        match panel.event(ctx) {
37            Outcome::Clicked(x) => match x.as_ref() {
38                "close" => Some(LayerOutcome::Close),
39                _ => unreachable!(),
40            },
41            _ => None,
42        }
43    }
44}
45
46// TODO Just return a bool for closed? Less readable...
47pub enum LayerOutcome {
48    Close,
49    Replace(Box<dyn Layer>),
50    Transition(Transition),
51}
52
53// TODO Maybe overkill, but could embed a minimap and preview the layer on hover
54pub struct PickLayer {
55    panel: Panel,
56}
57
58impl PickLayer {
59    pub fn update(ctx: &mut EventCtx, app: &mut App) -> Option<Transition> {
60        app.primary.layer.as_ref()?;
61
62        // TODO Since the Layer is embedded in App, we have to do this slight trick
63        let mut layer = app.primary.layer.take().unwrap();
64        match layer.event(ctx, app) {
65            Some(LayerOutcome::Close) => {
66                app.primary.layer = None;
67                return None;
68            }
69            Some(LayerOutcome::Replace(l)) => {
70                app.primary.layer = Some(l);
71                return None;
72            }
73            Some(LayerOutcome::Transition(t)) => {
74                return Some(t);
75            }
76            None => {}
77        }
78        app.primary.layer = Some(layer);
79
80        None
81    }
82
83    pub fn pick(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
84        let mut col = vec![Widget::custom_row(vec![
85            Line("Layers").small_heading().into_widget(ctx),
86            ctx.style().btn_close_widget(ctx),
87        ])];
88
89        let current = match app.primary.layer {
90            None => "None",
91            Some(ref l) => l.name().unwrap_or(""),
92        };
93        let btn = |name: &str, key| {
94            ctx.style()
95                .btn_outline
96                .text(name)
97                .hotkey(key)
98                .disabled(name == current)
99                .build_widget(ctx, name)
100        };
101
102        col.push(btn("None", Key::N));
103
104        col.push(
105            Widget::custom_row(vec![
106                Widget::col(vec![
107                    "Traffic".text_widget(ctx),
108                    btn("delay", Key::D),
109                    btn("throughput", Key::T),
110                    btn("traffic jams", Key::J),
111                    btn("cycling activity", Key::B),
112                    btn("pedestrian crowding", Key::C),
113                ]),
114                Widget::col(vec![
115                    "Map".text_widget(ctx),
116                    btn("map edits", Key::E),
117                    btn("parking occupancy", Key::P),
118                    btn("transit network", Key::U),
119                    btn("population map", Key::X),
120                    btn("no sidewalks", Key::S),
121                    btn("favorite buildings", Key::F),
122                ]),
123            ])
124            .evenly_spaced(),
125        );
126
127        col.push(
128            Widget::custom_row(vec![
129                Widget::col(vec![
130                    "Experimental".text_widget(ctx),
131                    btn("amenities", Key::A),
132                    btn("backpressure", Key::Z),
133                    btn("steep streets", Key::V),
134                    btn("elevation", Key::G),
135                    btn("parking efficiency", Key::O),
136                    btn("blackholes", Key::L),
137                    btn("problem map", Key::K),
138                    btn("high stress", Key::H),
139                    if app.primary.sim.get_pandemic_model().is_some() {
140                        btn("pandemic model", Key::Y)
141                    } else {
142                        Widget::nothing()
143                    },
144                ]),
145                Widget::col(vec![
146                    "Data".text_widget(ctx),
147                    btn("traffic signal demand", Key::M),
148                    btn("commuter patterns", Key::R),
149                ]),
150            ])
151            .evenly_spaced(),
152        );
153
154        Box::new(PickLayer {
155            panel: Panel::new_builder(Widget::col(col))
156                .exact_size_percent(35, 70)
157                .build(ctx),
158        })
159    }
160}
161
162impl State<App> for PickLayer {
163    fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
164        match self.panel.event(ctx) {
165            Outcome::Clicked(x) => match x.as_ref() {
166                "close" => {}
167                "None" => {
168                    app.primary.layer = None;
169                }
170                "amenities" => {
171                    app.primary.layer = Some(Box::new(map::Static::amenities(ctx, app)));
172                }
173                "backpressure" => {
174                    app.primary.layer = Some(Box::new(traffic::Backpressure::new(ctx, app)));
175                }
176                "cycling activity" => {
177                    app.primary.layer = Some(Box::new(map::BikeActivity::new(ctx, app)));
178                }
179                "delay" => {
180                    app.primary.layer = Some(Box::new(traffic::Delay::new(ctx, app)));
181                }
182                "pedestrian crowding" => {
183                    app.primary.layer = Some(Box::new(traffic::PedestrianCrowding::new(ctx, app)));
184                }
185                "steep streets" => {
186                    app.primary.layer = Some(Box::new(elevation::SteepStreets::new(ctx, app)));
187                }
188                "elevation" => {
189                    app.primary.layer = Some(Box::new(elevation::ElevationContours::new(ctx, app)));
190                }
191                "map edits" => {
192                    app.primary.layer = Some(Box::new(map::Static::edits(ctx, app)));
193                }
194                "no sidewalks" => {
195                    app.primary.layer = Some(Box::new(map::Static::no_sidewalks(ctx, app)));
196                }
197                "high stress" => {
198                    app.primary.layer = Some(Box::new(map::Static::high_stress(ctx, app)));
199                }
200                "favorite buildings" => {
201                    app.primary.layer = Some(Box::new(favorites::ShowFavorites::new(ctx, app)));
202                }
203                "pandemic model" => {
204                    app.primary.layer = Some(Box::new(pandemic::Pandemic::new(
205                        ctx,
206                        app,
207                        pandemic::Options {
208                            heatmap: Some(HeatmapOptions::new()),
209                            state: pandemic::Seir::Infected,
210                        },
211                    )));
212                }
213                "blackholes" => {
214                    app.primary.layer = Some(Box::new(map::Static::blackholes(ctx, app)));
215                }
216                "parking occupancy" => {
217                    app.primary.layer = Some(Box::new(parking::Occupancy::new(
218                        ctx, app, true, true, true, false, true,
219                    )));
220                }
221                "parking efficiency" => {
222                    app.primary.layer = Some(Box::new(parking::Efficiency::new(ctx, app)));
223                }
224                "population map" => {
225                    app.primary.layer = Some(Box::new(population::PopulationMap::new(
226                        ctx,
227                        app,
228                        population::Options {
229                            heatmap: Some(HeatmapOptions::new()),
230                        },
231                    )));
232                }
233                "problem map" => {
234                    app.primary.layer = Some(Box::new(problems::ProblemMap::new(
235                        ctx,
236                        app,
237                        problems::Options::new(app),
238                    )));
239                }
240                "throughput" => {
241                    app.primary.layer = Some(Box::new(traffic::Throughput::new(
242                        ctx,
243                        app,
244                        AgentType::all().into_iter().collect(),
245                    )));
246                }
247                "traffic jams" => {
248                    app.primary.layer = Some(Box::new(traffic::TrafficJams::new(ctx, app)));
249                }
250                "transit network" => {
251                    app.primary.layer = Some(Box::new(transit::TransitNetwork::new(
252                        ctx, app, false, true, true,
253                    )));
254                }
255                "traffic signal demand" => {
256                    return Transition::Replace(dashboards::TrafficSignalDemand::new_state(
257                        ctx, app,
258                    ));
259                }
260                "commuter patterns" => {
261                    return Transition::Replace(dashboards::CommuterPatterns::new_state(ctx, app));
262                }
263                _ => unreachable!(),
264            },
265            _ => {
266                if self.panel.clicked_outside(ctx) {
267                    return Transition::Pop;
268                }
269                return Transition::Keep;
270            }
271        }
272        Transition::Pop
273    }
274
275    fn draw_baselayer(&self) -> DrawBaselayer {
276        DrawBaselayer::PreviousState
277    }
278
279    fn draw(&self, g: &mut GfxCtx, app: &App) {
280        grey_out_map(g, app);
281        self.panel.draw(g);
282    }
283}
284
285/// Creates the top row for any layer panel.
286pub fn header(ctx: &mut EventCtx, name: &str) -> Widget {
287    Widget::row(vec![
288        Image::from_path("system/assets/tools/layers.svg")
289            .into_widget(ctx)
290            .centered_vert(),
291        name.text_widget(ctx).centered_vert(),
292        ctx.style().btn_close_widget(ctx),
293    ])
294}
295
296pub const PANEL_PLACEMENT: (HorizontalAlignment, VerticalAlignment) = (
297    HorizontalAlignment::Percent(0.02),
298    VerticalAlignment::Percent(0.2),
299);