Humble start to adjusting how distances and durations are printed in the UI. #331

Also trimmed out a few old UI settings; they kind of started as
experiments, but now the alternative feels useless to maintain.
This commit is contained in:
Dustin Carlino 2020-10-11 10:45:13 -07:00
parent df873a5cf9
commit a1c53d5e1c
11 changed files with 135 additions and 118 deletions

View File

@ -199,15 +199,10 @@ impl App {
let mut cache = self.primary.draw_map.agents.borrow_mut();
cache.draw_unzoomed_agents(
g,
source,
&self.primary.map,
&self.unzoomed_agents,
g,
if self.opts.large_unzoomed_agents {
Some(Distance::meters(10.0) / g.canvas.cam_zoom)
} else {
None
},
self.opts.debug_all_agents,
&self.cs,
);

View File

@ -285,15 +285,10 @@ impl Minimap {
let mut cache = app.primary.draw_map.agents.borrow_mut();
cache.draw_unzoomed_agents(
g,
&app.primary.sim,
&app.primary.map,
&app.unzoomed_agents,
g,
if app.opts.large_unzoomed_agents {
Some(Distance::meters(2.0 + (self.zoom_lvl as f64)) / self.zoom)
} else {
None
},
app.opts.debug_all_agents,
&app.cs,
);

View File

@ -53,7 +53,7 @@ pub fn info(ctx: &EventCtx, app: &App, details: &mut Details, id: LaneID) -> Vec
kv.push(("Speed limit", r.speed_limit.to_string()));
}
kv.push(("Length", l.length().describe_rounded()));
kv.push(("Length", l.length().to_string(&app.opts.units)));
rows.extend(make_table(ctx, kv));

View File

@ -80,8 +80,8 @@ pub fn ongoing(
.force_width_pct(ctx, col_width),
Widget::col(vec![
Text::from_all(vec![
Line(props.dist_crossed.describe_rounded()),
Line(format!("/{}", props.total_dist.describe_rounded())).secondary(),
Line(props.dist_crossed.to_string(&app.opts.units)),
Line(format!("/{}", props.total_dist.to_string(&app.opts.units))).secondary(),
])
.draw(ctx),
Text::from_all(vec![
@ -488,14 +488,14 @@ fn highlight_slow_lanes(ctx: &EventCtx, app: &App, details: &mut Details, id: Tr
/// Helper func for make_bar()
/// Builds a default text overlay widget
fn build_text(msgs: &Vec<String>, distance: &Distance) -> Text {
fn build_text(app: &App, msgs: &Vec<String>, distance: Distance) -> Text {
let mut display_txt = Text::new();
for msg in msgs {
display_txt.add(Line(msg));
}
display_txt.add(Line(format!(
" Distance covered: {}",
distance.describe_rounded()
distance.to_string(&app.opts.units)
)));
display_txt
}
@ -606,7 +606,7 @@ fn make_bar(
segments.push((
norm_distance,
norm_color,
build_text(&msgs, &norm_distance),
build_text(app, &msgs, norm_distance),
));
let mut display_txt = Text::from(Line(&p.phase_type.describe(map)));
display_txt.add(Line(format!(
@ -617,7 +617,7 @@ fn make_bar(
display_txt.add(Line(format!(" Lane ID: {}", id)));
display_txt.add(Line(format!(
" Lane distance: {}",
lane_detail.length().describe_rounded()
lane_detail.length().to_string(&app.opts.units)
)));
display_txt.add(Line(format!(" Average speed: {}", avg_speed)));
segments.push((lane_detail.length(), Color::RED, display_txt));
@ -633,7 +633,7 @@ fn make_bar(
segments.push((
norm_distance,
norm_color,
build_text(&msgs, &norm_distance),
build_text(app, &msgs, norm_distance),
));
let mut display_txt = Text::from(Line(&p.phase_type.describe(map)));
@ -661,7 +661,11 @@ fn make_bar(
}
}
}
segments.push((norm_distance, norm_color, build_text(&msgs, &norm_distance)));
segments.push((
norm_distance,
norm_color,
build_text(app, &msgs, norm_distance),
));
} else {
// TODO Think of something to do instead
error!("No path for {}", trip_id)

View File

@ -117,7 +117,7 @@ impl BikeNetwork {
Line(format!("{} lanes", num_lanes)),
Line(format!(
"total distance of {}",
total_dist.describe_rounded()
total_dist.to_string(&app.opts.units)
)),
])
.draw(ctx),

View File

@ -1,4 +1,4 @@
use geom::Duration;
use geom::{Duration, UnitFmt};
use widgetry::{
Btn, Checkbox, Choice, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome, Panel, Spinner,
TextExt, Widget,
@ -9,24 +9,39 @@ use crate::colors::{ColorScheme, ColorSchemeChoice};
use crate::game::{State, Transition};
use crate::render::{DrawBuilding, DrawMap};
/// Options controlling the UI.
// TODO SimOptions stuff too
#[derive(Clone)]
pub struct Options {
/// Dev mode exposes experimental tools useful for debugging, but that'd likely confuse most
/// players.
pub dev: bool,
/// Every time we draw, render all agents zoomed in. Extremely slow. Just used to flush out
/// drawing bugs.
pub debug_all_agents: bool,
pub label_roads: bool,
/// How traffic signals should be rendered.
pub traffic_signal_style: TrafficSignalStyle,
/// The color scheme for map elements, agents, and the UI.
pub color_scheme: ColorSchemeChoice,
/// Map elements are drawn differently when unzoomed and zoomed. This specifies the canvas zoom
/// level where they switch.
pub min_zoom_for_detail: f64,
pub large_unzoomed_agents: bool,
/// Draw buildings in different perspectives
pub camera_angle: CameraAngle,
/// How much to advance the sim with one of the speed controls
pub time_increment: Duration,
/// When time warping, don't draw to speed up simulation
pub dont_draw_time_warp: bool,
/// The delay threshold to halt on when jumping to the next delay
pub jump_to_delay: Duration,
/// Display roads and buildings in an alternate language, if possible. None means to use the
/// OSM native name.
pub language: Option<String>,
/// How to render geometric units
pub units: UnitFmt,
}
impl Options {
@ -35,11 +50,9 @@ impl Options {
dev: false,
debug_all_agents: false,
label_roads: true,
traffic_signal_style: TrafficSignalStyle::BAP,
color_scheme: ColorSchemeChoice::Standard,
min_zoom_for_detail: 4.0,
large_unzoomed_agents: false,
camera_angle: CameraAngle::TopDown,
time_increment: Duration::minutes(10),
@ -47,10 +60,16 @@ impl Options {
jump_to_delay: Duration::minutes(5),
language: None,
units: UnitFmt {
round_durations: true,
// TODO Should default be based on the map?
metric: false,
},
}
}
}
/// Different ways of drawing traffic signals. The names of these aren't super meaningful...
#[derive(Clone, PartialEq, Debug)]
pub enum TrafficSignalStyle {
BAP,
@ -118,7 +137,6 @@ impl OptionsPanel {
.padding(8),
"Appearance".draw_text(ctx),
Widget::col(vec![
Checkbox::checkbox(ctx, "Draw road names", None, app.opts.label_roads),
Widget::row(vec![
"Traffic signal rendering:".draw_text(ctx),
Widget::dropdown(
@ -175,12 +193,6 @@ impl OptionsPanel {
],
),
]),
Checkbox::checkbox(
ctx,
"Draw enlarged unzoomed agents",
None,
app.opts.large_unzoomed_agents,
),
Widget::row(vec![
"Language".draw_text(ctx),
Widget::dropdown(ctx, "language", app.opts.language.clone(), {
@ -192,6 +204,14 @@ impl OptionsPanel {
choices
}),
]),
Checkbox::toggle(
ctx,
"metric / imperial units",
"metric",
"imperial",
None,
app.opts.units.metric,
),
])
.bg(app.cs.section_bg)
.padding(8),
@ -241,7 +261,6 @@ impl State for OptionsPanel {
ctx.canvas.edge_auto_panning = self.panel.is_checked("autopan");
ctx.canvas.gui_scroll_speed = self.panel.spinner("gui_scroll_speed") as usize;
app.opts.label_roads = self.panel.is_checked("Draw road names");
let style = self.panel.dropdown_value("Traffic signal rendering");
if app.opts.traffic_signal_style != style {
app.opts.traffic_signal_style = style;
@ -300,8 +319,7 @@ impl State for OptionsPanel {
}
app.opts.min_zoom_for_detail = self.panel.dropdown_value("min zoom");
app.opts.large_unzoomed_agents =
self.panel.is_checked("Draw enlarged unzoomed agents");
app.opts.units.metric = self.panel.is_checked("metric / imperial units");
let language = self.panel.dropdown_value("language");
if language != app.opts.language {

View File

@ -5,7 +5,7 @@ use std::collections::HashMap;
use aabb_quadtree::QuadTree;
use abstutil::Timer;
use geom::{Bounds, Circle, Distance, Polygon, Pt2D, Time};
use geom::{Bounds, Circle, Polygon, Pt2D, Time};
use map_model::{
AreaID, BuildingID, BusStopID, IntersectionID, LaneID, Map, ParkingLotID, RoadID, Traversable,
NORMAL_LANE_THICKNESS, SIDEWALK_THICKNESS,
@ -359,8 +359,7 @@ pub struct AgentCache {
// This time applies to agents_per_on. unzoomed has its own possibly separate Time!
time: Option<Time>,
agents_per_on: HashMap<Traversable, Vec<Box<dyn Renderable>>>,
// agent radius also matters
unzoomed: Option<(Time, Option<Distance>, UnzoomedAgents, Drawable)>,
unzoomed: Option<(Time, UnzoomedAgents, Drawable)>,
}
impl AgentCache {
@ -411,17 +410,16 @@ impl AgentCache {
// simplify this.
pub fn draw_unzoomed_agents(
&mut self,
g: &mut GfxCtx,
source: &dyn GetDrawAgents,
map: &Map,
color_agents: &UnzoomedAgents,
g: &mut GfxCtx,
maybe_radius: Option<Distance>,
debug_all_agents: bool,
cs: &ColorScheme,
) {
let now = source.time();
if let Some((time, r, ref orig_agents, ref draw)) = self.unzoomed {
if now == time && maybe_radius == r && color_agents == orig_agents {
if let Some((time, ref orig_agents, ref draw)) = self.unzoomed {
if now == time && color_agents == orig_agents {
g.redraw(draw);
return;
}
@ -429,34 +427,24 @@ impl AgentCache {
let mut batch = GeomBatch::new();
// It's quite silly to produce triangles for the same circle over and over again. ;)
if let Some(r) = maybe_radius {
let circle = Circle::new(Pt2D::new(0.0, 0.0), r).to_polygon();
for agent in source.get_unzoomed_agents(map) {
if let Some(color) = color_agents.color(&agent) {
batch.push(color, circle.translate(agent.pos.x(), agent.pos.y()));
}
}
} else {
// Lane thickness is a little hard to see, so double it. Most of the time, the circles
// don't leak out of the road too much.
let car_circle =
Circle::new(Pt2D::new(0.0, 0.0), 4.0 * NORMAL_LANE_THICKNESS).to_polygon();
let ped_circle =
Circle::new(Pt2D::new(0.0, 0.0), 4.0 * SIDEWALK_THICKNESS).to_polygon();
for agent in source.get_unzoomed_agents(map) {
if let Some(color) = color_agents.color(&agent) {
if agent.vehicle_type.is_some() {
batch.push(color, car_circle.translate(agent.pos.x(), agent.pos.y()));
} else {
batch.push(color, ped_circle.translate(agent.pos.x(), agent.pos.y()));
}
//
// Lane thickness is a little hard to see, so double it. Most of the time, the circles
// don't leak out of the road too much.
let car_circle = Circle::new(Pt2D::new(0.0, 0.0), 4.0 * NORMAL_LANE_THICKNESS).to_polygon();
let ped_circle = Circle::new(Pt2D::new(0.0, 0.0), 4.0 * SIDEWALK_THICKNESS).to_polygon();
for agent in source.get_unzoomed_agents(map) {
if let Some(color) = color_agents.color(&agent) {
if agent.vehicle_type.is_some() {
batch.push(color, car_circle.translate(agent.pos.x(), agent.pos.y()));
} else {
batch.push(color, ped_circle.translate(agent.pos.x(), agent.pos.y()));
}
}
}
let draw = g.upload(batch);
g.redraw(&draw);
self.unzoomed = Some((now, maybe_radius, color_agents.clone(), draw));
self.unzoomed = Some((now, color_agents.clone(), draw));
if debug_all_agents {
let mut cnt = 0;

View File

@ -71,55 +71,52 @@ impl Renderable for DrawRoad {
}
g.redraw(draw_center_line.as_ref().unwrap());
if app.opts.label_roads {
// Lazily calculate
let mut label = self.label.borrow_mut();
if label.is_none() {
let mut batch = GeomBatch::new();
let r = app.primary.map.get_r(self.id);
if !r.is_light_rail() {
let name = r.get_name(app.opts.language.as_ref());
if r.center_pts.length() >= Distance::meters(30.0) && name != "???" {
// TODO If it's definitely straddling bus/bike lanes, change the color? Or
// even easier, just skip the center lines?
let fg = if r.is_private() {
app.cs.road_center_line.lerp(app.cs.private_road, 0.5)
} else {
app.cs.road_center_line
};
let bg = if r.is_private() {
app.cs
.zoomed_road_surface(LaneType::Driving, r.get_rank())
.lerp(app.cs.private_road, 0.5)
} else {
app.cs.zoomed_road_surface(LaneType::Driving, r.get_rank())
};
// Lazily calculate
let mut label = self.label.borrow_mut();
if label.is_none() {
let mut batch = GeomBatch::new();
let r = app.primary.map.get_r(self.id);
if !r.is_light_rail() {
let name = r.get_name(app.opts.language.as_ref());
if r.center_pts.length() >= Distance::meters(30.0) && name != "???" {
// TODO If it's definitely straddling bus/bike lanes, change the color? Or
// even easier, just skip the center lines?
let fg = if r.is_private() {
app.cs.road_center_line.lerp(app.cs.private_road, 0.5)
} else {
app.cs.road_center_line
};
let bg = if r.is_private() {
app.cs
.zoomed_road_surface(LaneType::Driving, r.get_rank())
.lerp(app.cs.private_road, 0.5)
} else {
app.cs.zoomed_road_surface(LaneType::Driving, r.get_rank())
};
if false {
// TODO Not ready yet
batch.append(Line(name).fg(fg).render_curvey(
g.prerender,
&r.center_pts,
0.1,
));
} else {
let txt = Text::from(Line(name).fg(fg)).bg(bg);
let (pt, angle) =
r.center_pts.must_dist_along(r.center_pts.length() / 2.0);
batch.append(
txt.render_to_batch(g.prerender)
.scale(0.1)
.centered_on(pt)
.rotate(angle.reorient()),
);
}
if false {
// TODO Not ready yet
batch.append(Line(name).fg(fg).render_curvey(
g.prerender,
&r.center_pts,
0.1,
));
} else {
let txt = Text::from(Line(name).fg(fg)).bg(bg);
let (pt, angle) = r.center_pts.must_dist_along(r.center_pts.length() / 2.0);
batch.append(
txt.render_to_batch(g.prerender)
.scale(0.1)
.centered_on(pt)
.rotate(angle.reorient()),
);
}
}
*label = Some(g.prerender.upload(batch));
}
// TODO Covered up sometimes. We could fork and force a different z value...
g.redraw(label.as_ref().unwrap());
*label = Some(g.prerender.upload(batch));
}
// TODO Covered up sometimes. We could fork and force a different z value...
g.redraw(label.as_ref().unwrap());
}
fn get_outline(&self, map: &Map) -> Polygon {

View File

@ -2,7 +2,7 @@ use std::{cmp, f64, fmt, ops};
use serde::{Deserialize, Serialize};
use crate::{trim_f64, Duration, Speed};
use crate::{trim_f64, Duration, Speed, UnitFmt};
/// In meters. Can be negative.
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
@ -61,14 +61,18 @@ impl Distance {
self.0
}
// TODO Store a bit in Maps to mark if they're in the US or not, plumb here to use meters
pub fn describe_rounded(self) -> String {
let feet = self.0 * 3.28084;
let miles = feet / 5280.0;
if miles >= 0.1 {
format!("{} miles", (miles * 10.0).round() / 10.0)
pub fn to_string(self, fmt: &UnitFmt) -> String {
if fmt.metric {
// TODO Round values to nearest meter, and km
format!("{}m", self.0)
} else {
format!("{} ft", feet.round())
let feet = self.0 * 3.28084;
let miles = feet / 5280.0;
if miles >= 0.1 {
format!("{} miles", (miles * 10.0).round() / 10.0)
} else {
format!("{} ft", feet.round())
}
}
}

View File

@ -35,6 +35,21 @@ mod time;
// About 0.4 inches... which is quite tiny on the scale of things. :)
pub const EPSILON_DIST: Distance = Distance::const_meters(0.01);
/// Reduce the precision of an f64. This helps ensure serialization is idempotent (everything is
/// exacly the same before and after saving/loading). Ideally we'd use some kind of proper
/// fixed-precision type instead of f64.
pub fn trim_f64(x: f64) -> f64 {
(x * 10_000.0).round() / 10_000.0
}
/// Specifies how to stringify different geom objects.
// TODO Use for more units, and also maybe get rid of fmt::Display, to force everyone to opt in.
// Except the default for logging should always be the full detail...
#[derive(Clone)]
pub struct UnitFmt {
/// Round `Duration`s to a whole number of seconds.
// TODO Actually use this
pub round_durations: bool,
/// Display in metric; US imperial otherwise.
pub metric: bool,
}

View File

@ -71,6 +71,7 @@ pub struct Ctx<'a> {
pub map: &'a Map,
}
/// Options controlling the traffic simulation.
#[derive(Clone)]
pub struct SimOptions {
/// Used to distinguish savestates for running the same scenario.