1use abstio::{Manifest, MapName};
2use raw_map::RawMap;
3use widgetry::tools::{FileLoader, PopupMsg, URLManager};
4use widgetry::{
5 Autocomplete, EventCtx, GfxCtx, Image, Line, Outcome, Panel, State, Transition, Widget,
6};
7
8use crate::app::App;
9use crate::camera::CameraState;
10
11pub struct PickMap {
12 panel: Panel,
13}
14
15impl PickMap {
16 pub fn new_state(ctx: &mut EventCtx) -> Box<dyn State<App>> {
17 let mut entries = Vec::new();
18 for name in MapName::list_all_maps_merged(&Manifest::load()) {
19 entries.push((name.describe(), abstio::path_raw_map(&name)));
20 }
21
22 Box::new(PickMap {
23 panel: Panel::new_builder(Widget::col(vec![
24 Widget::row(vec![
25 Line("Select a map").small_heading().into_widget(ctx),
26 ctx.style().btn_close_widget(ctx),
27 ]),
28 Widget::row(vec![
29 Image::from_path("system/assets/tools/search.svg").into_widget(ctx),
30 Autocomplete::new_widget(ctx, entries, 20).named("search"),
31 ]),
32 ]))
33 .exact_size_percent(80, 80)
34 .build(ctx),
35 })
36 }
37}
38
39impl State<App> for PickMap {
40 fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition<App> {
41 if let Outcome::Clicked(x) = self.panel.event(ctx) {
42 match x.as_ref() {
43 "close" => {
44 return Transition::Pop;
45 }
46 _ => unreachable!(),
47 }
48 }
49 if let Some(mut paths) = self.panel.autocomplete_done::<String>("search") {
50 if !paths.is_empty() {
51 return Transition::Push(load_map(
52 ctx,
53 paths.remove(0),
54 app.model.include_bldgs,
55 None,
56 ));
57 }
58 }
59
60 Transition::Keep
61 }
62
63 fn draw(&self, g: &mut GfxCtx, _: &App) {
64 self.panel.draw(g);
65 }
66}
67
68pub fn load_map(
69 ctx: &mut EventCtx,
70 path: String,
71 include_bldgs: bool,
72 center_camera: Option<String>,
73) -> Box<dyn State<App>> {
74 FileLoader::<App, RawMap>::new_state(
75 ctx,
76 path,
77 Box::new(move |ctx, app, timer, map| match map {
78 Ok(map) => {
79 app.model = crate::model::Model::from_map(ctx, map, include_bldgs, timer);
80
81 if !URLManager::change_camera(
82 ctx,
83 center_camera.as_ref(),
84 &app.model.map.streets.gps_bounds,
85 ) && !app.model.map.name.map.is_empty()
86 {
87 CameraState::load(ctx, &app.model.map.name);
88 }
89
90 Transition::Clear(vec![crate::app::MainState::new_state(ctx, app)])
91 }
92 Err(err) => Transition::Replace(PopupMsg::new_state(
93 ctx,
94 "Error",
95 vec![
96 "The format of this file has become out-of-sync with this version of the code."
97 .to_string(),
98 "Please file an issue and ask for an update. Sorry for the hassle!".to_string(),
99 format!("Error: {}", err),
100 ],
101 )),
102 }),
103 )
104}