1use serde::{Deserialize, Serialize};
2
3use abstutil::Timer;
4use geom::{Duration, UnitFmt};
5use widgetry::{
6 CanvasSettings, Choice, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome, Panel, Spinner, State,
7 TextExt, Toggle, Widget,
8};
9
10use crate::colors::ColorSchemeChoice;
11use crate::render::DrawBuilding;
12use crate::tools::grey_out_map;
13use crate::AppLike;
14
15#[derive(Clone, Serialize, Deserialize)]
19pub struct Options {
20 pub dev: bool,
23 pub debug_all_agents: bool,
26
27 pub traffic_signal_style: TrafficSignalStyle,
29 pub color_scheme: ColorSchemeChoice,
31 pub toggle_day_night_colors: bool,
33 pub camera_angle: CameraAngle,
35 pub show_building_driveways: bool,
37 pub show_building_outlines: bool,
39 pub show_stop_signs: bool,
41 pub show_crosswalks: bool,
43 pub show_traffic_signal_icon: bool,
46 pub simplify_basemap: bool,
48
49 pub minimal_controls: bool,
51 pub canvas_settings: CanvasSettings,
53
54 pub time_increment: Duration,
56 pub dont_draw_time_warp: bool,
58 pub jump_to_delay: Duration,
60
61 pub language: Option<String>,
64 pub units: UnitFmt,
66}
67
68impl Options {
69 pub fn load_or_default() -> Options {
72 match abstio::maybe_read_json::<Options>(
73 abstio::path_player("settings.json"),
74 &mut Timer::throwaway(),
75 ) {
76 Ok(opts) => {
77 return opts;
78 }
79 Err(err) => {
80 warn!("Couldn't restore settings, so using defaults. {}", err);
81 }
82 }
83
84 Options {
85 dev: false,
86 debug_all_agents: false,
87
88 traffic_signal_style: TrafficSignalStyle::Brian,
89 color_scheme: ColorSchemeChoice::DayMode,
90 toggle_day_night_colors: false,
91 camera_angle: CameraAngle::TopDown,
92 show_building_driveways: true,
93 show_building_outlines: true,
94 show_stop_signs: true,
95 show_crosswalks: true,
96 show_traffic_signal_icon: false,
97 simplify_basemap: false,
98
99 time_increment: Duration::minutes(10),
100 dont_draw_time_warp: false,
101 jump_to_delay: Duration::minutes(5),
102
103 minimal_controls: false,
104 canvas_settings: CanvasSettings::new(),
105 language: None,
106 units: UnitFmt {
107 round_durations: true,
108 metric: false,
110 },
111 }
112 }
113}
114
115#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
117pub enum TrafficSignalStyle {
118 Brian,
119 Yuwen,
120 IndividualTurnArrows,
121}
122
123#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
124pub enum CameraAngle {
125 TopDown,
126 IsometricNE,
127 IsometricNW,
128 IsometricSE,
129 IsometricSW,
130 Abstract,
131}
132
133pub struct OptionsPanel {
134 panel: Panel,
135}
136
137impl OptionsPanel {
138 pub fn new_state<A: AppLike>(ctx: &mut EventCtx, app: &A) -> Box<dyn State<A>> {
139 Box::new(OptionsPanel {
140 panel: Panel::new_builder(Widget::col(vec![
141 Widget::custom_row(vec![
142 Line("Settings").small_heading().into_widget(ctx),
143 ctx.style().btn_close_widget(ctx),
144 ]),
145 "Camera controls".text_widget(ctx),
146 Widget::col(vec![
147 Toggle::checkbox(
148 ctx,
149 "Invert direction of vertical scrolling",
150 None,
151 ctx.canvas.settings.invert_scroll,
152 ),
153 Toggle::checkbox(
154 ctx,
155 "Pan map when cursor is at edge of screen",
156 None,
157 ctx.canvas.settings.edge_auto_panning,
158 )
159 .named("autopan"),
160 Toggle::checkbox(
161 ctx,
162 "Use touchpad to pan and hold Control to zoom",
163 None,
164 ctx.canvas.settings.touchpad_to_move,
165 ),
166 Toggle::checkbox(
167 ctx,
168 "Use arrow keys to pan and Q/W to zoom",
169 None,
170 ctx.canvas.settings.keys_to_pan,
171 ),
172 Widget::row(vec![
173 "Scroll speed for menus".text_widget(ctx).centered_vert(),
174 Spinner::widget(
175 ctx,
176 "gui_scroll_speed",
177 (1, 50),
178 ctx.canvas.settings.gui_scroll_speed,
179 1,
180 ),
181 ]),
182 Widget::row(vec![
183 "Zoom speed for the map".text_widget(ctx).centered_vert(),
184 Spinner::widget(
185 ctx,
186 "canvas_scroll_speed",
187 (1, 30),
188 ctx.canvas.settings.canvas_scroll_speed,
189 1,
190 ),
191 ]),
192 ])
193 .bg(app.cs().inner_panel_bg)
194 .padding(8),
195 "Appearance".text_widget(ctx),
196 Widget::col(vec![
197 Widget::row(vec![
198 "Traffic signal rendering:".text_widget(ctx),
199 Widget::dropdown(
200 ctx,
201 "Traffic signal rendering",
202 app.opts().traffic_signal_style.clone(),
203 vec![
204 Choice::new("Default (Brian's style)", TrafficSignalStyle::Brian),
205 Choice::new("Yuwen's style", TrafficSignalStyle::Yuwen),
206 Choice::new(
207 "arrows showing individual turns (to debug)",
208 TrafficSignalStyle::IndividualTurnArrows,
209 ),
210 ],
211 ),
212 ]),
213 Widget::row(vec![
214 "Camera angle:".text_widget(ctx),
215 Widget::dropdown(
216 ctx,
217 "Camera angle",
218 app.opts().camera_angle.clone(),
219 vec![
220 Choice::new("Top-down", CameraAngle::TopDown),
221 Choice::new("Isometric (northeast)", CameraAngle::IsometricNE),
222 Choice::new("Isometric (northwest)", CameraAngle::IsometricNW),
223 Choice::new("Isometric (southeast)", CameraAngle::IsometricSE),
224 Choice::new("Isometric (southwest)", CameraAngle::IsometricSW),
225 Choice::new("Abstract (just symbols)", CameraAngle::Abstract),
226 ],
227 ),
228 ]),
229 Widget::row(vec![
230 "Color scheme:".text_widget(ctx),
231 Widget::dropdown(
232 ctx,
233 "Color scheme",
234 app.opts().color_scheme,
235 ColorSchemeChoice::choices(),
236 ),
237 ]),
238 Widget::row(vec![
239 "Camera zoom to switch to unzoomed view".text_widget(ctx),
240 Widget::dropdown(
241 ctx,
242 "min zoom",
243 ctx.canvas.settings.min_zoom_for_detail,
244 vec![
245 Choice::new("1.0", 1.0),
246 Choice::new("2.0", 2.0),
247 Choice::new("3.0", 3.0),
248 Choice::new("4.0", 4.0),
249 Choice::new("5.0", 5.0),
250 Choice::new("6.0", 6.0),
251 ],
252 ),
253 ]),
254 Widget::row(vec!["Language".text_widget(ctx), {
255 let mut default = app.opts().language.clone();
256 let mut have_default = false;
257 let mut choices = vec![Choice::new("Map native language", None)];
258 for lang in app.map().get_languages() {
259 if default.as_ref() == Some(&lang) {
260 have_default = true;
261 }
262 choices.push(Choice::new(lang.clone(), Some(lang)));
263 }
264 if !have_default {
267 default = None;
268 }
269 Widget::dropdown(ctx, "language", default, choices)
270 }]),
271 Toggle::choice(
272 ctx,
273 "metric / imperial units",
274 "metric",
275 "imperial",
276 None,
277 app.opts().units.metric,
278 ),
279 ])
280 .bg(app.cs().inner_panel_bg)
281 .padding(8),
282 "Debug".text_widget(ctx),
283 Widget::col(vec![
284 Toggle::checkbox(ctx, "Enable developer mode", None, app.opts().dev),
285 Toggle::checkbox(
286 ctx,
287 "Draw all agents to debug geometry (Slow!)",
288 None,
289 app.opts().debug_all_agents,
290 ),
291 ])
292 .bg(app.cs().inner_panel_bg)
293 .padding(8),
294 ctx.style()
295 .btn_solid_primary
296 .text("Apply")
297 .hotkey(Key::Enter)
298 .build_def(ctx)
299 .centered_horiz(),
300 ]))
301 .build(ctx),
302 })
303 }
304}
305
306impl<A: AppLike> State<A> for OptionsPanel {
307 fn event(&mut self, ctx: &mut EventCtx, app: &mut A) -> widgetry::Transition<A> {
308 if let Outcome::Clicked(x) = self.panel.event(ctx) {
309 match x.as_ref() {
310 "close" => {
311 return widgetry::Transition::Pop;
312 }
313 "Apply" => {
314 let mut opts = app.opts().clone();
315 opts.dev = self.panel.is_checked("Enable developer mode");
316 opts.debug_all_agents = self
317 .panel
318 .is_checked("Draw all agents to debug geometry (Slow!)");
319
320 ctx.canvas.settings.invert_scroll = self
321 .panel
322 .is_checked("Invert direction of vertical scrolling");
323 ctx.canvas.settings.touchpad_to_move = self
324 .panel
325 .is_checked("Use touchpad to pan and hold Control to zoom");
326 ctx.canvas.settings.keys_to_pan = self
327 .panel
328 .is_checked("Use arrow keys to pan and Q/W to zoom");
329 ctx.canvas.settings.edge_auto_panning = self.panel.is_checked("autopan");
330 ctx.canvas.settings.gui_scroll_speed = self.panel.spinner("gui_scroll_speed");
331 ctx.canvas.settings.canvas_scroll_speed =
332 self.panel.spinner("canvas_scroll_speed");
333 ctx.canvas.settings.min_zoom_for_detail = self.panel.dropdown_value("min zoom");
334 opts.canvas_settings = ctx.canvas.settings.clone();
336
337 let style = self.panel.dropdown_value("Traffic signal rendering");
338 if opts.traffic_signal_style != style {
339 opts.traffic_signal_style = style;
340 println!("Rerendering traffic signals...");
341 for i in &mut app.mut_draw_map().intersections {
342 *i.draw_traffic_signal.borrow_mut() = None;
343 }
344 }
345
346 let camera_angle = self.panel.dropdown_value("Camera angle");
347 if opts.camera_angle != camera_angle {
348 opts.camera_angle = camera_angle;
349 ctx.loading_screen("rerendering buildings", |ctx, timer| {
350 let mut all_buildings = GeomBatch::new();
351 let mut all_building_outlines = GeomBatch::new();
352 timer
353 .start_iter("rendering buildings", app.map().all_buildings().len());
354 for b in app.map().all_buildings() {
355 timer.next();
356 DrawBuilding::new(
357 ctx,
358 b,
359 app.map(),
360 app.cs(),
361 &opts,
362 &mut all_buildings,
363 &mut all_building_outlines,
364 );
365 }
366 for r in &mut app.mut_draw_map().roads {
367 r.clear_rendering();
368 }
369
370 timer.start("upload geometry");
371 app.mut_draw_map().draw_all_buildings = all_buildings.upload(ctx);
372 app.mut_draw_map().draw_all_building_outlines =
373 all_building_outlines.upload(ctx);
374 timer.stop("upload geometry");
375 });
376 }
377
378 if app.change_color_scheme(ctx, self.panel.dropdown_value("Color scheme")) {
379 opts.color_scheme = app.opts().color_scheme;
381 opts.toggle_day_night_colors = false;
383 }
384
385 opts.units.metric = self.panel.is_checked("metric / imperial units");
386
387 let language = self.panel.dropdown_value("language");
388 if language != opts.language {
389 opts.language = language;
390 for r in &mut app.mut_draw_map().roads {
391 r.clear_rendering();
392 }
393 }
394
395 let show_building_driveways = opts.show_building_driveways;
397 opts.show_building_driveways = true;
398 let show_building_outlines = opts.show_building_outlines;
399 opts.show_building_outlines = true;
400 abstio::write_json(abstio::path_player("settings.json"), &opts);
401 opts.show_building_driveways = show_building_driveways;
402 opts.show_building_outlines = show_building_outlines;
403 *app.mut_opts() = opts;
404
405 return widgetry::Transition::Pop;
406 }
407 _ => unreachable!(),
408 }
409 }
410
411 widgetry::Transition::Keep
412 }
413
414 fn draw(&self, g: &mut GfxCtx, app: &A) {
415 grey_out_map(g, app);
416 self.panel.draw(g);
417 }
418}