continuing with some of the harder info tabs. got everything moved over!

This commit is contained in:
Dustin Carlino 2020-03-27 11:41:56 -07:00
parent fff8c8ae9d
commit 49870c0f9f
6 changed files with 411 additions and 664 deletions

View File

@ -1,270 +0,0 @@
use crate::app::App;
use crate::colors;
use crate::helpers::ID;
use crate::info::trip::trip_details;
use crate::info::{make_browser, make_table, make_tabs, person, InfoTab, TripDetails};
use crate::render::Renderable;
use ezgui::{Color, EventCtx, GeomBatch, Line, Text, Widget};
use sim::{AgentID, CarID, PedestrianID, PersonID, VehicleType};
use std::collections::HashMap;
#[derive(Clone)]
pub enum Tab {
Person(PersonID),
// The crowd could change as we go; just freeze the list.
Crowd(Vec<PedestrianID>, usize),
}
impl std::cmp::PartialEq for Tab {
fn eq(&self, other: &Tab) -> bool {
match (self, other) {
// Only one possibility per ID
(Tab::Person(_), Tab::Person(_)) => true,
(Tab::Crowd(_, _), Tab::Crowd(_, _)) => true,
_ => false,
}
}
}
pub fn car_info(
ctx: &mut EventCtx,
app: &App,
id: CarID,
tab: InfoTab,
header_btns: Widget,
action_btns: Vec<Widget>,
batch: &mut GeomBatch,
hyperlinks: &mut HashMap<String, (ID, InfoTab)>,
warpers: &mut HashMap<String, ID>,
) -> (Vec<Widget>, Option<TripDetails>) {
let mut rows = vec![];
let label = match id.1 {
VehicleType::Car => "Car",
VehicleType::Bike => "Bike",
VehicleType::Bus => "Bus",
};
rows.push(Widget::row(vec![
Line(format!("{} #{}", label, id.0))
.small_heading()
.draw(ctx),
header_btns,
]));
rows.push(make_tabs(ctx, hyperlinks, ID::Car(id), tab.clone(), {
let mut tabs = vec![("Info", InfoTab::Nil)];
if let Some(p) = app
.primary
.sim
.agent_to_trip(AgentID::Car(id))
.map(|t| app.primary.sim.trip_to_person(t))
{
tabs.push(("Trips", InfoTab::Agent(Tab::Person(p))));
}
tabs
}));
let mut details: Option<TripDetails> = None;
match tab {
InfoTab::Nil => {
rows.extend(action_btns);
let (kv, extra) = app.primary.sim.car_properties(id, &app.primary.map);
rows.extend(make_table(ctx, kv));
if !extra.is_empty() {
let mut txt = Text::from(Line(""));
for line in extra {
txt.add(Line(line));
}
rows.push(txt.draw(ctx));
}
let trip = if id.1 == VehicleType::Bus {
None
} else {
app.primary.sim.agent_to_trip(AgentID::Car(id))
};
details = trip.map(|t| {
let (more, details) = trip_details(
ctx,
app,
t,
app.primary.sim.progress_along_path(AgentID::Car(id)),
warpers,
);
rows.push(more);
details
});
}
InfoTab::Agent(Tab::Person(p)) => {
// TODO Reorganize this
rows.extend(person::info(
ctx,
app,
p,
InfoTab::Nil,
None,
Vec::new(),
hyperlinks,
warpers,
));
}
_ => unreachable!(),
}
if let Some(b) = app.primary.sim.get_owner_of_car(id) {
// TODO Mention this, with a warp tool
batch.push(
app.cs
.get_def("something associated with something else", Color::PURPLE),
app.primary.draw_map.get_b(b).get_outline(&app.primary.map),
);
}
(rows, details)
}
pub fn ped_info(
ctx: &mut EventCtx,
app: &App,
id: PedestrianID,
tab: InfoTab,
header_btns: Widget,
action_btns: Vec<Widget>,
hyperlinks: &mut HashMap<String, (ID, InfoTab)>,
warpers: &mut HashMap<String, ID>,
) -> (Vec<Widget>, Option<TripDetails>) {
let mut rows = vec![];
rows.push(Widget::row(vec![
Line(format!("Pedestrian #{}", id.0))
.small_heading()
.draw(ctx),
header_btns,
]));
let trip = app
.primary
.sim
.agent_to_trip(AgentID::Pedestrian(id))
.unwrap();
rows.push(make_tabs(
ctx,
hyperlinks,
ID::Pedestrian(id),
tab.clone(),
vec![
("Info", InfoTab::Nil),
(
"Trips",
InfoTab::Agent(Tab::Person(app.primary.sim.trip_to_person(trip))),
),
],
));
let mut details: Option<TripDetails> = None;
match tab {
InfoTab::Nil => {
rows.extend(action_btns);
let (kv, extra) = app.primary.sim.ped_properties(id, &app.primary.map);
rows.extend(make_table(ctx, kv));
if !extra.is_empty() {
let mut txt = Text::from(Line(""));
for line in extra {
txt.add(Line(line));
}
rows.push(txt.draw(ctx));
}
let (more, trip_details) = trip_details(
ctx,
app,
app.primary
.sim
.agent_to_trip(AgentID::Pedestrian(id))
.unwrap(),
app.primary.sim.progress_along_path(AgentID::Pedestrian(id)),
warpers,
);
rows.push(more);
details = Some(trip_details);
}
InfoTab::Agent(Tab::Person(p)) => {
// TODO Reorganize this
rows.extend(person::info(
ctx,
app,
p,
InfoTab::Nil,
None,
Vec::new(),
hyperlinks,
warpers,
));
}
_ => unreachable!(),
}
(rows, details)
}
pub fn crowd_info(
ctx: &mut EventCtx,
app: &App,
members: Vec<PedestrianID>,
mut tab: InfoTab,
header_btns: Widget,
hyperlinks: &mut HashMap<String, (ID, InfoTab)>,
warpers: &mut HashMap<String, ID>,
) -> Vec<Widget> {
let mut rows = vec![];
rows.push(Widget::row(vec![
Line("Pedestrian crowd").small_heading().draw(ctx),
header_btns,
]));
if tab == InfoTab::Nil {
tab = InfoTab::Agent(Tab::Crowd(members, 0));
}
match tab {
InfoTab::Agent(Tab::Crowd(peds, idx)) => {
let mut inner = vec![make_browser(
ctx,
hyperlinks,
"Pedestrian",
peds.len(),
idx,
|n| {
(
ID::PedCrowd(peds.clone()),
InfoTab::Agent(Tab::Crowd(peds.clone(), n)),
)
},
)];
// If we click a tab for a pedestrian, we lose the crowd. Woops?
inner.extend(
ped_info(
ctx,
app,
peds[idx],
InfoTab::Nil,
// No header buttons
Widget::nothing(),
Vec::new(),
hyperlinks,
warpers,
)
.0,
);
rows.push(Widget::col(inner).bg(colors::INNER_PANEL_BG));
}
_ => unreachable!(),
}
rows
}

View File

@ -1,143 +1,182 @@
use crate::app::App;
use crate::helpers::ID;
use crate::info::{make_table, make_tabs, person, InfoTab};
use ezgui::{EventCtx, GeomBatch, Line, Text, TextExt, Widget};
use crate::info::{header_btns, make_table, make_tabs, Details, Tab};
use ezgui::{Btn, EventCtx, Line, Text, TextExt, Widget};
use geom::Time;
use map_model::BuildingID;
use sim::TripEndpoint;
use std::collections::HashMap;
use sim::{TripEndpoint, TripMode, TripResult};
#[derive(Clone, PartialEq)]
pub enum Tab {
// If we're live updating, the people inside could change! Re-calculate constantly.
People,
Debug,
pub fn info(ctx: &mut EventCtx, app: &App, details: &mut Details, id: BuildingID) -> Vec<Widget> {
let mut rows = header(ctx, app, details, id, Tab::BldgInfo(id));
let b = app.primary.map.get_b(id);
let mut kv = Vec::new();
kv.push(("Address", b.just_address(&app.primary.map)));
if let Some(name) = b.just_name() {
kv.push(("Name", name.to_string()));
}
if let Some(ref p) = b.parking {
kv.push(("Parking", format!("{} spots via {}", p.num_stalls, p.name)));
} else {
kv.push(("Parking", "None".to_string()));
}
rows.extend(make_table(ctx, kv));
let mut txt = Text::new();
if !b.amenities.is_empty() {
txt.add(Line(""));
if b.amenities.len() > 1 {
txt.add(Line(format!("{} amenities:", b.amenities.len())));
}
for (name, amenity) in &b.amenities {
txt.add(Line(format!("- {} (a {})", name, amenity)));
}
}
// TODO Rethink this
let trip_lines = app
.primary
.sim
.count_trips(TripEndpoint::Bldg(id))
.describe();
if !trip_lines.is_empty() {
txt.add(Line(""));
for line in trip_lines {
txt.add(Line(line));
}
}
let cars = app.primary.sim.get_parked_cars_by_owner(id);
if !cars.is_empty() {
txt.add(Line(""));
txt.add(Line(format!(
"{} parked cars owned by this building",
cars.len()
)));
// TODO Jump to it or see status
for p in cars {
txt.add(Line(format!("- {}", p.vehicle.id)));
}
}
if !txt.is_empty() {
rows.push(txt.draw(ctx))
}
rows
}
pub fn info(
ctx: &mut EventCtx,
pub fn debug(ctx: &mut EventCtx, app: &App, details: &mut Details, id: BuildingID) -> Vec<Widget> {
let mut rows = header(ctx, app, details, id, Tab::BldgDebug(id));
let b = app.primary.map.get_b(id);
rows.extend(make_table(
ctx,
vec![(
"Dist along sidewalk",
b.front_path.sidewalk.dist_along().to_string(),
)],
));
rows.push("Raw OpenStreetMap data".draw_text(ctx));
rows.extend(make_table(ctx, b.osm_tags.clone().into_iter().collect()));
rows
}
pub fn people(ctx: &mut EventCtx, app: &App, details: &mut Details, id: BuildingID) -> Vec<Widget> {
let mut rows = header(ctx, app, details, id, Tab::BldgPeople(id));
// TODO Sort/group better
// Show minimal info: ID, next departure time, type of that trip
for p in app.primary.sim.bldg_to_people(id) {
let person = app.primary.sim.get_person(p);
let mut next_trip: Option<(Time, TripMode)> = None;
for t in &person.trips {
match app.primary.sim.trip_to_agent(*t) {
TripResult::TripNotStarted => {
let (start_time, _, _, mode) = app.primary.sim.trip_info(*t);
next_trip = Some((start_time, mode));
break;
}
TripResult::Ok(_) | TripResult::ModeChange => {
// TODO What to do here? This is meant for building callers right now
break;
}
TripResult::TripDone => {}
TripResult::TripDoesntExist => unreachable!(),
}
}
let label = format!("Person #{}", p.0);
details
.hyperlinks
.insert(label.clone(), Tab::PersonStatus(p));
rows.push(Widget::col(vec![
Btn::text_bg1(label).build_def(ctx, None),
if let Some((t, mode)) = next_trip {
format!("Leaving in {} to {}", t - app.primary.sim.time(), mode).draw_text(ctx)
} else {
"Staying inside".draw_text(ctx)
},
]));
}
rows
}
fn header(
ctx: &EventCtx,
app: &App,
details: &mut Details,
id: BuildingID,
tab: InfoTab,
header_btns: Widget,
action_btns: Vec<Widget>,
batch: &mut GeomBatch,
hyperlinks: &mut HashMap<String, (ID, InfoTab)>,
tab: Tab,
) -> Vec<Widget> {
let mut rows = vec![];
let b = app.primary.map.get_b(id);
let ppl = app.primary.sim.bldg_to_people(id);
rows.push(Widget::row(vec![
Line(format!("Building #{}", id.0))
.small_heading()
.draw(ctx),
header_btns,
header_btns(ctx),
]));
rows.push(make_tabs(ctx, hyperlinks, ID::Building(id), tab.clone(), {
let mut tabs = vec![("Info", InfoTab::Nil), ("Debug", InfoTab::Bldg(Tab::Debug))];
if !ppl.is_empty() {
tabs.push(("People", InfoTab::Bldg(Tab::People)));
}
tabs
}));
match tab {
InfoTab::Nil => {
rows.extend(action_btns);
let mut kv = Vec::new();
kv.push(("Address", b.just_address(&app.primary.map)));
if let Some(name) = b.just_name() {
kv.push(("Name", name.to_string()));
}
if let Some(ref p) = b.parking {
kv.push(("Parking", format!("{} spots via {}", p.num_stalls, p.name)));
} else {
kv.push(("Parking", "None".to_string()));
}
rows.extend(make_table(ctx, kv));
let mut txt = Text::new();
if !b.amenities.is_empty() {
txt.add(Line(""));
if b.amenities.len() > 1 {
txt.add(Line(format!("{} amenities:", b.amenities.len())));
}
for (name, amenity) in &b.amenities {
txt.add(Line(format!("- {} (a {})", name, amenity)));
}
}
// TODO Rethink this
let trip_lines = app
.primary
.sim
.count_trips(TripEndpoint::Bldg(id))
.describe();
if !trip_lines.is_empty() {
txt.add(Line(""));
for line in trip_lines {
txt.add(Line(line));
}
}
let cars = app.primary.sim.get_parked_cars_by_owner(id);
if !cars.is_empty() {
txt.add(Line(""));
txt.add(Line(format!(
"{} parked cars owned by this building",
cars.len()
)));
// TODO Jump to it or see status
for p in cars {
txt.add(Line(format!("- {}", p.vehicle.id)));
}
}
if !txt.is_empty() {
rows.push(txt.draw(ctx))
}
}
InfoTab::Bldg(Tab::Debug) => {
rows.extend(make_table(
ctx,
vec![(
"Dist along sidewalk",
b.front_path.sidewalk.dist_along().to_string(),
)],
));
rows.push("Raw OpenStreetMap data".draw_text(ctx));
rows.extend(make_table(ctx, b.osm_tags.clone().into_iter().collect()));
}
InfoTab::Bldg(Tab::People) => {
// TODO Sort/group better
// Show minimal info: ID, next departure time, type of that trip
for person in ppl {
rows.push(person::summary(ctx, app, person, hyperlinks));
}
}
_ => unreachable!(),
}
rows.push(make_tabs(
ctx,
&mut details.hyperlinks,
tab,
vec![
("Info", Tab::BldgInfo(id)),
("Debug", Tab::BldgDebug(id)),
("People", Tab::BldgPeople(id)),
],
));
// TODO On every tab?
for p in app.primary.sim.get_parked_cars_by_owner(id) {
batch.push(
let shape = app
.primary
.draw_map
.get_obj(
ID::Car(p.vehicle.id),
app,
&mut app.primary.draw_map.agents.borrow_mut(),
ctx.prerender,
)
.unwrap()
.get_outline(&app.primary.map);
details.unzoomed.push(
app.cs.get("something associated with something else"),
app.primary
.draw_map
.get_obj(
ID::Car(p.vehicle.id),
app,
&mut app.primary.draw_map.agents.borrow_mut(),
ctx.prerender,
)
.unwrap()
.get_outline(&app.primary.map),
shape.clone(),
);
details.zoomed.push(
app.cs.get("something associated with something else"),
shape,
);
}

View File

@ -1,25 +1,20 @@
use crate::app::App;
use crate::info::{header_btns, make_table, Details};
use ezgui::{EventCtx, Line, Text, Widget};
use geom::Time;
use map_model::BusStopID;
use sim::CarID;
pub fn info(
ctx: &EventCtx,
app: &App,
id: BusStopID,
header_btns: Widget,
action_btns: Vec<Widget>,
) -> Vec<Widget> {
// TODO Needs much more work
pub fn stop(ctx: &EventCtx, app: &App, details: &mut Details, id: BusStopID) -> Vec<Widget> {
let mut rows = vec![];
let sim = &app.primary.sim;
rows.push(Widget::row(vec![
Line("Bus stop").small_heading().draw(ctx),
header_btns,
header_btns(ctx),
]));
rows.extend(action_btns);
let mut txt = Text::new();
txt.add(Line(format!(
@ -53,3 +48,25 @@ pub fn info(
rows
}
// TODO Likewise
pub fn bus(ctx: &EventCtx, app: &App, details: &mut Details, id: CarID) -> Vec<Widget> {
let mut rows = vec![];
rows.push(Widget::row(vec![
Line(format!("Bus #{}", id.0)).small_heading().draw(ctx),
header_btns(ctx),
]));
let (kv, extra) = app.primary.sim.car_properties(id, &app.primary.map);
rows.extend(make_table(ctx, kv));
if !extra.is_empty() {
let mut txt = Text::from(Line(""));
for line in extra {
txt.add(Line(line));
}
rows.push(txt.draw(ctx));
}
rows
}

View File

@ -1,9 +1,10 @@
//mod building;
//mod bus;
mod building;
mod bus;
mod debug;
mod intersection;
mod lane;
//mod person;
mod person;
mod trip;
use crate::app::App;
use crate::colors;
@ -200,20 +201,16 @@ impl InfoPanel {
}*/
let col = match tab {
/*Tab::PersonStatus(p) => person::status(ctx, app, &mut details, p),
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::Crowd(ref 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),
@ -222,7 +219,6 @@ impl InfoPanel {
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"),
};
/*
@ -430,35 +426,6 @@ fn make_tabs(
Widget::row(row).bg(Color::WHITE)
}
fn make_browser<F: Fn(usize) -> Tab>(
ctx: &EventCtx,
hyperlinks: &mut HashMap<String, Tab>,
noun: &str,
total: usize,
idx: usize,
make_link: F,
) -> Widget {
// TODO Keys are weird! But left/right for speed
Widget::row(vec![
if idx != 0 {
hyperlinks.insert("previous".to_string(), make_link(idx - 1));
Btn::text_fg("<").build(ctx, "previous", hotkey(Key::UpArrow))
} else {
Btn::text_fg("<").inactive(ctx)
}
.margin(5),
format!("{} {}/{}", noun, idx + 1, total).draw_text(ctx),
if idx != total - 1 {
hyperlinks.insert("next".to_string(), make_link(idx + 1));
Btn::text_fg(">").build(ctx, "next", hotkey(Key::DownArrow))
} else {
Btn::text_fg(">").inactive(ctx)
}
.margin(5),
])
.centered()
}
fn header_btns(ctx: &EventCtx) -> Widget {
Widget::row(vec![
Btn::svg_def("../data/system/assets/tools/location.svg")

View File

@ -1,113 +1,223 @@
use crate::app::App;
use crate::colors;
use crate::helpers::ID;
use crate::info::trip::trip_details;
use crate::info::{make_table, make_tabs, InfoTab};
use ezgui::{Btn, EventCtx, Line, TextExt, Widget};
use geom::Time;
use crate::info::{header_btns, make_table, make_tabs, Details, Tab, Text};
use crate::render::Renderable;
use ezgui::{Btn, Color, EventCtx, Line, TextExt, Widget};
use map_model::Map;
use sim::{Person, PersonID, PersonState, TripMode, TripResult};
use std::collections::HashMap;
use sim::{AgentID, CarID, PedestrianID, Person, PersonID, PersonState, TripResult};
#[derive(Clone, PartialEq)]
pub enum Tab {
Bio,
pub fn status(ctx: &mut EventCtx, app: &App, details: &mut Details, id: PersonID) -> Vec<Widget> {
let mut rows = header(ctx, app, details, id, Tab::PersonStatus(id));
let map = &app.primary.map;
let sim = &app.primary.sim;
let person = sim.get_person(id);
match sim.get_person(id).state {
PersonState::Inside(b) => {
// TODO hyperlink
rows.push(
format!("Currently inside {}", map.get_b(b).just_address(map)).draw_text(ctx),
);
}
PersonState::OffMap => {
rows.push("Currently outside the map boundaries".draw_text(ctx));
}
PersonState::Limbo => {
rows.push(
"Currently in limbo -- they broke out of the Matrix! Woops. (A bug occurred)"
.draw_text(ctx),
);
}
PersonState::Trip(t) => {
if let Some(a) = sim.trip_to_agent(t).ok() {
rows.push(Widget::col(vec![
Line(format!("Trip #{}", t.0)).small_heading().draw(ctx),
trip_details(ctx, app, t, sim.progress_along_path(a), details),
]));
let (kv, extra) = match a {
AgentID::Car(c) => sim.car_properties(c, map),
AgentID::Pedestrian(p) => sim.ped_properties(p, map),
};
rows.extend(make_table(ctx, kv));
if !extra.is_empty() {
let mut txt = Text::from(Line(""));
for line in extra {
txt.add(Line(line));
}
rows.push(txt.draw(ctx));
}
if let AgentID::Car(c) = a {
if let Some(b) = app.primary.sim.get_owner_of_car(c) {
// TODO Mention this, with a warp tool
details.unzoomed.push(
app.cs
.get_def("something associated with something else", Color::PURPLE),
app.primary.draw_map.get_b(b).get_outline(&app.primary.map),
);
details.zoomed.push(
app.cs.get("something associated with something else"),
app.primary.draw_map.get_b(b).get_outline(&app.primary.map),
);
}
}
} else {
// TODO Temporary mode change, what's going on?
rows.push(Widget::col(vec![
Line(format!("Trip #{}", t.0)).small_heading().draw(ctx),
trip_details(ctx, app, t, None, details),
]));
}
}
}
rows
}
pub fn info(
ctx: &mut EventCtx,
pub fn trips(ctx: &mut EventCtx, app: &App, details: &mut Details, id: PersonID) -> Vec<Widget> {
let mut rows = header(ctx, app, details, id, Tab::PersonTrips(id));
let map = &app.primary.map;
let sim = &app.primary.sim;
let person = sim.get_person(id);
// I'm sorry for bad variable names
let mut wheres_waldo = true;
// TODO Classify trips as not started, ongoing, done. Don't mention current status as much?
for t in &person.trips {
match sim.trip_to_agent(*t) {
TripResult::TripNotStarted => {
if wheres_waldo {
wheres_waldo = false;
rows.push(current_status(ctx, person, map));
}
}
TripResult::Ok(_) | TripResult::ModeChange => {
// ongoing
assert!(wheres_waldo);
wheres_waldo = false;
}
TripResult::TripDone => {
assert!(wheres_waldo);
}
TripResult::TripDoesntExist => unreachable!(),
}
rows.push(
Widget::col(vec![
Line(format!("Trip #{}", t.0)).small_heading().draw(ctx),
trip_details(ctx, app, *t, None, details),
])
.bg(colors::SECTION_BG)
.margin(10),
);
}
if wheres_waldo {
rows.push(current_status(ctx, person, map));
}
rows
}
pub fn bio(ctx: &EventCtx, app: &App, details: &mut Details, id: PersonID) -> Vec<Widget> {
let mut rows = header(ctx, app, details, id, Tab::PersonBio(id));
// TODO A little picture
rows.extend(make_table(
ctx,
vec![
("Name", "Somebody".to_string()),
("Age", "42".to_string()),
("Occupation", "classified".to_string()),
],
));
// TODO Mad libs!
// - Keeps a collection of ___ at all times
// - Origin story: accidentally fell into a vat of cheese curds
// - Superpower: Makes unnervingly realistic squirrel noises
// - Rides a fixie
// - Has 17 pinky toe piercings (surprising, considering they're the state champ at
// barefoot marathons)
rows
}
pub fn crowd(
ctx: &EventCtx,
app: &App,
id: PersonID,
tab: InfoTab,
// If None, then the panel is embedded
header_btns: Option<Widget>,
action_btns: Vec<Widget>,
hyperlinks: &mut HashMap<String, (ID, InfoTab)>,
warpers: &mut HashMap<String, ID>,
details: &mut Details,
members: &Vec<PedestrianID>,
) -> Vec<Widget> {
let mut rows = vec![];
// Header
if let Some(btns) = header_btns {
rows.push(Widget::row(vec![
Line("Pedestrian crowd").small_heading().draw(ctx),
header_btns(ctx),
]));
for (idx, id) in members.into_iter().enumerate() {
let person = app
.primary
.sim
.agent_to_person(AgentID::Pedestrian(*id))
.unwrap();
// TODO What other info is useful to summarize?
rows.push(Widget::row(vec![
Line(format!("Person #{}", id.0)).small_heading().draw(ctx),
btns,
format!("{})", idx + 1).draw_text(ctx),
Btn::text_fg(format!("Person #{}", person.0)).build_def(ctx, None),
]));
} else {
rows.push(Line(format!("Person #{}", id.0)).small_heading().draw(ctx));
details
.hyperlinks
.insert(format!("Person #{}", person.0), Tab::PersonStatus(person));
}
rows
}
pub fn parked_car(ctx: &EventCtx, app: &App, details: &mut Details, id: CarID) -> Vec<Widget> {
let mut rows = vec![];
rows.push(Widget::row(vec![
Line(format!("Parked car #{}", id.0))
.small_heading()
.draw(ctx),
header_btns(ctx),
]));
let (kv, extra) = app.primary.sim.car_properties(id, &app.primary.map);
rows.extend(make_table(ctx, kv));
if !extra.is_empty() {
let mut txt = Text::from(Line(""));
for line in extra {
txt.add(Line(line));
}
rows.push(txt.draw(ctx));
}
rows
}
fn header(ctx: &EventCtx, app: &App, details: &mut Details, id: PersonID, tab: Tab) -> Vec<Widget> {
let mut rows = vec![];
rows.push(Widget::row(vec![
Line(format!("Person #{}", id.0)).small_heading().draw(ctx),
header_btns(ctx),
]));
rows.push(make_tabs(
ctx,
hyperlinks,
ID::Person(id),
tab.clone(),
vec![("Trips", InfoTab::Nil), ("Bio", InfoTab::Person(Tab::Bio))],
&mut details.hyperlinks,
tab,
vec![
("Status", Tab::PersonStatus(id)),
("Trips", Tab::PersonTrips(id)),
("Bio", Tab::PersonBio(id)),
],
));
match tab {
InfoTab::Nil => {
// TODO None of these right now
rows.extend(action_btns);
let map = &app.primary.map;
let sim = &app.primary.sim;
let person = sim.get_person(id);
// I'm sorry for bad variable names
let mut wheres_waldo = true;
for t in &person.trips {
match sim.trip_to_agent(*t) {
TripResult::TripNotStarted => {
if wheres_waldo {
wheres_waldo = false;
rows.push(current_status(ctx, person, map));
}
}
TripResult::Ok(_) | TripResult::ModeChange => {
// ongoing
assert!(wheres_waldo);
wheres_waldo = false;
}
TripResult::TripDone => {
assert!(wheres_waldo);
}
TripResult::TripDoesntExist => unreachable!(),
}
rows.push(
Widget::col(vec![
Line(format!("Trip #{}", t.0)).small_heading().draw(ctx),
trip_details(ctx, app, *t, None, warpers).0,
])
.bg(colors::SECTION_BG)
.margin(10),
);
}
if wheres_waldo {
rows.push(current_status(ctx, person, map));
}
}
InfoTab::Person(Tab::Bio) => {
// TODO A little picture
rows.extend(make_table(
ctx,
vec![
("Name", "Somebody".to_string()),
("Age", "42".to_string()),
("Occupation", "classified".to_string()),
],
));
// TODO Mad libs!
// - Keeps a collection of ___ at all times
// - Origin story: accidentally fell into a vat of cheese curds
// - Superpower: Makes unnervingly realistic squirrel noises
// - Rides a fixie
// - Has 17 pinky toe piercings (surprising, considering they're the state champ at
// barefoot marathons)
}
_ => unreachable!(),
}
rows
}
@ -124,40 +234,3 @@ fn current_status(ctx: &EventCtx, person: &Person, map: &Map) -> Widget {
.draw_text(ctx),
}
}
pub fn summary(
ctx: &EventCtx,
app: &App,
id: PersonID,
hyperlinks: &mut HashMap<String, (ID, InfoTab)>,
) -> Widget {
let person = app.primary.sim.get_person(id);
let mut next_trip: Option<(Time, TripMode)> = None;
for t in &person.trips {
match app.primary.sim.trip_to_agent(*t) {
TripResult::TripNotStarted => {
let (start_time, _, _, mode) = app.primary.sim.trip_info(*t);
next_trip = Some((start_time, mode));
break;
}
TripResult::Ok(_) | TripResult::ModeChange => {
// TODO What to do here? This is meant for building callers right now
break;
}
TripResult::TripDone => {}
TripResult::TripDoesntExist => unreachable!(),
}
}
let label = format!("Person #{}", id.0);
hyperlinks.insert(label.clone(), (ID::Person(id), InfoTab::Nil));
Widget::col(vec![
Btn::text_bg1(label).build_def(ctx, None),
if let Some((t, mode)) = next_trip {
format!("Leaving in {} to {}", t - app.primary.sim.time(), mode).draw_text(ctx)
} else {
"Staying inside".draw_text(ctx)
},
])
}

View File

@ -1,97 +1,26 @@
use crate::app::App;
use crate::colors;
use crate::helpers::ID;
use crate::info::{make_table, make_tabs, person, InfoTab, TripDetails};
use crate::info::{make_table, Details};
use crate::render::dashed_lines;
use ezgui::{
hotkey, Btn, Color, EventCtx, GeomBatch, Key, Line, Plot, PlotOptions, RewriteColor, Series,
Text, Widget,
Btn, Color, EventCtx, GeomBatch, Line, Plot, PlotOptions, RewriteColor, Series, Text, Widget,
};
use geom::{Angle, Distance, Duration, Polygon, Pt2D, Time};
use map_model::{Map, Path, PathStep};
use sim::{PersonID, TripEndpoint, TripID, TripPhaseType};
use std::collections::HashMap;
#[derive(Clone, PartialEq)]
pub enum Tab {
Person(PersonID),
}
pub fn inactive_info(
ctx: &mut EventCtx,
app: &App,
id: TripID,
tab: InfoTab,
action_btns: Vec<Widget>,
hyperlinks: &mut HashMap<String, (ID, InfoTab)>,
warpers: &mut HashMap<String, ID>,
) -> (Vec<Widget>, Option<TripDetails>) {
let mut rows = vec![];
rows.push(Widget::row(vec![
Line(format!("Trip #{}", id.0)).small_heading().draw(ctx),
Btn::text_fg("X")
.build(ctx, "close info", hotkey(Key::Escape))
.align_right(),
]));
rows.push(make_tabs(
ctx,
hyperlinks,
ID::Trip(id),
tab.clone(),
vec![
("Info", InfoTab::Nil),
(
"Trips",
InfoTab::Trip(Tab::Person(app.primary.sim.trip_to_person(id))),
),
],
));
let mut details: Option<TripDetails> = None;
match tab {
InfoTab::Nil => {
rows.extend(action_btns);
let (more, trip_details) = trip_details(ctx, app, id, None, warpers);
rows.push(more);
details = Some(trip_details);
}
InfoTab::Trip(Tab::Person(p)) => {
// TODO Hyperlink?
rows.extend(person::info(
ctx,
app,
p,
InfoTab::Nil,
None,
Vec::new(),
hyperlinks,
warpers,
));
}
_ => unreachable!(),
}
(rows, details)
}
use sim::{TripEndpoint, TripID, TripPhaseType};
pub fn trip_details(
ctx: &mut EventCtx,
app: &App,
trip: TripID,
progress_along_path: Option<f64>,
warpers: &mut HashMap<String, ID>,
) -> (Widget, TripDetails) {
details: &mut Details,
) -> Widget {
let map = &app.primary.map;
let phases = app.primary.sim.get_analytics().get_trip_phases(trip, map);
let (start_time, trip_start, trip_end, trip_mode) = app.primary.sim.trip_info(trip);
let mut unzoomed = GeomBatch::new();
let mut zoomed = GeomBatch::new();
if phases.is_empty() {
// The trip hasn't started
let kv = vec![
@ -103,27 +32,22 @@ pub fn trip_details(
("From", endpoint(&trip_start, map).2),
("To", endpoint(&trip_end, map).2),
];
return (
Widget::col(make_table(ctx, kv)),
TripDetails {
id: trip,
unzoomed: unzoomed.upload(ctx),
zoomed: zoomed.upload(ctx),
},
);
return Widget::col(make_table(ctx, kv));
}
let start_btn = {
let (id, center, name) = endpoint(&trip_start, map);
warpers.insert(format!("jump to start of Trip #{}", trip.0), id);
unzoomed.add_svg(
details
.warpers
.insert(format!("jump to start of Trip #{}", trip.0), id);
details.unzoomed.add_svg(
ctx.prerender,
"../data/system/assets/timeline/start_pos.svg",
center,
1.0,
Angle::ZERO,
);
zoomed.add_svg(
details.zoomed.add_svg(
ctx.prerender,
"../data/system/assets/timeline/start_pos.svg",
center,
@ -145,15 +69,17 @@ pub fn trip_details(
let goal_btn = {
let (id, center, name) = endpoint(&trip_end, map);
warpers.insert(format!("jump to goal of Trip #{}", trip.0), id);
unzoomed.add_svg(
details
.warpers
.insert(format!("jump to goal of Trip #{}", trip.0), id);
details.unzoomed.add_svg(
ctx.prerender,
"../data/system/assets/timeline/goal_pos.svg",
center,
1.0,
Angle::ZERO,
);
zoomed.add_svg(
details.zoomed.add_svg(
ctx.prerender,
"../data/system/assets/timeline/goal_pos.svg",
center,
@ -281,8 +207,10 @@ pub fn trip_details(
}
if let Some(trace) = path.trace(map, dist, None) {
unzoomed.push(color, trace.make_polygons(Distance::meters(10.0)));
zoomed.extend(
details
.unzoomed
.push(color, trace.make_polygons(Distance::meters(10.0)));
details.zoomed.extend(
color,
dashed_lines(
&trace,
@ -309,14 +237,7 @@ pub fn trip_details(
col.extend(make_table(ctx, kv));
col.extend(elevation);
(
Widget::col(col),
TripDetails {
id: trip,
unzoomed: unzoomed.upload(ctx),
zoomed: zoomed.upload(ctx),
},
)
Widget::col(col)
}
fn make_elevation(ctx: &EventCtx, color: Color, walking: bool, path: &Path, map: &Map) -> Widget {