game/devtools/
mod.rs

1//! This directory contains extra/experimental tools not directly related to A/B Street the game.
2//! Eventually some might be split into separate crates.
3
4use abstutil::Timer;
5use geom::{LonLat, Percent};
6use map_gui::colors::ColorSchemeChoice;
7use map_gui::tools::CityPicker;
8use map_gui::AppLike;
9use widgetry::tools::ChooseSomething;
10use widgetry::{Choice, EventCtx, Key, Line, Panel, SimpleState, State, Widget};
11
12use crate::app::{App, Transition};
13
14mod collisions;
15pub mod compare_counts;
16mod destinations;
17pub mod kml;
18mod polygon;
19mod scenario;
20mod story;
21
22pub struct DevToolsMode;
23
24impl DevToolsMode {
25    pub fn new_state(ctx: &mut EventCtx, app: &mut App) -> Box<dyn State<App>> {
26        app.change_color_scheme(ctx, ColorSchemeChoice::DayMode);
27
28        let panel = Panel::new_builder(Widget::col(vec![
29            Widget::row(vec![
30                Line("Advanced tools").small_heading().into_widget(ctx),
31                ctx.style().btn_close_widget(ctx),
32            ]),
33            map_gui::tools::change_map_btn(ctx, app),
34            Widget::custom_row(vec![
35                ctx.style()
36                    .btn_outline
37                    .text("edit a polygon")
38                    .hotkey(Key::E)
39                    .build_def(ctx),
40                ctx.style()
41                    .btn_outline
42                    .text("draw a polygon")
43                    .hotkey(Key::P)
44                    .build_def(ctx),
45                ctx.style()
46                    .btn_outline
47                    .text("load scenario")
48                    .hotkey(Key::W)
49                    .build_def(ctx),
50                ctx.style()
51                    .btn_outline
52                    .text("view KML")
53                    .hotkey(Key::K)
54                    .build_def(ctx),
55                ctx.style()
56                    .btn_outline
57                    .text("story maps")
58                    .hotkey(Key::S)
59                    .build_def(ctx),
60                if abstio::file_exists(app.primary.map.get_city_name().input_path("collisions.bin"))
61                {
62                    ctx.style()
63                        .btn_outline
64                        .text("collisions")
65                        .hotkey(Key::C)
66                        .build_def(ctx)
67                } else {
68                    Widget::nothing()
69                },
70            ])
71            .flex_wrap(ctx, Percent::int(60)),
72            Widget::row(vec![
73                ctx.style()
74                    .btn_solid_primary
75                    .text("OpenStreetMap viewer")
76                    .build_def(ctx),
77                if cfg!(not(target_arch = "wasm32")) {
78                    ctx.style()
79                        .btn_solid_primary
80                        .text("Parking mapper")
81                        .build_def(ctx)
82                } else {
83                    Widget::nothing()
84                },
85                if abstio::file_exists(abstio::path_raw_map(app.primary.map.get_name())) {
86                    ctx.style()
87                        .btn_solid_primary
88                        .text("RawMap editor")
89                        .build_def(ctx)
90                } else {
91                    Widget::nothing()
92                },
93            ]),
94        ]))
95        .build(ctx);
96        <dyn SimpleState<_>>::new_state(panel, Box::new(DevToolsMode))
97    }
98}
99
100impl SimpleState<App> for DevToolsMode {
101    fn on_click(
102        &mut self,
103        ctx: &mut EventCtx,
104        app: &mut App,
105        x: &str,
106        _: &mut Panel,
107    ) -> Transition {
108        match x {
109            "close" => Transition::Pop,
110            "edit a polygon" => {
111                Transition::Push(ChooseSomething::new_state(
112                    ctx,
113                    "Choose a polygon",
114                    // This directory won't exist on the web or for binary releases, only for
115                    // people building from source. Also, abstio::path is abused to find the
116                    // importer/ directory.
117                    abstio::list_dir(abstio::path(format!(
118                        "../importer/config/{}/{}",
119                        app.primary.map.get_city_name().country,
120                        app.primary.map.get_city_name().city
121                    )))
122                    .into_iter()
123                    .filter(|path| path.ends_with(".geojson"))
124                    .map(|path| Choice::new(abstutil::basename(&path), path))
125                    .collect(),
126                    Box::new(|path, ctx, app| match LonLat::read_geojson_polygon(&path) {
127                        Ok(pts) => Transition::Replace(polygon::PolygonEditor::new_state(
128                            ctx,
129                            app,
130                            abstutil::basename(path),
131                            pts,
132                        )),
133                        Err(err) => {
134                            println!("Bad polygon {}: {}", path, err);
135                            Transition::Pop
136                        }
137                    }),
138                ))
139            }
140            "draw a polygon" => Transition::Push(polygon::PolygonEditor::new_state(
141                ctx,
142                app,
143                "name goes here".to_string(),
144                Vec::new(),
145            )),
146            "load scenario" => Transition::Push(ChooseSomething::new_state(
147                ctx,
148                "Choose a scenario",
149                Choice::strings(abstio::list_all_objects(abstio::path_all_scenarios(
150                    app.primary.map.get_name(),
151                ))),
152                Box::new(|s, ctx, app| {
153                    let scenario = abstio::read_binary(
154                        abstio::path_scenario(app.primary.map.get_name(), &s),
155                        &mut Timer::throwaway(),
156                    );
157                    Transition::Replace(scenario::ScenarioManager::new_state(scenario, ctx, app))
158                }),
159            )),
160            "view KML" => Transition::Push(kml::ViewKML::new_state(ctx, app, None)),
161            "story maps" => Transition::Push(story::StoryMapEditor::new_state(ctx)),
162            "collisions" => Transition::Push(collisions::CollisionsViewer::new_state(ctx, app)),
163            "OpenStreetMap viewer" => {
164                map_gui::tools::Executable::OSMViewer.replace_process(ctx, app, vec![])
165            }
166            "Parking mapper" => {
167                map_gui::tools::Executable::ParkingMapper.replace_process(ctx, app, vec![])
168            }
169            "RawMap editor" => {
170                map_gui::tools::Executable::RawMapEditor.replace_process(ctx, app, vec![])
171            }
172            "change map" => Transition::Push(CityPicker::new_state(
173                ctx,
174                app,
175                Box::new(|ctx, app| {
176                    Transition::Multi(vec![
177                        Transition::Pop,
178                        Transition::Replace(DevToolsMode::new_state(ctx, app)),
179                    ])
180                }),
181            )),
182            _ => unreachable!(),
183        }
184    }
185}