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
22pub trait Layer {
26 fn name(&self) -> Option<&'static str>;
27 fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Option<LayerOutcome>;
28 fn draw(&self, g: &mut GfxCtx, app: &App);
30 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
46pub enum LayerOutcome {
48 Close,
49 Replace(Box<dyn Layer>),
50 Transition(Transition),
51}
52
53pub 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 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
285pub 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);