correctly detect the current tab, even when inner settings change. also

make tab per object type sticky, so you can (for example) click arrivals
on one border, then click a different border and be on the same tab!
This commit is contained in:
Dustin Carlino 2020-07-14 13:37:13 -07:00
parent 7cd0c77068
commit 2c27c89796
6 changed files with 127 additions and 40 deletions

View File

@ -9,6 +9,7 @@ use abstutil::{MeasureMemory, Timer};
use ezgui::{EventCtx, GfxCtx, Prerender}; use ezgui::{EventCtx, GfxCtx, Prerender};
use geom::{Bounds, Circle, Distance, Duration, Pt2D, Time}; use geom::{Bounds, Circle, Distance, Duration, Pt2D, Time};
use map_model::{IntersectionID, Map, Traversable}; use map_model::{IntersectionID, Map, Traversable};
use maplit::btreemap;
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use sim::{Analytics, GetDrawAgents, Sim, SimCallback, SimFlags}; use sim::{Analytics, GetDrawAgents, Sim, SimCallback, SimFlags};
use std::collections::BTreeMap; use std::collections::BTreeMap;
@ -573,6 +574,7 @@ impl PerMap {
pub struct SessionState { pub struct SessionState {
pub tutorial: Option<TutorialState>, pub tutorial: Option<TutorialState>,
pub high_scores: BTreeMap<GameplayMode, Vec<HighScore>>, pub high_scores: BTreeMap<GameplayMode, Vec<HighScore>>,
pub info_panel_tab: BTreeMap<&'static str, &'static str>,
} }
impl SessionState { impl SessionState {
@ -580,6 +582,13 @@ impl SessionState {
SessionState { SessionState {
tutorial: None, tutorial: None,
high_scores: BTreeMap::new(), high_scores: BTreeMap::new(),
info_panel_tab: btreemap! {
"lane" => "info",
"intersection" => "info",
"bldg" => "info",
"person" => "trips",
"bus" => "status",
},
} }
} }
} }

View File

@ -55,15 +55,11 @@ impl CommonState {
return Some(Transition::Push(warp::EnteringWarp::new())); return Some(Transition::Push(warp::EnteringWarp::new()));
} }
if let Some(ref id) = app.primary.current_selection { if let Some(id) = app.primary.current_selection.clone() {
// TODO Also have a hotkey binding for this? // TODO Also have a hotkey binding for this?
if app.per_obj.left_click(ctx, "show info") { if app.per_obj.left_click(ctx, "show info") {
self.info_panel = Some(InfoPanel::new( self.info_panel =
ctx, Some(InfoPanel::new(ctx, app, Tab::from_id(app, id), ctx_actions));
app,
Tab::from_id(app, id.clone()),
ctx_actions,
));
return None; return None;
} }
} }

View File

@ -295,7 +295,7 @@ fn header(
tabs.push(("Delay", Tab::IntersectionDelay(id, DataOptions::new()))); tabs.push(("Delay", Tab::IntersectionDelay(id, DataOptions::new())));
tabs.push(("Current demand", Tab::IntersectionDemand(id))); tabs.push(("Current demand", Tab::IntersectionDemand(id)));
} }
if i.is_border() && !i.outgoing_lanes.is_empty() { if i.is_incoming_border() {
tabs.push(( tabs.push((
"Arrivals", "Arrivals",
Tab::IntersectionArrivals(id, DataOptions::new()), Tab::IntersectionArrivals(id, DataOptions::new()),

View File

@ -45,9 +45,7 @@ pub struct InfoPanel {
cached_actions: Vec<Key>, cached_actions: Vec<Key>,
} }
// TODO We need a separate, weaker form of PartialEq for this to detect when we're on the "current" #[derive(Clone)]
// tab.
#[derive(Clone, PartialEq)]
pub enum Tab { pub enum Tab {
// What trips are open? For finished trips, show the timeline in the current simulation if // What trips are open? For finished trips, show the timeline in the current simulation if
// true, prebaked if false. // true, prebaked if false.
@ -85,41 +83,94 @@ impl Tab {
pub fn from_id(app: &App, id: ID) -> Tab { pub fn from_id(app: &App, id: ID) -> Tab {
match id { match id {
ID::Road(_) => unreachable!(), ID::Road(_) => unreachable!(),
ID::Lane(l) => Tab::LaneInfo(l), ID::Lane(l) => match app.session.info_panel_tab["lane"] {
ID::Intersection(i) => Tab::IntersectionInfo(i), "info" => Tab::LaneInfo(l),
ID::Building(b) => Tab::BldgInfo(b), "debug" => Tab::LaneDebug(l),
"traffic" => Tab::LaneTraffic(l, DataOptions::new()),
_ => unreachable!(),
},
ID::Intersection(i) => match app.session.info_panel_tab["intersection"] {
"info" => Tab::IntersectionInfo(i),
"traffic" => Tab::IntersectionTraffic(i, DataOptions::new()),
"delay" => {
if app.primary.map.get_i(i).is_traffic_signal() {
Tab::IntersectionDelay(i, DataOptions::new())
} else {
Tab::IntersectionInfo(i)
}
}
"demand" => {
if app.primary.map.get_i(i).is_traffic_signal() {
Tab::IntersectionDemand(i)
} else {
Tab::IntersectionInfo(i)
}
}
"arrivals" => {
if app.primary.map.get_i(i).is_incoming_border() {
Tab::IntersectionArrivals(i, DataOptions::new())
} else {
Tab::IntersectionInfo(i)
}
}
_ => unreachable!(),
},
ID::Building(b) => match app.session.info_panel_tab["bldg"] {
"info" => Tab::BldgInfo(b),
"people" => Tab::BldgPeople(b),
_ => unreachable!(),
},
ID::ParkingLot(b) => Tab::ParkingLot(b), ID::ParkingLot(b) => Tab::ParkingLot(b),
ID::Car(c) => { ID::Car(c) => {
if let Some(p) = app.primary.sim.agent_to_person(AgentID::Car(c)) { if let Some(p) = app.primary.sim.agent_to_person(AgentID::Car(c)) {
Tab::PersonTrips( match app.session.info_panel_tab["person"] {
p, "trips" => Tab::PersonTrips(
OpenTrip::single(app.primary.sim.agent_to_trip(AgentID::Car(c)).unwrap()), p,
) OpenTrip::single(
app.primary.sim.agent_to_trip(AgentID::Car(c)).unwrap(),
),
),
"bio" => Tab::PersonBio(p),
"schedule" => Tab::PersonSchedule(p),
_ => unreachable!(),
}
} else if c.1 == VehicleType::Bus || c.1 == VehicleType::Train { } else if c.1 == VehicleType::Bus || c.1 == VehicleType::Train {
Tab::BusStatus(c) match app.session.info_panel_tab["bus"] {
"status" => Tab::BusStatus(c),
"delays" => Tab::BusDelays(c),
_ => unreachable!(),
}
} else { } else {
Tab::ParkedCar(c) Tab::ParkedCar(c)
} }
} }
ID::Pedestrian(p) => Tab::PersonTrips( ID::Pedestrian(p) => {
app.primary let person = app
.primary
.sim .sim
.agent_to_person(AgentID::Pedestrian(p)) .agent_to_person(AgentID::Pedestrian(p))
.unwrap(), .unwrap();
OpenTrip::single( match app.session.info_panel_tab["person"] {
app.primary "trips" => Tab::PersonTrips(
.sim person,
.agent_to_trip(AgentID::Pedestrian(p)) OpenTrip::single(
.unwrap(), app.primary
), .sim
), .agent_to_trip(AgentID::Pedestrian(p))
.unwrap(),
),
),
"bio" => Tab::PersonBio(person),
"schedule" => Tab::PersonSchedule(person),
_ => unreachable!(),
}
}
ID::PedCrowd(members) => Tab::Crowd(members), ID::PedCrowd(members) => Tab::Crowd(members),
ID::BusStop(bs) => Tab::BusStop(bs), ID::BusStop(bs) => Tab::BusStop(bs),
ID::Area(a) => Tab::Area(a), ID::Area(a) => Tab::Area(a),
} }
} }
// TODO Temporary hack until object actions go away.
fn to_id(&self, app: &App) -> Option<ID> { fn to_id(&self, app: &App) -> Option<ID> {
match self { match self {
Tab::PersonTrips(p, _) | Tab::PersonBio(p) | Tab::PersonSchedule(p) => { Tab::PersonTrips(p, _) | Tab::PersonBio(p) | Tab::PersonSchedule(p) => {
@ -136,8 +187,8 @@ impl Tab {
} }
Tab::BusStatus(c) | Tab::BusDelays(c) => Some(ID::Car(*c)), Tab::BusStatus(c) | Tab::BusDelays(c) => Some(ID::Car(*c)),
Tab::BusStop(bs) => Some(ID::BusStop(*bs)), Tab::BusStop(bs) => Some(ID::BusStop(*bs)),
// TODO If a parked car becomes in use while the panel is open, should update the panel // TODO If a parked car becomes in use while the panel is open, should update the
// better. // panel better.
Tab::ParkedCar(c) => match app.primary.sim.lookup_parked_car(*c)?.spot { Tab::ParkedCar(c) => match app.primary.sim.lookup_parked_car(*c)?.spot {
ParkingSpot::Onstreet(_, _) => Some(ID::Car(*c)), ParkingSpot::Onstreet(_, _) => Some(ID::Car(*c)),
ParkingSpot::Offstreet(b, _) => Some(ID::Building(b)), ParkingSpot::Offstreet(b, _) => Some(ID::Building(b)),
@ -174,14 +225,39 @@ impl Tab {
| Tab::IntersectionDelay(_, ref mut opts) | Tab::IntersectionDelay(_, ref mut opts)
| Tab::IntersectionArrivals(_, ref mut opts) | Tab::IntersectionArrivals(_, ref mut opts)
| Tab::LaneTraffic(_, ref mut opts) => { | Tab::LaneTraffic(_, ref mut opts) => {
*opts = DataOptions::from_controls(c); let new_opts = DataOptions::from_controls(c);
if *opts == new_opts {
return None;
}
*opts = new_opts;
} }
_ => unreachable!(), _ => unreachable!(),
} }
if &new_tab == self { Some(new_tab)
None }
} else {
Some(new_tab) fn variant(&self) -> (&'static str, &'static str) {
match self {
Tab::PersonTrips(_, _) => ("person", "trips"),
Tab::PersonBio(_) => ("person", "bio"),
Tab::PersonSchedule(_) => ("person", "schedule"),
Tab::BusStatus(_) => ("bus", "status"),
Tab::BusDelays(_) => ("bus", "delays"),
Tab::BusStop(_) => ("bus stop", "info"),
Tab::ParkedCar(_) => ("parked car", "info"),
Tab::BldgInfo(_) => ("bldg", "info"),
Tab::BldgPeople(_) => ("bldg", "people"),
Tab::ParkingLot(_) => ("parking lot", "info"),
Tab::Crowd(_) => ("crowd", "info"),
Tab::Area(_) => ("area", "info"),
Tab::IntersectionInfo(_) => ("intersection", "info"),
Tab::IntersectionTraffic(_, _) => ("intersection", "traffic"),
Tab::IntersectionDelay(_, _) => ("intersection", "delay"),
Tab::IntersectionDemand(_) => ("intersection", "demand"),
Tab::IntersectionArrivals(_, _) => ("intersection", "arrivals"),
Tab::LaneInfo(_) => ("lane", "info"),
Tab::LaneDebug(_) => ("lane", "debug"),
Tab::LaneTraffic(_, _) => ("lane", "traffic"),
} }
} }
} }
@ -198,10 +274,13 @@ pub struct Details {
impl InfoPanel { impl InfoPanel {
pub fn new( pub fn new(
ctx: &mut EventCtx, ctx: &mut EventCtx,
app: &App, app: &mut App,
mut tab: Tab, mut tab: Tab,
ctx_actions: &mut dyn ContextualActions, ctx_actions: &mut dyn ContextualActions,
) -> InfoPanel { ) -> InfoPanel {
let (k, v) = tab.variant();
app.session.info_panel_tab.insert(k, v);
let mut details = Details { let mut details = Details {
unzoomed: GeomBatch::new(), unzoomed: GeomBatch::new(),
zoomed: GeomBatch::new(), zoomed: GeomBatch::new(),
@ -561,7 +640,7 @@ fn make_tabs(
) -> Widget { ) -> Widget {
let mut row = Vec::new(); let mut row = Vec::new();
for (name, link) in tabs { for (name, link) in tabs {
if current_tab == link { if current_tab.variant() == link.variant() {
row.push(Btn::text_bg2(name).inactive(ctx).centered_vert()); row.push(Btn::text_bg2(name).inactive(ctx).centered_vert());
} else { } else {
hyperlinks.insert(name.to_string(), link); hyperlinks.insert(name.to_string(), link);

View File

@ -421,7 +421,7 @@ impl Map {
pub fn all_incoming_borders(&self) -> Vec<&Intersection> { pub fn all_incoming_borders(&self) -> Vec<&Intersection> {
let mut result: Vec<&Intersection> = Vec::new(); let mut result: Vec<&Intersection> = Vec::new();
for i in &self.intersections { for i in &self.intersections {
if i.is_border() && !i.outgoing_lanes.is_empty() { if i.is_incoming_border() {
result.push(i); result.push(i);
} }
} }

View File

@ -53,6 +53,9 @@ impl Intersection {
pub fn is_border(&self) -> bool { pub fn is_border(&self) -> bool {
self.intersection_type == IntersectionType::Border self.intersection_type == IntersectionType::Border
} }
pub fn is_incoming_border(&self) -> bool {
self.intersection_type == IntersectionType::Border && !self.outgoing_lanes.is_empty()
}
pub fn is_closed(&self) -> bool { pub fn is_closed(&self) -> bool {
self.intersection_type == IntersectionType::Construction self.intersection_type == IntersectionType::Construction