mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-25 23:43:25 +03:00
try a huge cutover to the new info panel organization. few implemented,
going to do this in another branch
This commit is contained in:
parent
ccaaff82be
commit
fff8c8ae9d
@ -19,7 +19,7 @@ pub use self::warp::Warping;
|
||||
use crate::app::App;
|
||||
use crate::game::Transition;
|
||||
use crate::helpers::{list_names, ID};
|
||||
use crate::info::{InfoPanel, InfoTab};
|
||||
use crate::info::InfoPanel;
|
||||
use crate::sandbox::SpeedControls;
|
||||
use ezgui::{
|
||||
lctrl, Color, EventCtx, GeomBatch, GfxCtx, Key, Line, ScreenDims, ScreenPt, ScreenRectangle,
|
||||
@ -68,11 +68,10 @@ impl CommonState {
|
||||
{
|
||||
app.per_obj.info_panel_open = true;
|
||||
let actions = app.per_obj.consume();
|
||||
self.info_panel = Some(InfoPanel::new(
|
||||
id.clone(),
|
||||
InfoTab::Nil,
|
||||
self.info_panel = Some(InfoPanel::launch(
|
||||
ctx,
|
||||
app,
|
||||
id.clone(),
|
||||
actions,
|
||||
maybe_speed,
|
||||
));
|
||||
@ -285,11 +284,12 @@ impl CommonState {
|
||||
|
||||
// Meant to be used for launching from other states
|
||||
pub fn launch_info_panel(&mut self, id: ID, ctx: &mut EventCtx, app: &mut App) {
|
||||
self.info_panel = Some(InfoPanel::new(id, InfoTab::Nil, ctx, app, Vec::new(), None));
|
||||
self.info_panel = Some(InfoPanel::launch(ctx, app, id, Vec::new(), None));
|
||||
app.per_obj.info_panel_open = true;
|
||||
}
|
||||
|
||||
pub fn info_panel_open(&self) -> Option<ID> {
|
||||
self.info_panel.as_ref().map(|i| i.id.clone())
|
||||
panic!("TODO")
|
||||
//self.info_panel.as_ref().map(|i| i.id.clone())
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,16 @@
|
||||
use crate::app::App;
|
||||
use crate::info::make_table;
|
||||
use crate::info::{header_btns, make_table, Details};
|
||||
use crate::render::ExtraShapeID;
|
||||
use ezgui::{EventCtx, Line, Widget};
|
||||
use map_model::AreaID;
|
||||
|
||||
pub fn area(
|
||||
ctx: &EventCtx,
|
||||
app: &App,
|
||||
id: AreaID,
|
||||
header_btns: Widget,
|
||||
action_btns: Vec<Widget>,
|
||||
) -> Vec<Widget> {
|
||||
pub fn area(ctx: &EventCtx, app: &App, _: &mut Details, id: AreaID) -> Vec<Widget> {
|
||||
let mut rows = vec![];
|
||||
|
||||
rows.push(Widget::row(vec![
|
||||
Line(format!("Area #{}", id.0)).small_heading().draw(ctx),
|
||||
header_btns,
|
||||
header_btns(ctx),
|
||||
]));
|
||||
rows.extend(action_btns);
|
||||
|
||||
let a = app.primary.map.get_a(id);
|
||||
let mut kv = Vec::new();
|
||||
@ -29,22 +22,15 @@ pub fn area(
|
||||
rows
|
||||
}
|
||||
|
||||
pub fn extra_shape(
|
||||
ctx: &EventCtx,
|
||||
app: &App,
|
||||
id: ExtraShapeID,
|
||||
header_btns: Widget,
|
||||
action_btns: Vec<Widget>,
|
||||
) -> Vec<Widget> {
|
||||
pub fn extra_shape(ctx: &EventCtx, app: &App, _: &mut Details, id: ExtraShapeID) -> Vec<Widget> {
|
||||
let mut rows = vec![];
|
||||
|
||||
rows.push(Widget::row(vec![
|
||||
Line(format!("Extra GIS shape #{}", id.0))
|
||||
.small_heading()
|
||||
.draw(ctx),
|
||||
header_btns,
|
||||
header_btns(ctx),
|
||||
]));
|
||||
rows.extend(action_btns);
|
||||
|
||||
let es = app.primary.draw_map.get_es(id);
|
||||
let mut kv = Vec::new();
|
||||
|
@ -1,132 +1,99 @@
|
||||
use crate::app::App;
|
||||
use crate::helpers::{rotating_color_map, ID};
|
||||
use crate::info::{make_tabs, throughput, InfoTab};
|
||||
use crate::helpers::rotating_color_map;
|
||||
use crate::info::{header_btns, make_tabs, throughput, Details, Tab};
|
||||
use abstutil::prettyprint_usize;
|
||||
use ezgui::{EventCtx, Line, Plot, PlotOptions, Series, Text, Widget};
|
||||
use geom::{Duration, Statistic, Time};
|
||||
use map_model::{IntersectionID, IntersectionType};
|
||||
use sim::{Analytics, TripEndpoint};
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum Tab {
|
||||
Throughput,
|
||||
Delay,
|
||||
}
|
||||
|
||||
pub fn info(
|
||||
ctx: &EventCtx,
|
||||
app: &App,
|
||||
id: IntersectionID,
|
||||
tab: InfoTab,
|
||||
header_btns: Widget,
|
||||
action_btns: Vec<Widget>,
|
||||
hyperlinks: &mut HashMap<String, (ID, InfoTab)>,
|
||||
) -> Vec<Widget> {
|
||||
let mut rows = vec![];
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
pub fn info(ctx: &EventCtx, app: &App, details: &mut Details, id: IntersectionID) -> Vec<Widget> {
|
||||
let mut rows = header(ctx, app, details, id, Tab::IntersectionInfo(id));
|
||||
let i = app.primary.map.get_i(id);
|
||||
|
||||
let label = match i.intersection_type {
|
||||
IntersectionType::StopSign => format!("Intersection #{} (Stop signs)", id.0),
|
||||
IntersectionType::TrafficSignal => format!("Intersection #{} (Traffic signals)", id.0),
|
||||
IntersectionType::Border => format!("Border #{}", id.0),
|
||||
IntersectionType::Construction => format!("Intersection #{} (under construction)", id.0),
|
||||
};
|
||||
rows.push(Widget::row(vec![
|
||||
Line(label).small_heading().draw(ctx),
|
||||
header_btns,
|
||||
]));
|
||||
let mut txt = Text::from(Line("Connecting"));
|
||||
let mut road_names = BTreeSet::new();
|
||||
for r in &i.roads {
|
||||
road_names.insert(app.primary.map.get_r(*r).get_name());
|
||||
}
|
||||
for r in road_names {
|
||||
// TODO The spacing is ignored, so use -
|
||||
txt.add(Line(format!("- {}", r)));
|
||||
}
|
||||
rows.push(txt.draw(ctx));
|
||||
|
||||
rows.push(make_tabs(
|
||||
ctx,
|
||||
hyperlinks,
|
||||
ID::Intersection(id),
|
||||
tab.clone(),
|
||||
{
|
||||
let mut tabs = vec![
|
||||
("Info", InfoTab::Nil),
|
||||
("Traffic", InfoTab::Intersection(Tab::Throughput)),
|
||||
];
|
||||
if app.primary.map.get_i(id).is_traffic_signal() {
|
||||
tabs.push(("Delay", InfoTab::Intersection(Tab::Delay)));
|
||||
}
|
||||
tabs
|
||||
},
|
||||
));
|
||||
|
||||
match tab {
|
||||
InfoTab::Nil => {
|
||||
rows.extend(action_btns);
|
||||
|
||||
let mut txt = Text::from(Line("Connecting"));
|
||||
let mut road_names = BTreeSet::new();
|
||||
for r in &i.roads {
|
||||
road_names.insert(app.primary.map.get_r(*r).get_name());
|
||||
}
|
||||
for r in road_names {
|
||||
// TODO The spacing is ignored, so use -
|
||||
txt.add(Line(format!("- {}", r)));
|
||||
}
|
||||
rows.push(txt.draw(ctx));
|
||||
|
||||
// TODO Rethink
|
||||
let trip_lines = app
|
||||
.primary
|
||||
.sim
|
||||
.count_trips(TripEndpoint::Border(id))
|
||||
.describe();
|
||||
if !trip_lines.is_empty() {
|
||||
let mut txt = Text::new();
|
||||
for line in trip_lines {
|
||||
txt.add(Line(line));
|
||||
}
|
||||
rows.push(txt.draw(ctx));
|
||||
}
|
||||
// TODO Rethink
|
||||
let trip_lines = app
|
||||
.primary
|
||||
.sim
|
||||
.count_trips(TripEndpoint::Border(id))
|
||||
.describe();
|
||||
if !trip_lines.is_empty() {
|
||||
let mut txt = Text::new();
|
||||
for line in trip_lines {
|
||||
txt.add(Line(line));
|
||||
}
|
||||
InfoTab::Intersection(Tab::Throughput) => {
|
||||
let mut txt = Text::new();
|
||||
|
||||
txt.add(Line("Throughput"));
|
||||
txt.add(
|
||||
Line(format!(
|
||||
"Since midnight: {} agents crossed",
|
||||
prettyprint_usize(
|
||||
app.primary
|
||||
.sim
|
||||
.get_analytics()
|
||||
.thruput_stats
|
||||
.count_per_intersection
|
||||
.get(id)
|
||||
)
|
||||
))
|
||||
.secondary(),
|
||||
);
|
||||
txt.add(Line(format!("In 20 minute buckets:")).secondary());
|
||||
rows.push(txt.draw(ctx));
|
||||
|
||||
rows.push(
|
||||
throughput(ctx, app, move |a, t| {
|
||||
a.throughput_intersection(t, id, Duration::minutes(20))
|
||||
})
|
||||
.margin(10),
|
||||
);
|
||||
}
|
||||
InfoTab::Intersection(Tab::Delay) => {
|
||||
assert!(app.primary.map.get_i(id).is_traffic_signal());
|
||||
let mut txt = Text::from(Line("Delay"));
|
||||
txt.add(Line(format!("In 20 minute buckets:")).secondary());
|
||||
rows.push(txt.draw(ctx));
|
||||
|
||||
rows.push(delay(ctx, app, id, Duration::minutes(20)).margin(10));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
rows.push(txt.draw(ctx));
|
||||
}
|
||||
|
||||
rows
|
||||
}
|
||||
|
||||
fn delay(ctx: &EventCtx, app: &App, i: IntersectionID, bucket: Duration) -> Widget {
|
||||
pub fn traffic(
|
||||
ctx: &EventCtx,
|
||||
app: &App,
|
||||
details: &mut Details,
|
||||
id: IntersectionID,
|
||||
) -> Vec<Widget> {
|
||||
let mut rows = header(ctx, app, details, id, Tab::IntersectionTraffic(id));
|
||||
let i = app.primary.map.get_i(id);
|
||||
|
||||
let mut txt = Text::new();
|
||||
|
||||
txt.add(Line("Throughput"));
|
||||
txt.add(
|
||||
Line(format!(
|
||||
"Since midnight: {} agents crossed",
|
||||
prettyprint_usize(
|
||||
app.primary
|
||||
.sim
|
||||
.get_analytics()
|
||||
.thruput_stats
|
||||
.count_per_intersection
|
||||
.get(id)
|
||||
)
|
||||
))
|
||||
.secondary(),
|
||||
);
|
||||
txt.add(Line(format!("In 20 minute buckets:")).secondary());
|
||||
rows.push(txt.draw(ctx));
|
||||
|
||||
rows.push(
|
||||
throughput(ctx, app, move |a, t| {
|
||||
a.throughput_intersection(t, id, Duration::minutes(20))
|
||||
})
|
||||
.margin(10),
|
||||
);
|
||||
|
||||
rows
|
||||
}
|
||||
|
||||
pub fn delay(ctx: &EventCtx, app: &App, details: &mut Details, id: IntersectionID) -> Vec<Widget> {
|
||||
let mut rows = header(ctx, app, details, id, Tab::IntersectionDelay(id));
|
||||
let i = app.primary.map.get_i(id);
|
||||
|
||||
assert!(i.is_traffic_signal());
|
||||
let mut txt = Text::from(Line("Delay"));
|
||||
txt.add(Line(format!("In 20 minute buckets:")).secondary());
|
||||
rows.push(txt.draw(ctx));
|
||||
|
||||
rows.push(delay_plot(ctx, app, id, Duration::minutes(20)).margin(10));
|
||||
|
||||
rows
|
||||
}
|
||||
|
||||
fn delay_plot(ctx: &EventCtx, app: &App, i: IntersectionID, bucket: Duration) -> Widget {
|
||||
let get_data = |a: &Analytics, t: Time| {
|
||||
let mut series: Vec<(Statistic, Vec<(Time, Duration)>)> = Statistic::all()
|
||||
.into_iter()
|
||||
@ -170,3 +137,39 @@ fn delay(ctx: &EventCtx, app: &App, i: IntersectionID, bucket: Duration) -> Widg
|
||||
|
||||
Plot::new_duration(ctx, all_series, PlotOptions::new())
|
||||
}
|
||||
|
||||
fn header(
|
||||
ctx: &EventCtx,
|
||||
app: &App,
|
||||
details: &mut Details,
|
||||
id: IntersectionID,
|
||||
tab: Tab,
|
||||
) -> Vec<Widget> {
|
||||
let mut rows = vec![];
|
||||
|
||||
let i = app.primary.map.get_i(id);
|
||||
|
||||
let label = match i.intersection_type {
|
||||
IntersectionType::StopSign => format!("Intersection #{} (Stop signs)", id.0),
|
||||
IntersectionType::TrafficSignal => format!("Intersection #{} (Traffic signals)", id.0),
|
||||
IntersectionType::Border => format!("Border #{}", id.0),
|
||||
IntersectionType::Construction => format!("Intersection #{} (under construction)", id.0),
|
||||
};
|
||||
rows.push(Widget::row(vec![
|
||||
Line(label).small_heading().draw(ctx),
|
||||
header_btns(ctx),
|
||||
]));
|
||||
|
||||
rows.push(make_tabs(ctx, &mut details.hyperlinks, tab, {
|
||||
let mut tabs = vec![
|
||||
("Info", Tab::IntersectionInfo(id)),
|
||||
("Traffic", Tab::IntersectionTraffic(id)),
|
||||
];
|
||||
if i.is_traffic_signal() {
|
||||
tabs.push(("Delay", Tab::IntersectionDelay(id)));
|
||||
}
|
||||
tabs
|
||||
}));
|
||||
|
||||
rows
|
||||
}
|
||||
|
@ -1,27 +1,136 @@
|
||||
use crate::app::App;
|
||||
use crate::helpers::ID;
|
||||
use crate::info::{make_table, make_tabs, throughput, InfoTab};
|
||||
use crate::info::{header_btns, make_table, make_tabs, throughput, Details, Tab};
|
||||
use abstutil::prettyprint_usize;
|
||||
use ezgui::{EventCtx, Line, Text, TextExt, Widget};
|
||||
use geom::Duration;
|
||||
use map_model::LaneID;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum Tab {
|
||||
Debug,
|
||||
Throughput,
|
||||
pub fn info(ctx: &EventCtx, app: &App, details: &mut Details, id: LaneID) -> Vec<Widget> {
|
||||
let mut rows = header(ctx, app, details, id, Tab::LaneInfo(id));
|
||||
let map = &app.primary.map;
|
||||
let l = map.get_l(id);
|
||||
let r = map.get_r(l.parent);
|
||||
|
||||
let mut kv = Vec::new();
|
||||
|
||||
if !l.is_sidewalk() {
|
||||
kv.push(("Type", l.lane_type.describe().to_string()));
|
||||
}
|
||||
|
||||
if l.is_parking() {
|
||||
kv.push((
|
||||
"Parking",
|
||||
format!("{} spots, parallel parking", l.number_parking_spots()),
|
||||
));
|
||||
} else {
|
||||
kv.push(("Speed limit", r.get_speed_limit().to_string()));
|
||||
}
|
||||
|
||||
kv.push(("Length", l.length().describe_rounded()));
|
||||
|
||||
rows.extend(make_table(ctx, kv));
|
||||
|
||||
rows
|
||||
}
|
||||
|
||||
pub fn info(
|
||||
ctx: &EventCtx,
|
||||
app: &App,
|
||||
id: LaneID,
|
||||
tab: InfoTab,
|
||||
header_btns: Widget,
|
||||
action_btns: Vec<Widget>,
|
||||
hyperlinks: &mut HashMap<String, (ID, InfoTab)>,
|
||||
) -> Vec<Widget> {
|
||||
pub fn debug(ctx: &EventCtx, app: &App, details: &mut Details, id: LaneID) -> Vec<Widget> {
|
||||
let mut rows = header(ctx, app, details, id, Tab::LaneDebug(id));
|
||||
let map = &app.primary.map;
|
||||
let l = map.get_l(id);
|
||||
let r = map.get_r(l.parent);
|
||||
|
||||
let mut kv = Vec::new();
|
||||
|
||||
kv.push(("Parent".to_string(), r.id.to_string()));
|
||||
|
||||
if l.is_driving() {
|
||||
kv.push((
|
||||
"Parking blackhole redirect".to_string(),
|
||||
format!("{:?}", l.parking_blackhole),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(types) = l.get_turn_restrictions(r) {
|
||||
kv.push(("Turn restrictions".to_string(), format!("{:?}", types)));
|
||||
}
|
||||
for (restriction, to) in &r.turn_restrictions {
|
||||
kv.push((
|
||||
format!("Restriction from this road to {}", to),
|
||||
format!("{:?}", restriction),
|
||||
));
|
||||
}
|
||||
|
||||
// TODO Simplify and expose everywhere after there's better data
|
||||
kv.push((
|
||||
"Elevation change".to_string(),
|
||||
format!(
|
||||
"{} to {}",
|
||||
map.get_i(l.src_i).elevation,
|
||||
map.get_i(l.dst_i).elevation
|
||||
),
|
||||
));
|
||||
kv.push((
|
||||
"Incline / grade".to_string(),
|
||||
format!("{:.1}%", l.percent_grade(map) * 100.0),
|
||||
));
|
||||
kv.push((
|
||||
"Elevation details".to_string(),
|
||||
format!(
|
||||
"{} over {}",
|
||||
map.get_i(l.dst_i).elevation - map.get_i(l.src_i).elevation,
|
||||
l.length()
|
||||
),
|
||||
));
|
||||
|
||||
rows.extend(make_table(ctx, kv));
|
||||
|
||||
let mut txt = Text::from(Line(""));
|
||||
txt.add(Line("Raw OpenStreetMap data"));
|
||||
rows.push(txt.draw(ctx));
|
||||
|
||||
rows.extend(make_table(ctx, r.osm_tags.clone().into_iter().collect()));
|
||||
|
||||
rows
|
||||
}
|
||||
|
||||
pub fn traffic(ctx: &EventCtx, app: &App, details: &mut Details, id: LaneID) -> Vec<Widget> {
|
||||
let mut rows = header(ctx, app, details, id, Tab::LaneTraffic(id));
|
||||
let map = &app.primary.map;
|
||||
let l = map.get_l(id);
|
||||
let r = map.get_r(l.parent);
|
||||
|
||||
// Since this applies to the entire road, ignore lane type.
|
||||
let mut txt = Text::from(Line(""));
|
||||
txt.add(Line("Throughput (entire road)"));
|
||||
txt.add(
|
||||
Line(format!(
|
||||
"Since midnight: {} agents crossed",
|
||||
prettyprint_usize(
|
||||
app.primary
|
||||
.sim
|
||||
.get_analytics()
|
||||
.thruput_stats
|
||||
.count_per_road
|
||||
.get(r.id)
|
||||
)
|
||||
))
|
||||
.secondary(),
|
||||
);
|
||||
txt.add(Line(format!("In 20 minute buckets:")).secondary());
|
||||
rows.push(txt.draw(ctx));
|
||||
|
||||
let r = map.get_l(id).parent;
|
||||
rows.push(
|
||||
throughput(ctx, app, move |a, t| {
|
||||
a.throughput_road(t, r, Duration::minutes(20))
|
||||
})
|
||||
.margin(10),
|
||||
);
|
||||
|
||||
rows
|
||||
}
|
||||
|
||||
fn header(ctx: &EventCtx, app: &App, details: &mut Details, id: LaneID, tab: Tab) -> Vec<Widget> {
|
||||
let mut rows = vec![];
|
||||
|
||||
let map = &app.primary.map;
|
||||
@ -33,128 +142,20 @@ pub fn info(
|
||||
Line(format!("{} #{}", label, id.0))
|
||||
.small_heading()
|
||||
.draw(ctx),
|
||||
header_btns,
|
||||
header_btns(ctx),
|
||||
]));
|
||||
rows.push(format!("@ {}", r.get_name()).draw_text(ctx));
|
||||
|
||||
rows.push(make_tabs(
|
||||
ctx,
|
||||
hyperlinks,
|
||||
ID::Lane(id),
|
||||
tab.clone(),
|
||||
&mut details.hyperlinks,
|
||||
tab,
|
||||
vec![
|
||||
("Info", InfoTab::Nil),
|
||||
("Traffic", InfoTab::Lane(Tab::Throughput)),
|
||||
("Debug", InfoTab::Lane(Tab::Debug)),
|
||||
("Info", Tab::LaneInfo(id)),
|
||||
("Traffic", Tab::LaneTraffic(id)),
|
||||
("Debug", Tab::LaneDebug(id)),
|
||||
],
|
||||
));
|
||||
|
||||
match tab {
|
||||
InfoTab::Nil => {
|
||||
rows.extend(action_btns);
|
||||
|
||||
let mut kv = Vec::new();
|
||||
|
||||
if !l.is_sidewalk() {
|
||||
kv.push(("Type", l.lane_type.describe().to_string()));
|
||||
}
|
||||
|
||||
if l.is_parking() {
|
||||
kv.push((
|
||||
"Parking",
|
||||
format!("{} spots, parallel parking", l.number_parking_spots()),
|
||||
));
|
||||
} else {
|
||||
kv.push(("Speed limit", r.get_speed_limit().to_string()));
|
||||
}
|
||||
|
||||
kv.push(("Length", l.length().describe_rounded()));
|
||||
|
||||
rows.extend(make_table(ctx, kv));
|
||||
}
|
||||
InfoTab::Lane(Tab::Debug) => {
|
||||
let mut kv = Vec::new();
|
||||
|
||||
kv.push(("Parent".to_string(), r.id.to_string()));
|
||||
|
||||
if l.is_driving() {
|
||||
kv.push((
|
||||
"Parking blackhole redirect".to_string(),
|
||||
format!("{:?}", l.parking_blackhole),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(types) = l.get_turn_restrictions(r) {
|
||||
kv.push(("Turn restrictions".to_string(), format!("{:?}", types)));
|
||||
}
|
||||
for (restriction, to) in &r.turn_restrictions {
|
||||
kv.push((
|
||||
format!("Restriction from this road to {}", to),
|
||||
format!("{:?}", restriction),
|
||||
));
|
||||
}
|
||||
|
||||
// TODO Simplify and expose everywhere after there's better data
|
||||
kv.push((
|
||||
"Elevation change".to_string(),
|
||||
format!(
|
||||
"{} to {}",
|
||||
map.get_i(l.src_i).elevation,
|
||||
map.get_i(l.dst_i).elevation
|
||||
),
|
||||
));
|
||||
kv.push((
|
||||
"Incline / grade".to_string(),
|
||||
format!("{:.1}%", l.percent_grade(map) * 100.0),
|
||||
));
|
||||
kv.push((
|
||||
"Elevation details".to_string(),
|
||||
format!(
|
||||
"{} over {}",
|
||||
map.get_i(l.dst_i).elevation - map.get_i(l.src_i).elevation,
|
||||
l.length()
|
||||
),
|
||||
));
|
||||
|
||||
rows.extend(make_table(ctx, kv));
|
||||
|
||||
let mut txt = Text::from(Line(""));
|
||||
txt.add(Line("Raw OpenStreetMap data"));
|
||||
rows.push(txt.draw(ctx));
|
||||
|
||||
rows.extend(make_table(ctx, r.osm_tags.clone().into_iter().collect()));
|
||||
}
|
||||
InfoTab::Lane(Tab::Throughput) => {
|
||||
// Since this applies to the entire road, ignore lane type.
|
||||
let mut txt = Text::from(Line(""));
|
||||
txt.add(Line("Throughput (entire road)"));
|
||||
txt.add(
|
||||
Line(format!(
|
||||
"Since midnight: {} agents crossed",
|
||||
prettyprint_usize(
|
||||
app.primary
|
||||
.sim
|
||||
.get_analytics()
|
||||
.thruput_stats
|
||||
.count_per_road
|
||||
.get(r.id)
|
||||
)
|
||||
))
|
||||
.secondary(),
|
||||
);
|
||||
txt.add(Line(format!("In 20 minute buckets:")).secondary());
|
||||
rows.push(txt.draw(ctx));
|
||||
|
||||
let r = app.primary.map.get_l(id).parent;
|
||||
rows.push(
|
||||
throughput(ctx, app, move |a, t| {
|
||||
a.throughput_road(t, r, Duration::minutes(20))
|
||||
})
|
||||
.margin(10),
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
rows
|
||||
}
|
||||
|
@ -1,70 +1,128 @@
|
||||
mod agents;
|
||||
mod building;
|
||||
mod bus_stop;
|
||||
//mod building;
|
||||
//mod bus;
|
||||
mod debug;
|
||||
mod intersection;
|
||||
mod lane;
|
||||
mod person;
|
||||
mod trip;
|
||||
//mod person;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::colors;
|
||||
use crate::common::Warping;
|
||||
use crate::game::{msg, State, Transition, WizardState};
|
||||
use crate::game::Transition;
|
||||
use crate::helpers::ID;
|
||||
use crate::render::MIN_ZOOM_FOR_DETAIL;
|
||||
use crate::sandbox::{SandboxMode, SpeedControls};
|
||||
use crate::render::{ExtraShapeID, MIN_ZOOM_FOR_DETAIL};
|
||||
use crate::sandbox::SpeedControls;
|
||||
use ezgui::{
|
||||
hotkey, Btn, Color, Composite, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key,
|
||||
Line, Outcome, Plot, PlotOptions, Series, Text, TextExt, VerticalAlignment, Widget,
|
||||
};
|
||||
use geom::{Circle, Distance, Time};
|
||||
use sim::{AgentID, Analytics, TripID, TripMode, TripResult, VehicleType};
|
||||
use geom::Time;
|
||||
use map_model::{AreaID, BuildingID, BusStopID, IntersectionID, LaneID};
|
||||
use sim::{AgentID, Analytics, CarID, PedestrianID, PersonID, TripMode, VehicleType};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
pub struct InfoPanel {
|
||||
pub id: ID,
|
||||
tab: InfoTab,
|
||||
tab: Tab,
|
||||
time: Time,
|
||||
composite: Composite,
|
||||
|
||||
also_draw: Drawable,
|
||||
trip_details: Option<TripDetails>,
|
||||
unzoomed: Drawable,
|
||||
zoomed: Drawable,
|
||||
|
||||
actions: Vec<(Key, String)>,
|
||||
hyperlinks: HashMap<String, (ID, InfoTab)>,
|
||||
hyperlinks: HashMap<String, Tab>,
|
||||
warpers: HashMap<String, ID>,
|
||||
}
|
||||
|
||||
// TODO Safer to expand out ID cases here
|
||||
// The PartialEq is ONLY used for determining when we're on the current tab. So maybe omit data.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum InfoTab {
|
||||
Nil,
|
||||
Bldg(building::Tab),
|
||||
Lane(lane::Tab),
|
||||
Intersection(intersection::Tab),
|
||||
Agent(agents::Tab),
|
||||
Trip(trip::Tab),
|
||||
Person(person::Tab),
|
||||
pub enum Tab {
|
||||
PersonStatus(PersonID),
|
||||
PersonTrips(PersonID),
|
||||
PersonBio(PersonID),
|
||||
|
||||
Bus(CarID),
|
||||
BusStop(BusStopID),
|
||||
|
||||
ParkedCar(CarID),
|
||||
|
||||
BldgInfo(BuildingID),
|
||||
BldgDebug(BuildingID),
|
||||
BldgPeople(BuildingID),
|
||||
|
||||
Crowd(Vec<PedestrianID>),
|
||||
|
||||
Area(AreaID),
|
||||
ExtraShape(ExtraShapeID),
|
||||
|
||||
IntersectionInfo(IntersectionID),
|
||||
IntersectionTraffic(IntersectionID),
|
||||
IntersectionDelay(IntersectionID),
|
||||
|
||||
LaneInfo(LaneID),
|
||||
LaneDebug(LaneID),
|
||||
LaneTraffic(LaneID),
|
||||
}
|
||||
|
||||
pub struct TripDetails {
|
||||
id: TripID,
|
||||
unzoomed: Drawable,
|
||||
zoomed: Drawable,
|
||||
impl Tab {
|
||||
fn from_id(app: &App, id: ID) -> Tab {
|
||||
match id {
|
||||
ID::Road(_) => unreachable!(),
|
||||
ID::Lane(l) => Tab::LaneInfo(l),
|
||||
ID::Intersection(i) => Tab::IntersectionInfo(i),
|
||||
ID::Turn(_) => unreachable!(),
|
||||
ID::Building(b) => Tab::BldgInfo(b),
|
||||
ID::Car(c) => {
|
||||
if let Some(p) = app.primary.sim.agent_to_person(AgentID::Car(c)) {
|
||||
Tab::PersonStatus(p)
|
||||
} else if c.1 == VehicleType::Bus {
|
||||
Tab::Bus(c)
|
||||
} else {
|
||||
Tab::ParkedCar(c)
|
||||
}
|
||||
}
|
||||
ID::Pedestrian(p) => Tab::PersonStatus(
|
||||
app.primary
|
||||
.sim
|
||||
.agent_to_person(AgentID::Pedestrian(p))
|
||||
.unwrap(),
|
||||
),
|
||||
ID::PedCrowd(members) => Tab::Crowd(members),
|
||||
ID::ExtraShape(es) => Tab::ExtraShape(es),
|
||||
ID::BusStop(bs) => Tab::BusStop(bs),
|
||||
ID::Area(a) => Tab::Area(a),
|
||||
ID::Trip(_) => unreachable!(),
|
||||
ID::Person(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Name sucks
|
||||
pub struct Details {
|
||||
pub unzoomed: GeomBatch,
|
||||
pub zoomed: GeomBatch,
|
||||
pub hyperlinks: HashMap<String, Tab>,
|
||||
pub warpers: HashMap<String, ID>,
|
||||
}
|
||||
|
||||
impl InfoPanel {
|
||||
pub fn new(
|
||||
id: ID,
|
||||
tab: InfoTab,
|
||||
pub fn launch(
|
||||
ctx: &mut EventCtx,
|
||||
app: &App,
|
||||
id: ID,
|
||||
actions: Vec<(Key, String)>,
|
||||
maybe_speed: Option<&mut SpeedControls>,
|
||||
) -> InfoPanel {
|
||||
InfoPanel::new(ctx, app, Tab::from_id(app, id), actions, maybe_speed)
|
||||
}
|
||||
|
||||
fn new(
|
||||
ctx: &mut EventCtx,
|
||||
app: &App,
|
||||
tab: Tab,
|
||||
mut actions: Vec<(Key, String)>,
|
||||
maybe_speed: Option<&mut SpeedControls>,
|
||||
) -> InfoPanel {
|
||||
if maybe_speed.map(|s| s.is_paused()).unwrap_or(false)
|
||||
/*if maybe_speed.map(|s| s.is_paused()).unwrap_or(false)
|
||||
&& id.agent_id().is_some()
|
||||
&& actions
|
||||
.get(0)
|
||||
@ -72,8 +130,9 @@ impl InfoPanel {
|
||||
.unwrap_or(true)
|
||||
{
|
||||
actions.insert(0, (Key::F, "follow agent".to_string()));
|
||||
}
|
||||
}*/
|
||||
|
||||
// TODO dont forget these
|
||||
let action_btns = actions
|
||||
.iter()
|
||||
.map(|(key, label)| {
|
||||
@ -84,11 +143,17 @@ impl InfoPanel {
|
||||
.build_def(ctx, hotkey(*key))
|
||||
.margin(5)
|
||||
})
|
||||
.collect();
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut batch = GeomBatch::new();
|
||||
// TODO Handle transitions between peds and crowds better
|
||||
if let Some(obj) = app.primary.draw_map.get_obj(
|
||||
let mut details = Details {
|
||||
unzoomed: GeomBatch::new(),
|
||||
zoomed: GeomBatch::new(),
|
||||
hyperlinks: HashMap::new(),
|
||||
warpers: HashMap::new(),
|
||||
};
|
||||
|
||||
// Highlight something?
|
||||
/*if let Some(obj) = app.primary.draw_map.get_obj(
|
||||
id.clone(),
|
||||
app,
|
||||
&mut app.primary.draw_map.agents.borrow_mut(),
|
||||
@ -132,146 +197,35 @@ impl InfoPanel {
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
let header_btns = Widget::row(vec![
|
||||
Btn::svg_def("../data/system/assets/tools/location.svg")
|
||||
.build(ctx, "jump to object", hotkey(Key::J))
|
||||
.margin(5),
|
||||
Btn::text_fg("X").build(ctx, "close info", hotkey(Key::Escape)),
|
||||
])
|
||||
.align_right();
|
||||
let mut hyperlinks = HashMap::new();
|
||||
let mut warpers = HashMap::new();
|
||||
let (col, trip_details) = match id.clone() {
|
||||
ID::Road(_) => unreachable!(),
|
||||
ID::Lane(id) => (
|
||||
lane::info(
|
||||
ctx,
|
||||
app,
|
||||
id,
|
||||
tab.clone(),
|
||||
header_btns,
|
||||
action_btns,
|
||||
&mut hyperlinks,
|
||||
),
|
||||
None,
|
||||
),
|
||||
ID::Intersection(id) => (
|
||||
intersection::info(
|
||||
ctx,
|
||||
app,
|
||||
id,
|
||||
tab.clone(),
|
||||
header_btns,
|
||||
action_btns,
|
||||
&mut hyperlinks,
|
||||
),
|
||||
None,
|
||||
),
|
||||
ID::Turn(_) => unreachable!(),
|
||||
ID::Building(id) => (
|
||||
building::info(
|
||||
ctx,
|
||||
app,
|
||||
id,
|
||||
tab.clone(),
|
||||
header_btns,
|
||||
action_btns,
|
||||
&mut batch,
|
||||
&mut hyperlinks,
|
||||
),
|
||||
None,
|
||||
),
|
||||
ID::Car(id) => agents::car_info(
|
||||
ctx,
|
||||
app,
|
||||
id,
|
||||
tab.clone(),
|
||||
header_btns,
|
||||
action_btns,
|
||||
&mut batch,
|
||||
&mut hyperlinks,
|
||||
&mut warpers,
|
||||
),
|
||||
ID::Pedestrian(id) => agents::ped_info(
|
||||
ctx,
|
||||
app,
|
||||
id,
|
||||
tab.clone(),
|
||||
header_btns,
|
||||
action_btns,
|
||||
&mut hyperlinks,
|
||||
&mut warpers,
|
||||
),
|
||||
ID::PedCrowd(members) => {
|
||||
assert!(action_btns.is_empty());
|
||||
(
|
||||
agents::crowd_info(
|
||||
ctx,
|
||||
app,
|
||||
members,
|
||||
tab.clone(),
|
||||
header_btns,
|
||||
&mut hyperlinks,
|
||||
&mut warpers,
|
||||
),
|
||||
None,
|
||||
)
|
||||
}
|
||||
ID::BusStop(id) => (bus_stop::info(ctx, app, id, header_btns, action_btns), None),
|
||||
ID::Area(id) => (debug::area(ctx, app, id, header_btns, action_btns), None),
|
||||
ID::ExtraShape(id) => (
|
||||
debug::extra_shape(ctx, app, id, header_btns, action_btns),
|
||||
None,
|
||||
),
|
||||
ID::Trip(id) => match app.primary.sim.trip_to_agent(id).ok() {
|
||||
Some(AgentID::Car(c)) => agents::car_info(
|
||||
ctx,
|
||||
app,
|
||||
c,
|
||||
tab.clone(),
|
||||
header_btns,
|
||||
Vec::new(),
|
||||
&mut batch,
|
||||
&mut hyperlinks,
|
||||
&mut warpers,
|
||||
),
|
||||
Some(AgentID::Pedestrian(p)) => agents::ped_info(
|
||||
ctx,
|
||||
app,
|
||||
p,
|
||||
tab.clone(),
|
||||
header_btns,
|
||||
Vec::new(),
|
||||
&mut hyperlinks,
|
||||
&mut warpers,
|
||||
),
|
||||
None => trip::inactive_info(
|
||||
ctx,
|
||||
app,
|
||||
id,
|
||||
tab.clone(),
|
||||
action_btns,
|
||||
&mut hyperlinks,
|
||||
&mut warpers,
|
||||
),
|
||||
},
|
||||
ID::Person(id) => (
|
||||
person::info(
|
||||
ctx,
|
||||
app,
|
||||
id,
|
||||
tab.clone(),
|
||||
Some(header_btns),
|
||||
action_btns,
|
||||
&mut hyperlinks,
|
||||
&mut warpers,
|
||||
),
|
||||
None,
|
||||
),
|
||||
let col = match tab {
|
||||
/*Tab::PersonStatus(p) => person::status(ctx, app, &mut details, p),
|
||||
Tab::PersonTrips(p) => person::trips(ctx, app, &mut details, p),
|
||||
Tab::PersonBio(p) => person::bio(ctx, app, &mut details, p),
|
||||
|
||||
Tab::Bus(c) => bus::bus(ctx, app, &mut details, c),
|
||||
Tab::BusStop(bs) => bus::stop(ctx, app, &mut details, bs),
|
||||
|
||||
Tab::ParkedCar(c) => person::parked_car(ctx, app, &mut details, c),
|
||||
|
||||
Tab::BldgInfo(b) => building::info(ctx, app, &mut details, b),
|
||||
Tab::BldgDebug(b) => building::debug(ctx, app, &mut details, b),
|
||||
Tab::BldgPeople(b) => building::people(ctx, app, &mut details, b),
|
||||
|
||||
Tab::Crowd(members) => person::crowd(ctx, app, &mut details, members),*/
|
||||
Tab::Area(a) => debug::area(ctx, app, &mut details, a),
|
||||
Tab::ExtraShape(es) => debug::extra_shape(ctx, app, &mut details, es),
|
||||
Tab::IntersectionInfo(i) => intersection::info(ctx, app, &mut details, i),
|
||||
Tab::IntersectionTraffic(i) => intersection::traffic(ctx, app, &mut details, i),
|
||||
Tab::IntersectionDelay(i) => intersection::delay(ctx, app, &mut details, i),
|
||||
Tab::LaneInfo(l) => lane::info(ctx, app, &mut details, l),
|
||||
Tab::LaneDebug(l) => lane::debug(ctx, app, &mut details, l),
|
||||
Tab::LaneTraffic(l) => lane::traffic(ctx, app, &mut details, l),
|
||||
_ => panic!("TODO"),
|
||||
};
|
||||
|
||||
/*
|
||||
// Follow the agent. When the sim is paused, this lets the player naturally pan away,
|
||||
// because the InfoPanel isn't being updated.
|
||||
if let Some(pt) = id
|
||||
@ -279,13 +233,11 @@ impl InfoPanel {
|
||||
.and_then(|a| app.primary.sim.canonical_pt_for_agent(a, &app.primary.map))
|
||||
{
|
||||
ctx.canvas.center_on_map_pt(pt);
|
||||
}
|
||||
}*/
|
||||
|
||||
InfoPanel {
|
||||
id,
|
||||
tab,
|
||||
actions,
|
||||
trip_details,
|
||||
time: app.primary.sim.time(),
|
||||
composite: Composite::new(Widget::col(col).bg(colors::PANEL_BG).padding(10))
|
||||
.aligned(
|
||||
@ -294,9 +246,10 @@ impl InfoPanel {
|
||||
)
|
||||
.max_size_percent(35, 60)
|
||||
.build(ctx),
|
||||
also_draw: batch.upload(ctx),
|
||||
hyperlinks,
|
||||
warpers,
|
||||
unzoomed: details.unzoomed.upload(ctx),
|
||||
zoomed: details.zoomed.upload(ctx),
|
||||
hyperlinks: details.hyperlinks,
|
||||
warpers: details.warpers,
|
||||
}
|
||||
}
|
||||
|
||||
@ -317,86 +270,17 @@ impl InfoPanel {
|
||||
|
||||
// Live update?
|
||||
if app.primary.sim.time() != self.time {
|
||||
if let Some(a) = self.id.agent_id() {
|
||||
if let Some(ref details) = self.trip_details {
|
||||
match app.primary.sim.trip_to_agent(details.id) {
|
||||
TripResult::Ok(a2) => {
|
||||
if a != a2 {
|
||||
if !app.primary.sim.does_agent_exist(a) {
|
||||
*self = InfoPanel::new(
|
||||
ID::from_agent(a2),
|
||||
InfoTab::Nil,
|
||||
ctx,
|
||||
app,
|
||||
Vec::new(),
|
||||
maybe_speed,
|
||||
);
|
||||
return (
|
||||
false,
|
||||
Some(Transition::Push(msg(
|
||||
"The trip is transitioning to a new mode",
|
||||
vec![format!(
|
||||
"{} is now {}, following them instead",
|
||||
agent_name(a),
|
||||
agent_name(a2)
|
||||
)],
|
||||
))),
|
||||
);
|
||||
}
|
||||
|
||||
return (true, Some(Transition::Push(trip_transition(a, a2))));
|
||||
}
|
||||
}
|
||||
TripResult::TripDone => {
|
||||
*self = InfoPanel::new(
|
||||
ID::Trip(details.id),
|
||||
InfoTab::Nil,
|
||||
ctx,
|
||||
app,
|
||||
Vec::new(),
|
||||
maybe_speed,
|
||||
);
|
||||
return (
|
||||
false,
|
||||
Some(Transition::Push(msg(
|
||||
"Trip complete",
|
||||
vec![format!(
|
||||
"{} has finished their trip. Say goodbye!",
|
||||
agent_name(a)
|
||||
)],
|
||||
))),
|
||||
);
|
||||
}
|
||||
TripResult::TripNotStarted | TripResult::TripDoesntExist => unreachable!(),
|
||||
// Just wait a moment for trip_transition to kick in...
|
||||
TripResult::ModeChange => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO Detect crowds changing here maybe
|
||||
|
||||
let preserve_scroll = self.composite.preserve_scroll();
|
||||
*self = InfoPanel::new(
|
||||
self.id.clone(),
|
||||
self.tab.clone(),
|
||||
ctx,
|
||||
app,
|
||||
self.actions.clone(),
|
||||
maybe_speed,
|
||||
);
|
||||
self.composite.restore_scroll(ctx, preserve_scroll);
|
||||
return (false, None);
|
||||
// TODO
|
||||
}
|
||||
|
||||
match self.composite.event(ctx) {
|
||||
Some(Outcome::Clicked(action)) => {
|
||||
if let Some((new_id, tab)) = self.hyperlinks.get(&action).cloned() {
|
||||
if let Some(new_tab) = self.hyperlinks.get(&action).cloned() {
|
||||
*self = InfoPanel::new(
|
||||
new_id.clone(),
|
||||
tab,
|
||||
ctx,
|
||||
app,
|
||||
if self.id == new_id {
|
||||
new_tab.clone(),
|
||||
if self.tab == new_tab {
|
||||
self.actions.clone()
|
||||
} else {
|
||||
Vec::new()
|
||||
@ -407,7 +291,7 @@ impl InfoPanel {
|
||||
} else if action == "close info" {
|
||||
(true, None)
|
||||
} else if action == "jump to object" {
|
||||
(
|
||||
/*(
|
||||
false,
|
||||
Some(Transition::Push(Warping::new(
|
||||
ctx,
|
||||
@ -416,7 +300,8 @@ impl InfoPanel {
|
||||
Some(self.id.clone()),
|
||||
&mut app.primary,
|
||||
))),
|
||||
)
|
||||
)*/
|
||||
(false, None)
|
||||
} else if action == "follow agent" {
|
||||
maybe_speed.unwrap().resume_realtime(ctx);
|
||||
(false, None)
|
||||
@ -434,20 +319,10 @@ impl InfoPanel {
|
||||
&mut app.primary,
|
||||
))),
|
||||
)
|
||||
} else if action == "Info" {
|
||||
// Genericish
|
||||
*self = InfoPanel::new(
|
||||
self.id.clone(),
|
||||
InfoTab::Nil,
|
||||
ctx,
|
||||
app,
|
||||
self.actions.clone(),
|
||||
maybe_speed,
|
||||
);
|
||||
return (false, None);
|
||||
} else {
|
||||
app.primary.current_selection = Some(self.id.clone());
|
||||
(true, Some(Transition::ApplyObjectAction(action)))
|
||||
/*app.primary.current_selection = Some(self.id.clone());
|
||||
(true, Some(Transition::ApplyObjectAction(action)))*/
|
||||
(false, None)
|
||||
}
|
||||
}
|
||||
None => (false, None),
|
||||
@ -456,14 +331,11 @@ impl InfoPanel {
|
||||
|
||||
pub fn draw(&self, g: &mut GfxCtx) {
|
||||
self.composite.draw(g);
|
||||
if let Some(ref details) = self.trip_details {
|
||||
if g.canvas.cam_zoom < MIN_ZOOM_FOR_DETAIL {
|
||||
g.redraw(&details.unzoomed);
|
||||
} else {
|
||||
g.redraw(&details.zoomed);
|
||||
}
|
||||
if g.canvas.cam_zoom < MIN_ZOOM_FOR_DETAIL {
|
||||
g.redraw(&self.unzoomed);
|
||||
} else {
|
||||
g.redraw(&self.zoomed);
|
||||
}
|
||||
g.redraw(&self.also_draw);
|
||||
}
|
||||
}
|
||||
|
||||
@ -527,35 +399,6 @@ fn color_for_mode(m: TripMode, app: &App) -> Color {
|
||||
}
|
||||
}
|
||||
|
||||
fn trip_transition(from: AgentID, to: AgentID) -> Box<dyn State> {
|
||||
WizardState::new(Box::new(move |wiz, ctx, _| {
|
||||
let orig = format!("keep following {}", agent_name(from));
|
||||
let change = format!("follow {} instead", agent_name(to));
|
||||
|
||||
let id = if wiz
|
||||
.wrap(ctx)
|
||||
.choose_string("The trip is transitioning to a new mode", || {
|
||||
vec![orig.clone(), change.clone()]
|
||||
})?
|
||||
== orig
|
||||
{
|
||||
ID::from_agent(from)
|
||||
} else {
|
||||
ID::from_agent(to)
|
||||
};
|
||||
Some(Transition::PopWithData(Box::new(move |state, app, ctx| {
|
||||
state
|
||||
.downcast_mut::<SandboxMode>()
|
||||
.unwrap()
|
||||
.controls
|
||||
.common
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.launch_info_panel(id, ctx, app);
|
||||
})))
|
||||
}))
|
||||
}
|
||||
|
||||
fn agent_name(a: AgentID) -> String {
|
||||
match a {
|
||||
AgentID::Car(c) => match c.1 {
|
||||
@ -569,17 +412,16 @@ fn agent_name(a: AgentID) -> String {
|
||||
|
||||
fn make_tabs(
|
||||
ctx: &EventCtx,
|
||||
hyperlinks: &mut HashMap<String, (ID, InfoTab)>,
|
||||
id: ID,
|
||||
current_tab: InfoTab,
|
||||
tabs: Vec<(&str, InfoTab)>,
|
||||
hyperlinks: &mut HashMap<String, Tab>,
|
||||
current_tab: Tab,
|
||||
tabs: Vec<(&str, Tab)>,
|
||||
) -> Widget {
|
||||
let mut row = Vec::new();
|
||||
for (name, link) in tabs {
|
||||
if current_tab == link {
|
||||
row.push(Btn::text_bg2(name).inactive(ctx));
|
||||
} else {
|
||||
hyperlinks.insert(name.to_string(), (id.clone(), link));
|
||||
hyperlinks.insert(name.to_string(), link);
|
||||
row.push(Btn::text_bg2(name).build_def(ctx, None));
|
||||
}
|
||||
}
|
||||
@ -588,9 +430,9 @@ fn make_tabs(
|
||||
Widget::row(row).bg(Color::WHITE)
|
||||
}
|
||||
|
||||
fn make_browser<F: Fn(usize) -> (ID, InfoTab)>(
|
||||
fn make_browser<F: Fn(usize) -> Tab>(
|
||||
ctx: &EventCtx,
|
||||
hyperlinks: &mut HashMap<String, (ID, InfoTab)>,
|
||||
hyperlinks: &mut HashMap<String, Tab>,
|
||||
noun: &str,
|
||||
total: usize,
|
||||
idx: usize,
|
||||
@ -616,3 +458,13 @@ fn make_browser<F: Fn(usize) -> (ID, InfoTab)>(
|
||||
])
|
||||
.centered()
|
||||
}
|
||||
|
||||
fn header_btns(ctx: &EventCtx) -> Widget {
|
||||
Widget::row(vec![
|
||||
Btn::svg_def("../data/system/assets/tools/location.svg")
|
||||
.build(ctx, "jump to object", hotkey(Key::J))
|
||||
.margin(5),
|
||||
Btn::text_fg("X").build(ctx, "close info", hotkey(Key::Escape)),
|
||||
])
|
||||
.align_right()
|
||||
}
|
||||
|
@ -961,6 +961,9 @@ impl Sim {
|
||||
pub fn trip_to_person(&self, id: TripID) -> PersonID {
|
||||
self.trips.trip_to_person(id)
|
||||
}
|
||||
pub fn agent_to_person(&self, id: AgentID) -> Option<PersonID> {
|
||||
self.agent_to_trip(id).map(|t| self.trip_to_person(t))
|
||||
}
|
||||
|
||||
pub fn get_person(&self, id: PersonID) -> &Person {
|
||||
self.trips.get_person(id)
|
||||
|
Loading…
Reference in New Issue
Block a user