mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-25 07:25:47 +03:00
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:
parent
df873a5cf9
commit
a1c53d5e1c
@ -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,
|
||||
);
|
||||
|
@ -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,
|
||||
);
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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),
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user