1use structopt::StructOpt;
2
3use abstio::MapName;
4use abstutil::Timer;
5use geom::{Circle, Distance, Duration, Pt2D, Time};
6use map_model::{IntersectionID, Map};
7use widgetry::tools::URLManager;
8use widgetry::{Canvas, EventCtx, GfxCtx, Settings, SharedAppState, State, Transition, Warper};
9
10use crate::colors::{ColorScheme, ColorSchemeChoice};
11use crate::load::MapLoader;
12use crate::options::Options;
13use crate::render::DrawMap;
14use crate::render::{DrawOptions, Renderable};
15use crate::tools::CameraState;
16use crate::{AppLike, ID};
17
18pub struct SimpleApp<T> {
20 pub map: Map,
21 pub draw_map: DrawMap,
22 pub cs: ColorScheme,
23 pub opts: Options,
24 pub current_selection: Option<ID>,
25 pub session: T,
27 pub time: Time,
29}
30
31#[derive(StructOpt)]
34pub struct SimpleAppArgs {
35 #[structopt()]
38 pub map_path: Option<String>,
39 #[structopt(long)]
42 pub cam: Option<String>,
43 #[structopt(long)]
46 pub dev: bool,
47 #[structopt(long, parse(try_from_str = ColorSchemeChoice::parse))]
49 pub color_scheme: Option<ColorSchemeChoice>,
50 #[structopt(long)]
52 pub minimal_controls: bool,
53 #[structopt(long)]
55 pub scale_factor: Option<f64>,
56}
57
58impl SimpleAppArgs {
59 pub fn override_options(&self, opts: &mut Options) {
62 opts.dev = self.dev;
63 opts.minimal_controls = self.minimal_controls;
64 if let Some(cs) = self.color_scheme {
65 opts.color_scheme = cs;
66 opts.toggle_day_night_colors = false;
67 }
68 }
69
70 pub fn update_widgetry_settings(&self, mut settings: Settings) -> Settings {
71 settings = settings
72 .read_svg(Box::new(abstio::slurp_bytes))
73 .window_icon(abstio::path("system/assets/pregame/icon.png"));
74 if let Some(s) = self.scale_factor {
75 settings = settings.scale_factor(s);
76 }
77 settings
78 }
79
80 pub fn map_name(&self) -> MapName {
81 self.map_path
82 .as_ref()
83 .map(|path| {
84 MapName::from_path(path).unwrap_or_else(|| panic!("bad map path: {}", path))
85 })
86 .or_else(|| {
87 abstio::maybe_read_json::<crate::tools::DefaultMap>(
88 abstio::path_player("maps.json"),
89 &mut Timer::throwaway(),
90 )
91 .ok()
92 .map(|x| x.last_map)
93 })
94 .unwrap_or_else(|| MapName::seattle("montlake"))
95 }
96}
97
98impl<T: 'static> SimpleApp<T> {
99 pub fn new<
100 F: 'static + Fn(&mut EventCtx, &mut SimpleApp<T>) -> Vec<Box<dyn State<SimpleApp<T>>>>,
101 >(
102 ctx: &mut EventCtx,
103 opts: Options,
104 map_name: Option<MapName>,
105 cam: Option<String>,
106 session: T,
107 init_states: F,
108 ) -> (SimpleApp<T>, Vec<Box<dyn State<SimpleApp<T>>>>) {
109 abstutil::logger::setup();
110 ctx.canvas.settings = opts.canvas_settings.clone();
111
112 let cs = ColorScheme::new(ctx, opts.color_scheme);
113 let map = Map::almost_blank();
115 let draw_map = DrawMap::new(ctx, &map, &opts, &cs, &mut Timer::throwaway());
116 let mut app = SimpleApp {
117 map,
118 draw_map,
119 cs,
120 opts,
121 current_selection: None,
122 session,
123 time: Time::START_OF_DAY,
124 };
125
126 let states = if let Some(map_name) = map_name {
127 vec![MapLoader::new_state(
128 ctx,
129 &app,
130 map_name,
131 Box::new(move |ctx, app| {
132 URLManager::change_camera(ctx, cam.as_ref(), app.map().get_gps_bounds());
133 Transition::Clear(init_states(ctx, app))
134 }),
135 )]
136 } else {
137 init_states(ctx, &mut app)
138 };
139 (app, states)
140 }
141
142 pub fn draw_unzoomed(&self, g: &mut GfxCtx) {
143 g.clear(self.cs.void_background);
144 g.redraw(&self.draw_map.boundary_polygon);
145 g.redraw(&self.draw_map.draw_all_areas);
146 g.redraw(&self.draw_map.draw_all_unzoomed_parking_lots);
147 g.redraw(&self.draw_map.draw_all_unzoomed_roads_and_intersections);
148 g.redraw(&self.draw_map.draw_all_buildings);
149 g.redraw(&self.draw_map.draw_all_building_outlines);
150 if let Some(ID::Area(id)) = self.current_selection {
155 g.draw_polygon(
156 self.cs.selected,
157 self.draw_map.get_a(id).get_outline(&self.map),
158 );
159 } else if let Some(ID::Road(id)) = self.current_selection {
160 g.draw_polygon(
161 self.cs.selected,
162 self.draw_map.get_r(id).get_outline(&self.map),
163 );
164 } else if let Some(ID::Intersection(id)) = self.current_selection {
165 g.draw_polygon(self.cs.selected, self.map.get_i(id).polygon.clone());
167 } else if let Some(ID::Building(id)) = self.current_selection {
168 g.draw_polygon(self.cs.selected, self.map.get_b(id).polygon.clone());
169 }
170 }
171
172 pub fn draw_zoomed(&self, g: &mut GfxCtx, opts: DrawOptions) {
173 g.clear(self.cs.void_background);
174 g.redraw(&self.draw_map.boundary_polygon);
175
176 let objects = self
177 .draw_map
178 .get_renderables_back_to_front(g.get_screen_bounds(), &self.map);
179
180 let mut drawn_all_buildings = false;
181 let mut drawn_all_areas = false;
182
183 for obj in objects {
184 obj.draw(g, self, &opts);
185
186 match obj.get_id() {
187 ID::Building(_) => {
188 if !drawn_all_buildings {
189 g.redraw(&self.draw_map.draw_all_buildings);
190 g.redraw(&self.draw_map.draw_all_building_outlines);
191 drawn_all_buildings = true;
192 }
193 }
194 ID::Area(_) => {
195 if !drawn_all_areas {
196 g.redraw(&self.draw_map.draw_all_areas);
197 drawn_all_areas = true;
198 }
199 }
200 _ => {}
201 }
202
203 if self.current_selection == Some(obj.get_id()) {
204 g.draw_polygon(self.cs.selected, obj.get_outline(&self.map));
205 }
206 }
207 }
208
209 pub fn recalculate_current_selection(&mut self, ctx: &EventCtx) {
211 self.current_selection = self.calculate_current_selection(ctx, false, false);
212 }
213
214 pub fn mouseover_unzoomed_roads_and_intersections(&self, ctx: &EventCtx) -> Option<ID> {
216 self.calculate_current_selection(ctx, true, false)
217 }
218 pub fn mouseover_unzoomed_buildings(&self, ctx: &EventCtx) -> Option<ID> {
220 self.calculate_current_selection(ctx, false, true)
221 .filter(|id| matches!(id, ID::Building(_)))
222 }
223
224 fn calculate_current_selection(
225 &self,
226 ctx: &EventCtx,
227 unzoomed_roads_and_intersections: bool,
228 unzoomed_buildings: bool,
229 ) -> Option<ID> {
230 if ctx.canvas.is_unzoomed() && !(unzoomed_roads_and_intersections || unzoomed_buildings) {
232 return None;
233 }
234
235 let pt = ctx.canvas.get_cursor_in_map_space()?;
236
237 let mut objects = self.draw_map.get_renderables_back_to_front(
238 Circle::new(pt, Distance::meters(3.0)).get_bounds(),
239 &self.map,
240 );
241 objects.reverse();
242
243 for obj in objects {
244 match obj.get_id() {
245 ID::Road(_) => {
246 if !unzoomed_roads_and_intersections || ctx.canvas.is_zoomed() {
247 continue;
248 }
249 }
250 ID::Intersection(_) => {
251 if ctx.canvas.is_unzoomed() && !unzoomed_roads_and_intersections {
252 continue;
253 }
254 }
255 ID::Building(_) => {
256 if ctx.canvas.is_unzoomed() && !unzoomed_buildings {
257 continue;
258 }
259 }
260 _ => {
261 if ctx.canvas.is_unzoomed() {
262 continue;
263 }
264 }
265 }
266 if obj.contains_pt(pt, &self.map) {
267 return Some(obj.get_id());
268 }
269 }
270 None
271 }
272}
273
274impl<T: 'static> AppLike for SimpleApp<T> {
275 #[inline]
276 fn map(&self) -> &Map {
277 &self.map
278 }
279 #[inline]
280 fn cs(&self) -> &ColorScheme {
281 &self.cs
282 }
283 #[inline]
284 fn mut_cs(&mut self) -> &mut ColorScheme {
285 &mut self.cs
286 }
287 #[inline]
288 fn draw_map(&self) -> &DrawMap {
289 &self.draw_map
290 }
291 #[inline]
292 fn mut_draw_map(&mut self) -> &mut DrawMap {
293 &mut self.draw_map
294 }
295 #[inline]
296 fn opts(&self) -> &Options {
297 &self.opts
298 }
299 #[inline]
300 fn mut_opts(&mut self) -> &mut Options {
301 &mut self.opts
302 }
303
304 fn map_switched(&mut self, ctx: &mut EventCtx, map: Map, timer: &mut Timer) {
305 CameraState::save(ctx.canvas, self.map.get_name());
306 self.map = map;
307 self.draw_map = DrawMap::new(ctx, &self.map, &self.opts, &self.cs, timer);
308 if !CameraState::load(ctx, self.map.get_name()) {
309 ctx.canvas.cam_zoom = ctx.canvas.min_zoom();
312 ctx.canvas
313 .center_on_map_pt(self.map.get_boundary_polygon().center());
314 }
315
316 self.opts.units.metric = self.map.get_name().city.uses_metric();
317 }
318
319 fn draw_with_opts(&self, g: &mut GfxCtx, opts: DrawOptions) {
320 if g.canvas.is_unzoomed() {
321 self.draw_unzoomed(g);
322 } else {
323 self.draw_zoomed(g, opts);
324 }
325 }
326
327 fn make_warper(
328 &mut self,
329 ctx: &EventCtx,
330 pt: Pt2D,
331 target_cam_zoom: Option<f64>,
332 _: Option<ID>,
333 ) -> Box<dyn State<SimpleApp<T>>> {
334 Box::new(SimpleWarper {
335 warper: Warper::new(ctx, pt, target_cam_zoom),
336 })
337 }
338
339 fn sim_time(&self) -> Time {
340 self.time
341 }
342
343 fn current_stage_and_remaining_time(&self, id: IntersectionID) -> (usize, Duration) {
344 let signal = self.map.get_traffic_signal(id);
345 let mut time_left = (self.time - Time::START_OF_DAY) % signal.simple_cycle_duration();
346 for (idx, stage) in signal.stages.iter().enumerate() {
347 if time_left < stage.stage_type.simple_duration() {
348 return (idx, time_left);
349 }
350 time_left -= stage.stage_type.simple_duration();
351 }
352 unreachable!()
353 }
354}
355
356impl<T: 'static> SharedAppState for SimpleApp<T> {
357 fn draw_default(&self, g: &mut GfxCtx) {
358 self.draw_with_opts(g, DrawOptions::new());
359 }
360
361 fn dump_before_abort(&self, canvas: &Canvas) {
362 CameraState::save(canvas, self.map.get_name());
363 }
364
365 fn before_quit(&self, canvas: &Canvas) {
366 CameraState::save(canvas, self.map.get_name());
367 }
368
369 fn free_memory(&mut self) {
370 self.draw_map.free_memory();
371 }
372}
373
374struct SimpleWarper {
375 warper: Warper,
376}
377
378impl<T> State<SimpleApp<T>> for SimpleWarper {
379 fn event(&mut self, ctx: &mut EventCtx, _: &mut SimpleApp<T>) -> Transition<SimpleApp<T>> {
380 if self.warper.event(ctx) {
381 Transition::Keep
382 } else {
383 Transition::Pop
384 }
385 }
386
387 fn draw(&self, _: &mut GfxCtx, _: &SimpleApp<T>) {}
388}