Click trip problems to time-warp. Useful for debugging spurious diffs in greater detail

This commit is contained in:
Dustin Carlino 2022-05-03 16:08:22 +01:00
parent 0fd39db440
commit e66112b3ea
3 changed files with 79 additions and 45 deletions

View File

@ -794,8 +794,11 @@ impl PerObjectActions {
} }
pub fn left_click<S: Into<String>>(&mut self, ctx: &mut EventCtx, label: S) -> bool { pub fn left_click<S: Into<String>>(&mut self, ctx: &mut EventCtx, label: S) -> bool {
assert!(self.click_action.is_none()); let label = label.into();
self.click_action = Some(label.into()); if let Some(ref old) = self.click_action {
panic!("left_click for \"{old}\" already called; can't also do \"{label}\"");
}
self.click_action = Some(label);
ctx.normal_left_click() ctx.normal_left_click()
} }
} }

View File

@ -41,7 +41,7 @@ pub struct InfoPanel {
panel: Panel, panel: Panel,
draw_extra: ToggleZoomed, draw_extra: ToggleZoomed,
tooltips: Vec<(Polygon, Text)>, tooltips: Vec<(Polygon, Text, (TripID, Time))>,
hyperlinks: HashMap<String, Tab>, hyperlinks: HashMap<String, Tab>,
warpers: HashMap<String, ID>, warpers: HashMap<String, ID>,
@ -290,8 +290,9 @@ impl Tab {
pub struct Details { pub struct Details {
/// Draw extra things when unzoomed or zoomed. /// Draw extra things when unzoomed or zoomed.
pub draw_extra: ToggleZoomedBuilder, pub draw_extra: ToggleZoomedBuilder,
/// Show these tooltips over the map. /// Show these tooltips over the map. If the tooltip is clicked, time-warp and open the info
pub tooltips: Vec<(Polygon, Text)>, /// panel.
pub tooltips: Vec<(Polygon, Text, (TripID, Time))>,
/// When a button with this label is clicked, open this info panel tab instead. /// When a button with this label is clicked, open this info panel tab instead.
pub hyperlinks: HashMap<String, Tab>, pub hyperlinks: HashMap<String, Tab>,
/// When a button with this label is clicked, warp to this ID. /// When a button with this label is clicked, warp to this ID.
@ -476,13 +477,30 @@ impl InfoPanel {
app: &mut App, app: &mut App,
ctx_actions: &mut dyn ContextualActions, ctx_actions: &mut dyn ContextualActions,
) -> (bool, Option<Transition>) { ) -> (bool, Option<Transition>) {
// Can click on the map to cancel // Let the user click on the map to cancel out this info panel, or click on a tooltip to
if ctx.canvas.get_cursor_in_map_space().is_some() // time warp.
&& app.primary.current_selection.is_none() if let Some(pt) = ctx.canvas.get_cursor_in_map_space() {
&& app.per_obj.left_click(ctx, "stop showing info") // TODO This'll fire left_click elsewhere and conflict; we can't override here
if app.primary.current_selection.is_none() {
let mut found_tooltip = false;
if let Some((_, _, (trip, time))) = self
.tooltips
.iter()
.find(|(poly, _, _)| poly.contains_pt(pt))
{ {
found_tooltip = true;
if app
.per_obj
.left_click(ctx, &format!("warp here at {}", time))
{
return do_time_warp(ctx_actions, app, *trip, *time);
}
}
if !found_tooltip && app.per_obj.left_click(ctx, "stop showing info") {
return (true, None); return (true, None);
} }
}
}
// Live update? // Live update?
if app.primary.sim.time() != self.time || ctx_actions.is_paused() != self.is_paused { if app.primary.sim.time() != self.time || ctx_actions.is_paused() != self.is_paused {
@ -538,38 +556,7 @@ impl InfoPanel {
))), ))),
) )
} else if let Some((trip, time)) = self.time_warpers.get(&action) { } else if let Some((trip, time)) = self.time_warpers.get(&action) {
let trip = *trip; do_time_warp(ctx_actions, app, *trip, *time)
let time = *time;
let person = app.primary.sim.trip_to_person(trip).unwrap();
// When executed, this assumes the SandboxMode is the top of the stack. It'll
// reopen the info panel, then launch the jump-to-time UI.
let jump_to_time =
Transition::ConsumeState(Box::new(move |state, ctx, app| {
let mut sandbox = state.downcast::<SandboxMode>().ok().unwrap();
let mut actions = sandbox.contextual_actions();
sandbox.controls.common.as_mut().unwrap().launch_info_panel(
ctx,
app,
Tab::PersonTrips(person, OpenTrip::single(trip)),
&mut actions,
);
vec![sandbox, TimeWarpScreen::new_state(ctx, app, time, None)]
}));
if time >= app.primary.sim.time() {
return (false, Some(jump_to_time));
}
// We need to first rewind the simulation
let rewind_sim = Transition::Replace(SandboxMode::async_new(
app,
ctx_actions.gameplay_mode(),
Box::new(move |_, _| vec![jump_to_time]),
));
(false, Some(rewind_sim))
} else if let Some(url) = action.strip_prefix("open ") { } else if let Some(url) = action.strip_prefix("open ") {
open_browser(url); open_browser(url);
(false, None) (false, None)
@ -638,7 +625,7 @@ impl InfoPanel {
self.panel.draw(g); self.panel.draw(g);
self.draw_extra.draw(g); self.draw_extra.draw(g);
if let Some(pt) = g.canvas.get_cursor_in_map_space() { if let Some(pt) = g.canvas.get_cursor_in_map_space() {
for (poly, txt) in &self.tooltips { for (poly, txt, _) in &self.tooltips {
if poly.contains_pt(pt) { if poly.contains_pt(pt) {
g.draw_mouse_tooltip(txt.clone()); g.draw_mouse_tooltip(txt.clone());
break; break;
@ -656,6 +643,44 @@ impl InfoPanel {
} }
} }
// Internal helper method for InfoPanel::event
fn do_time_warp(
ctx_actions: &mut dyn ContextualActions,
app: &mut App,
trip: TripID,
time: Time,
) -> (bool, Option<Transition>) {
let person = app.primary.sim.trip_to_person(trip).unwrap();
// When executed, this assumes the SandboxMode is the top of the stack. It'll
// reopen the info panel, then launch the jump-to-time UI.
let jump_to_time = Transition::ConsumeState(Box::new(move |state, ctx, app| {
let mut sandbox = state.downcast::<SandboxMode>().ok().unwrap();
let mut actions = sandbox.contextual_actions();
sandbox.controls.common.as_mut().unwrap().launch_info_panel(
ctx,
app,
Tab::PersonTrips(person, OpenTrip::single(trip)),
&mut actions,
);
vec![sandbox, TimeWarpScreen::new_state(ctx, app, time, None)]
}));
if time >= app.primary.sim.time() {
return (false, Some(jump_to_time));
}
// We need to first rewind the simulation
let rewind_sim = Transition::Replace(SandboxMode::async_new(
app,
ctx_actions.gameplay_mode(),
Box::new(move |_, _| vec![jump_to_time]),
));
(false, Some(rewind_sim))
}
fn make_table<I: Into<String>>(ctx: &EventCtx, rows: Vec<(I, String)>) -> Vec<Widget> { fn make_table<I: Into<String>>(ctx: &EventCtx, rows: Vec<(I, String)>) -> Vec<Widget> {
rows.into_iter() rows.into_iter()
.map(|(k, v)| { .map(|(k, v)| {

View File

@ -491,7 +491,7 @@ fn draw_problems(
map: &Map, map: &Map,
) { ) {
let empty = Vec::new(); let empty = Vec::new();
for (_, problem) in analytics.problems_per_trip.get(&id).unwrap_or(&empty) { for (time, problem) in analytics.problems_per_trip.get(&id).unwrap_or(&empty) {
match problem { match problem {
Problem::IntersectionDelay(i, delay) => { Problem::IntersectionDelay(i, delay) => {
let i = map.get_i(*i); let i = map.get_i(*i);
@ -524,6 +524,8 @@ fn draw_problems(
details.tooltips.push(( details.tooltips.push((
i.polygon.clone(), i.polygon.clone(),
Text::from(Line(format!("{} delay here", delay))), Text::from(Line(format!("{} delay here", delay))),
// Rewind to just before the agent starts waiting
(id, *time - *delay - Duration::seconds(5.0)),
)); ));
} }
Problem::ComplexIntersectionCrossing(i) => { Problem::ComplexIntersectionCrossing(i) => {
@ -546,6 +548,7 @@ fn draw_problems(
Line("This has an increased risk of crash or injury for cyclists"), Line("This has an increased risk of crash or injury for cyclists"),
Line("Source: 2020 Seattle DOT Safety Analysis"), Line("Source: 2020 Seattle DOT Safety Analysis"),
]), ]),
(id, *time),
)); ));
} }
Problem::OvertakeDesired(on) => { Problem::OvertakeDesired(on) => {
@ -567,6 +570,7 @@ fn draw_problems(
Traversable::Turn(t) => map.get_i(t.parent).polygon.clone(), Traversable::Turn(t) => map.get_i(t.parent).polygon.clone(),
}, },
Text::from("A vehicle wanted to over-take this cyclist near here."), Text::from("A vehicle wanted to over-take this cyclist near here."),
(id, *time),
)); ));
} }
Problem::ArterialIntersectionCrossing(t) => { Problem::ArterialIntersectionCrossing(t) => {
@ -590,6 +594,7 @@ fn draw_problems(
Line("Arterial intersections have an increased risk of crash or injury for pedestrians"), Line("Arterial intersections have an increased risk of crash or injury for pedestrians"),
Line("Source: 2020 Seattle DOT Safety Analysis"), Line("Source: 2020 Seattle DOT Safety Analysis"),
]), ]),
(id, *time)
)); ));
} }
Problem::PedestrianOvercrowding(on) => { Problem::PedestrianOvercrowding(on) => {
@ -611,6 +616,7 @@ fn draw_problems(
Traversable::Turn(t) => map.get_i(t.parent).polygon.clone(), Traversable::Turn(t) => map.get_i(t.parent).polygon.clone(),
}, },
Text::from("Too many pedestrians are crowded together here."), Text::from("Too many pedestrians are crowded together here."),
(id, *time),
)); ));
} }
} }