try a huge cutover to the new info panel organization. few implemented,

going to do this in another branch
This commit is contained in:
Dustin Carlino 2020-03-27 10:37:26 -07:00
parent ccaaff82be
commit fff8c8ae9d
6 changed files with 425 additions and 580 deletions

View File

@ -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())
}
}

View File

@ -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();

View File

@ -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
}

View File

@ -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
}

View File

@ -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()
}

View File

@ -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)