use serde::{Deserialize, Serialize};
use abstutil::{CmdArgs, Timer};
use geom::{Duration, UnitFmt};
use widgetry::{
CanvasSettings, Choice, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome, Panel, Spinner, State,
TextExt, Toggle, Widget,
};
use crate::colors::ColorSchemeChoice;
use crate::render::DrawBuilding;
use crate::tools::grey_out_map;
use crate::AppLike;
#[derive(Clone, Serialize, Deserialize)]
pub struct Options {
pub dev: bool,
pub debug_all_agents: bool,
pub traffic_signal_style: TrafficSignalStyle,
pub color_scheme: ColorSchemeChoice,
pub toggle_day_night_colors: bool,
pub min_zoom_for_detail: f64,
pub camera_angle: CameraAngle,
pub minimal_controls: bool,
pub canvas_settings: CanvasSettings,
pub time_increment: Duration,
pub dont_draw_time_warp: bool,
pub jump_to_delay: Duration,
pub language: Option<String>,
pub units: UnitFmt,
}
impl Options {
pub fn load_or_default() -> Options {
match abstio::maybe_read_json::<Options>(
abstio::path_player("settings.json"),
&mut Timer::throwaway(),
) {
Ok(opts) => {
return opts;
}
Err(err) => {
warn!("Couldn't restore settings, so using defaults. {}", err);
}
}
Options {
dev: false,
debug_all_agents: false,
traffic_signal_style: TrafficSignalStyle::Brian,
color_scheme: ColorSchemeChoice::DayMode,
toggle_day_night_colors: false,
min_zoom_for_detail: 4.0,
camera_angle: CameraAngle::TopDown,
time_increment: Duration::minutes(10),
dont_draw_time_warp: false,
jump_to_delay: Duration::minutes(5),
minimal_controls: false,
canvas_settings: CanvasSettings::new(),
language: None,
units: UnitFmt {
round_durations: true,
metric: false,
},
}
}
pub fn update_from_args(&mut self, args: &mut CmdArgs) {
self.dev = args.enabled("--dev");
if args.enabled("--lowzoom") {
self.min_zoom_for_detail = 1.0;
}
if let Some(x) = args.optional("--color_scheme") {
let mut ok = false;
let mut options = Vec::new();
for c in ColorSchemeChoice::choices() {
options.push(c.label.clone());
if c.label == x {
self.color_scheme = c.data;
ok = true;
break;
}
}
if !ok {
panic!(
"Invalid --color_scheme={}. Choices: {}",
x,
options.join(", ")
);
}
}
self.minimal_controls = args.enabled("--minimal_controls")
}
}
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
pub enum TrafficSignalStyle {
Brian,
Yuwen,
IndividualTurnArrows,
}
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
pub enum CameraAngle {
TopDown,
IsometricNE,
IsometricNW,
IsometricSE,
IsometricSW,
Abstract,
}
pub struct OptionsPanel {
panel: Panel,
}
impl OptionsPanel {
pub fn new_state<A: AppLike>(ctx: &mut EventCtx, app: &A) -> Box<dyn State<A>> {
Box::new(OptionsPanel {
panel: Panel::new_builder(Widget::col(vec![
Widget::custom_row(vec![
Line("Settings").small_heading().into_widget(ctx),
ctx.style().btn_close_widget(ctx),
]),
"Camera controls".text_widget(ctx),
Widget::col(vec![
Toggle::checkbox(
ctx,
"Invert direction of vertical scrolling",
None,
ctx.canvas.settings.invert_scroll,
),
Toggle::checkbox(
ctx,
"Pan map when cursor is at edge of screen",
None,
ctx.canvas.settings.edge_auto_panning,
)
.named("autopan"),
Toggle::checkbox(
ctx,
"Use touchpad to pan and hold Control to zoom",
None,
ctx.canvas.settings.touchpad_to_move,
),
Toggle::checkbox(
ctx,
"Use arrow keys to pan and Q/W to zoom",
None,
ctx.canvas.settings.keys_to_pan,
),
Widget::row(vec![
"Scroll speed for menus".text_widget(ctx).centered_vert(),
Spinner::widget(
ctx,
"gui_scroll_speed",
(1, 50),
ctx.canvas.settings.gui_scroll_speed,
1,
),
]),
Widget::row(vec![
"Zoom speed for the map".text_widget(ctx).centered_vert(),
Spinner::widget(
ctx,
"canvas_scroll_speed",
(1, 30),
ctx.canvas.settings.canvas_scroll_speed,
1,
),
]),
])
.bg(app.cs().inner_panel_bg)
.padding(8),
"Appearance".text_widget(ctx),
Widget::col(vec![
Widget::row(vec![
"Traffic signal rendering:".text_widget(ctx),
Widget::dropdown(
ctx,
"Traffic signal rendering",
app.opts().traffic_signal_style.clone(),
vec![
Choice::new("Default (Brian's style)", TrafficSignalStyle::Brian),
Choice::new("Yuwen's style", TrafficSignalStyle::Yuwen),
Choice::new(
"arrows showing individual turns (to debug)",
TrafficSignalStyle::IndividualTurnArrows,
),
],
),
]),
Widget::row(vec![
"Camera angle:".text_widget(ctx),
Widget::dropdown(
ctx,
"Camera angle",
app.opts().camera_angle.clone(),
vec![
Choice::new("Top-down", CameraAngle::TopDown),
Choice::new("Isometric (northeast)", CameraAngle::IsometricNE),
Choice::new("Isometric (northwest)", CameraAngle::IsometricNW),
Choice::new("Isometric (southeast)", CameraAngle::IsometricSE),
Choice::new("Isometric (southwest)", CameraAngle::IsometricSW),
Choice::new("Abstract (just symbols)", CameraAngle::Abstract),
],
),
]),
Widget::row(vec![
"Color scheme:".text_widget(ctx),
Widget::dropdown(
ctx,
"Color scheme",
app.opts().color_scheme,
ColorSchemeChoice::choices(),
),
]),
Widget::row(vec![
"Camera zoom to switch to unzoomed view".text_widget(ctx),
Widget::dropdown(
ctx,
"min zoom",
app.opts().min_zoom_for_detail,
vec![
Choice::new("1.0", 1.0),
Choice::new("2.0", 2.0),
Choice::new("3.0", 3.0),
Choice::new("4.0", 4.0),
Choice::new("5.0", 5.0),
Choice::new("6.0", 6.0),
],
),
]),
Widget::row(vec![
"Language".text_widget(ctx),
Widget::dropdown(ctx, "language", app.opts().language.clone(), {
let mut choices = vec![Choice::new("Map native language", None)];
for lang in app.map().get_languages() {
choices.push(Choice::new(lang, Some(lang.to_string())));
}
choices
}),
]),
Toggle::choice(
ctx,
"metric / imperial units",
"metric",
"imperial",
None,
app.opts().units.metric,
),
])
.bg(app.cs().inner_panel_bg)
.padding(8),
"Debug".text_widget(ctx),
Widget::col(vec![
Toggle::checkbox(ctx, "Enable developer mode", None, app.opts().dev),
Toggle::checkbox(
ctx,
"Draw all agents to debug geometry (Slow!)",
None,
app.opts().debug_all_agents,
),
])
.bg(app.cs().inner_panel_bg)
.padding(8),
ctx.style()
.btn_solid_primary
.text("Apply")
.hotkey(Key::Enter)
.build_def(ctx)
.centered_horiz(),
]))
.build(ctx),
})
}
}
impl<A: AppLike> State<A> for OptionsPanel {
fn event(&mut self, ctx: &mut EventCtx, app: &mut A) -> widgetry::Transition<A> {
if let Outcome::Clicked(x) = self.panel.event(ctx) {
match x.as_ref() {
"close" => {
return widgetry::Transition::Pop;
}
"Apply" => {
let mut opts = app.opts().clone();
opts.dev = self.panel.is_checked("Enable developer mode");
opts.debug_all_agents = self
.panel
.is_checked("Draw all agents to debug geometry (Slow!)");
ctx.canvas.settings.invert_scroll = self
.panel
.is_checked("Invert direction of vertical scrolling");
ctx.canvas.settings.touchpad_to_move = self
.panel
.is_checked("Use touchpad to pan and hold Control to zoom");
ctx.canvas.settings.keys_to_pan = self
.panel
.is_checked("Use arrow keys to pan and Q/W to zoom");
ctx.canvas.settings.edge_auto_panning = self.panel.is_checked("autopan");
ctx.canvas.settings.gui_scroll_speed = self.panel.spinner("gui_scroll_speed");
ctx.canvas.settings.canvas_scroll_speed =
self.panel.spinner("canvas_scroll_speed");
opts.canvas_settings = ctx.canvas.settings.clone();
let style = self.panel.dropdown_value("Traffic signal rendering");
if opts.traffic_signal_style != style {
opts.traffic_signal_style = style;
println!("Rerendering traffic signals...");
for i in &mut app.mut_draw_map().intersections {
*i.draw_traffic_signal.borrow_mut() = None;
}
}
let camera_angle = self.panel.dropdown_value("Camera angle");
if opts.camera_angle != camera_angle {
opts.camera_angle = camera_angle;
ctx.loading_screen("rerendering buildings", |ctx, timer| {
let mut all_buildings = GeomBatch::new();
let mut all_building_outlines = GeomBatch::new();
let mut all_building_driveways = GeomBatch::new();
timer
.start_iter("rendering buildings", app.map().all_buildings().len());
for b in app.map().all_buildings() {
timer.next();
DrawBuilding::new(
ctx,
b,
app.map(),
app.cs(),
&opts,
&mut all_buildings,
&mut all_building_outlines,
);
DrawBuilding::draw_driveway(
b,
app.map(),
app.cs(),
app.opts(),
&mut all_building_driveways,
);
}
timer.start("upload geometry");
app.mut_draw_map().draw_all_buildings = all_buildings.upload(ctx);
app.mut_draw_map().draw_all_building_driveways =
all_building_driveways.upload(ctx);
app.mut_draw_map().draw_all_building_outlines =
all_building_outlines.upload(ctx);
timer.stop("upload geometry");
});
}
if app.change_color_scheme(ctx, self.panel.dropdown_value("Color scheme")) {
opts.color_scheme = app.opts().color_scheme;
opts.toggle_day_night_colors = false;
}
opts.min_zoom_for_detail = self.panel.dropdown_value("min zoom");
opts.units.metric = self.panel.is_checked("metric / imperial units");
let language = self.panel.dropdown_value("language");
if language != opts.language {
opts.language = language;
for r in &mut app.mut_draw_map().roads {
r.clear_rendering();
}
}
abstio::write_json(abstio::path_player("settings.json"), &opts);
*app.mut_opts() = opts;
return widgetry::Transition::Pop;
}
_ => unreachable!(),
}
}
widgetry::Transition::Keep
}
fn draw(&self, g: &mut GfxCtx, app: &A) {
grey_out_map(g, app);
self.panel.draw(g);
}
}