beef up person schedule page. plumb markers like hyperlinks...

This commit is contained in:
Dustin Carlino 2020-03-25 16:39:33 -07:00
parent ce180f7228
commit 02a9a97bbc
5 changed files with 125 additions and 75 deletions

View File

@ -34,6 +34,7 @@ pub fn car_info(
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![];
@ -87,13 +88,22 @@ pub fn car_info(
app,
t,
app.primary.sim.progress_along_path(AgentID::Car(id)),
warpers,
);
rows.push(more);
details
});
}
InfoTab::Agent(Tab::Person(p)) => {
rows.extend(person::info(ctx, app, p, None, Vec::new(), hyperlinks));
rows.extend(person::info(
ctx,
app,
p,
None,
Vec::new(),
hyperlinks,
warpers,
));
}
_ => unreachable!(),
}
@ -118,6 +128,7 @@ pub fn ped_info(
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![];
@ -172,12 +183,21 @@ pub fn ped_info(
.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)) => {
rows.extend(person::info(ctx, app, p, None, Vec::new(), hyperlinks));
rows.extend(person::info(
ctx,
app,
p,
None,
Vec::new(),
hyperlinks,
warpers,
));
}
_ => unreachable!(),
}
@ -192,6 +212,7 @@ pub fn crowd_info(
mut tab: InfoTab,
header_btns: Widget,
hyperlinks: &mut HashMap<String, (ID, InfoTab)>,
warpers: &mut HashMap<String, ID>,
) -> Vec<Widget> {
let mut rows = vec![];
@ -230,6 +251,7 @@ pub fn crowd_info(
Widget::nothing(),
Vec::new(),
hyperlinks,
warpers,
)
.0,
);

View File

@ -25,7 +25,7 @@ impl std::cmp::PartialEq for Tab {
}
pub fn info(
ctx: &EventCtx,
ctx: &mut EventCtx,
app: &App,
id: BuildingID,
tab: InfoTab,
@ -33,6 +33,7 @@ pub fn info(
action_btns: Vec<Widget>,
batch: &mut GeomBatch,
hyperlinks: &mut HashMap<String, (ID, InfoTab)>,
warpers: &mut HashMap<String, ID>,
) -> Vec<Widget> {
let mut rows = vec![];
@ -142,6 +143,7 @@ pub fn info(
None,
Vec::new(),
hyperlinks,
warpers,
));
rows.push(Widget::col(inner).bg(colors::INNER_PANEL_BG));
}

View File

@ -33,6 +33,7 @@ pub struct InfoPanel {
actions: Vec<(Key, String)>,
hyperlinks: HashMap<String, (ID, InfoTab)>,
warpers: HashMap<String, ID>,
}
// TODO Safer to expand out ID cases here
@ -51,7 +52,6 @@ pub struct TripDetails {
id: TripID,
unzoomed: Drawable,
zoomed: Drawable,
markers: HashMap<String, ID>,
}
impl InfoPanel {
@ -141,6 +141,7 @@ impl InfoPanel {
])
.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) => (
@ -178,6 +179,7 @@ impl InfoPanel {
action_btns,
&mut batch,
&mut hyperlinks,
&mut warpers,
),
None,
),
@ -190,6 +192,7 @@ impl InfoPanel {
action_btns,
&mut batch,
&mut hyperlinks,
&mut warpers,
),
ID::Pedestrian(id) => agents::ped_info(
ctx,
@ -199,6 +202,7 @@ impl InfoPanel {
header_btns,
action_btns,
&mut hyperlinks,
&mut warpers,
),
ID::PedCrowd(members) => {
assert!(action_btns.is_empty());
@ -210,6 +214,7 @@ impl InfoPanel {
tab.clone(),
header_btns,
&mut hyperlinks,
&mut warpers,
),
None,
)
@ -230,6 +235,7 @@ impl InfoPanel {
Vec::new(),
&mut batch,
&mut hyperlinks,
&mut warpers,
),
Some(AgentID::Pedestrian(p)) => agents::ped_info(
ctx,
@ -239,10 +245,17 @@ impl InfoPanel {
header_btns,
Vec::new(),
&mut hyperlinks,
&mut warpers,
),
None => trip::inactive_info(
ctx,
app,
id,
tab.clone(),
action_btns,
&mut hyperlinks,
&mut warpers,
),
None => {
trip::inactive_info(ctx, app, id, tab.clone(), action_btns, &mut hyperlinks)
}
},
ID::Person(id) => (
person::info(
@ -252,6 +265,7 @@ impl InfoPanel {
Some(header_btns),
action_btns,
&mut hyperlinks,
&mut warpers,
),
None,
),
@ -281,6 +295,7 @@ impl InfoPanel {
.build(ctx),
also_draw: batch.upload(ctx),
hyperlinks,
warpers,
}
}
@ -404,14 +419,10 @@ impl InfoPanel {
} else if action == "follow agent" {
maybe_speed.unwrap().resume_realtime(ctx);
(false, None)
} else if let Some(_) = strip_prefix_usize(&action, "examine trip phase ") {
} else if action.starts_with("examine trip phase") {
// Don't do anything! Just using buttons for convenient tooltips.
(false, None)
} else if let Some(id) = self
.trip_details
.as_ref()
.and_then(|d| d.markers.get(&action))
{
} else if let Some(id) = self.warpers.get(&action) {
(
false,
Some(Transition::Push(Warping::new(
@ -555,16 +566,6 @@ fn agent_name(a: AgentID) -> String {
}
}
// TODO Can't easily use this in the other few cases, which use a match...
fn strip_prefix_usize(x: &String, prefix: &str) -> Option<usize> {
if x.starts_with(prefix) {
// If it starts with the prefix, insist on there being a valid number there
Some(x[prefix.len()..].parse::<usize>().unwrap())
} else {
None
}
}
fn make_tabs(
ctx: &EventCtx,
hyperlinks: &mut HashMap<String, (ID, InfoTab)>,

View File

@ -1,23 +1,26 @@
use crate::app::App;
use crate::colors;
use crate::helpers::ID;
use crate::info::trip::trip_details;
use crate::info::InfoTab;
use ezgui::{Btn, EventCtx, Line, TextExt, Widget};
use sim::{PersonID, PersonState};
use ezgui::{EventCtx, Line, TextExt, Widget};
use map_model::Map;
use sim::{Person, PersonID, PersonState, TripResult};
use std::collections::HashMap;
pub fn info(
ctx: &EventCtx,
ctx: &mut EventCtx,
app: &App,
id: PersonID,
// 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>,
) -> Vec<Widget> {
let mut rows = vec![];
// Header
let standalone = header_btns.is_some();
if let Some(btns) = header_btns {
rows.push(Widget::row(vec![
Line(format!("Person #{}", id.0)).roboto_bold().draw(ctx),
@ -26,51 +29,61 @@ pub fn info(
} else {
rows.push(Line(format!("Person #{}", id.0)).roboto_bold().draw(ctx));
}
// TODO None of these right now
rows.extend(action_btns);
let person = app.primary.sim.get_person(id);
let map = &app.primary.map;
let sim = &app.primary.sim;
let person = sim.get_person(id);
// TODO Redundant to say they're inside when the panel is embedded. But... if the person leaves
// while we have the panel open, then it IS relevant.
if standalone {
// TODO Point out where the person is now, relative to schedule...
rows.push(match person.state {
PersonState::Inside(b) => {
// TODO not the best tooltip, but disambiguous:(
hyperlinks.insert(
format!("examine Building #{}", b.0),
(ID::Building(b), InfoTab::Nil),
);
Btn::text_bg1(format!(
"Currently inside {}",
app.primary.map.get_b(b).just_address(&app.primary.map)
))
.build(ctx, format!("examine Building #{}", b.0), None)
}
PersonState::Trip(t) => format!("Currently doing Trip #{}", t.0).draw_text(ctx),
PersonState::OffMap => "Currently outside the map boundaries".draw_text(ctx),
PersonState::Limbo => "Currently in limbo -- they broke out of the Matrix! Woops. (A \
bug occurred)"
.draw_text(ctx),
});
}
rows.push(Line("Schedule").roboto_bold().draw(ctx));
// I'm sorry for bad variable names
let mut wheres_waldo = true;
for t in &person.trips {
let (start_time, _, _, _) = app.primary.sim.trip_info(*t);
hyperlinks.insert(
format!("examine Trip #{}", t.0),
(ID::Trip(*t), InfoTab::Nil),
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)).roboto_bold().draw(ctx),
trip_details(ctx, app, *t, None, warpers).0,
])
.bg(colors::SECTION_BG)
.margin(10),
);
rows.push(Widget::row(vec![
format!("{}: ", start_time.ampm_tostring()).draw_text(ctx),
Btn::text_bg1(format!("Trip #{}", t.0))
.build(ctx, format!("examine Trip #{}", t.0), None)
.margin(5),
]));
}
if wheres_waldo {
rows.push(current_status(ctx, person, map));
}
// TODO All the colorful side info
rows
}
fn current_status(ctx: &EventCtx, person: &Person, map: &Map) -> Widget {
match person.state {
PersonState::Inside(b) => {
// TODO hyperlink
format!("Currently inside {}", map.get_b(b).just_address(map)).draw_text(ctx)
}
PersonState::Trip(_) => unreachable!(),
PersonState::OffMap => "Currently outside the map boundaries".draw_text(ctx),
PersonState::Limbo => "Currently in limbo -- they broke out of the Matrix! Woops. (A bug \
occurred)"
.draw_text(ctx),
}
}

View File

@ -24,6 +24,7 @@ pub fn inactive_info(
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![];
@ -54,12 +55,20 @@ pub fn inactive_info(
InfoTab::Nil => {
rows.extend(action_btns);
let (more, trip_details) = trip_details(ctx, app, id, None);
let (more, trip_details) = trip_details(ctx, app, id, None, warpers);
rows.push(more);
details = Some(trip_details);
}
InfoTab::Trip(Tab::Person(p)) => {
rows.extend(person::info(ctx, app, p, None, Vec::new(), hyperlinks));
rows.extend(person::info(
ctx,
app,
p,
None,
Vec::new(),
hyperlinks,
warpers,
));
}
_ => unreachable!(),
}
@ -72,6 +81,7 @@ pub fn trip_details(
app: &App,
trip: TripID,
progress_along_path: Option<f64>,
warpers: &mut HashMap<String, ID>,
) -> (Widget, TripDetails) {
let map = &app.primary.map;
let phases = app.primary.sim.get_analytics().get_trip_phases(trip, map);
@ -79,15 +89,15 @@ pub fn trip_details(
let mut unzoomed = GeomBatch::new();
let mut zoomed = GeomBatch::new();
let mut markers = HashMap::new();
if phases.is_empty() {
// The trip hasn't started
let kv = vec![
("Trip start", start_time.ampm_tostring()),
("Departure", start_time.ampm_tostring()),
("Type", trip_mode.to_string()),
// TODO If we're looking at a building, then "here"...
// TODO Buttons
// TODO Should we explicitly print this for the other case, instead of the icons?
("From", endpoint(&trip_start, map).2),
("To", endpoint(&trip_end, map).2),
];
@ -97,14 +107,13 @@ pub fn trip_details(
id: trip,
unzoomed: unzoomed.upload(ctx),
zoomed: zoomed.upload(ctx),
markers,
},
);
}
let start_btn = {
let (id, center, name) = endpoint(&trip_start, map);
markers.insert("jump to start".to_string(), id);
warpers.insert(format!("jump to start of Trip #{}", trip.0), id);
unzoomed.add_svg(
ctx.prerender,
"../data/system/assets/timeline/start_pos.svg",
@ -127,14 +136,14 @@ pub fn trip_details(
RewriteColor::Change(Color::WHITE, colors::HOVERING),
)
.tooltip(txt)
.build(ctx, "jump to start", None)
.build(ctx, format!("jump to start of Trip #{}", trip.0), None)
};
let end_time = phases.last().as_ref().and_then(|p| p.end_time);
let goal_btn = {
let (id, center, name) = endpoint(&trip_end, map);
markers.insert("jump to goal".to_string(), id);
warpers.insert(format!("jump to goal of Trip #{}", trip.0), id);
unzoomed.add_svg(
ctx.prerender,
"../data/system/assets/timeline/goal_pos.svg",
@ -159,7 +168,7 @@ pub fn trip_details(
RewriteColor::Change(Color::WHITE, colors::HOVERING),
)
.tooltip(txt)
.build(ctx, "jump to goal", None)
.build(ctx, format!("jump to goal of Trip #{}", trip.0), None)
};
let total_duration_so_far =
@ -247,7 +256,11 @@ pub fn trip_details(
timeline.push(
Btn::custom(normal, hovered, rect)
.tooltip(txt)
.build(ctx, format!("examine trip phase {}", idx + 1), None)
.build(
ctx,
format!("examine trip phase {} of Trip #{}", idx + 1, trip.0),
None,
)
.centered_vert(),
);
@ -300,7 +313,6 @@ pub fn trip_details(
id: trip,
unzoomed: unzoomed.upload(ctx),
zoomed: zoomed.upload(ctx),
markers,
},
)
}