mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-01 02:33:54 +03:00
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:
parent
7cd0c77068
commit
2c27c89796
@ -9,6 +9,7 @@ use abstutil::{MeasureMemory, Timer};
|
||||
use ezgui::{EventCtx, GfxCtx, Prerender};
|
||||
use geom::{Bounds, Circle, Distance, Duration, Pt2D, Time};
|
||||
use map_model::{IntersectionID, Map, Traversable};
|
||||
use maplit::btreemap;
|
||||
use rand::seq::SliceRandom;
|
||||
use sim::{Analytics, GetDrawAgents, Sim, SimCallback, SimFlags};
|
||||
use std::collections::BTreeMap;
|
||||
@ -573,6 +574,7 @@ impl PerMap {
|
||||
pub struct SessionState {
|
||||
pub tutorial: Option<TutorialState>,
|
||||
pub high_scores: BTreeMap<GameplayMode, Vec<HighScore>>,
|
||||
pub info_panel_tab: BTreeMap<&'static str, &'static str>,
|
||||
}
|
||||
|
||||
impl SessionState {
|
||||
@ -580,6 +582,13 @@ impl SessionState {
|
||||
SessionState {
|
||||
tutorial: None,
|
||||
high_scores: BTreeMap::new(),
|
||||
info_panel_tab: btreemap! {
|
||||
"lane" => "info",
|
||||
"intersection" => "info",
|
||||
"bldg" => "info",
|
||||
"person" => "trips",
|
||||
"bus" => "status",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,15 +55,11 @@ impl CommonState {
|
||||
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?
|
||||
if app.per_obj.left_click(ctx, "show info") {
|
||||
self.info_panel = Some(InfoPanel::new(
|
||||
ctx,
|
||||
app,
|
||||
Tab::from_id(app, id.clone()),
|
||||
ctx_actions,
|
||||
));
|
||||
self.info_panel =
|
||||
Some(InfoPanel::new(ctx, app, Tab::from_id(app, id), ctx_actions));
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
@ -295,7 +295,7 @@ fn header(
|
||||
tabs.push(("Delay", Tab::IntersectionDelay(id, DataOptions::new())));
|
||||
tabs.push(("Current demand", Tab::IntersectionDemand(id)));
|
||||
}
|
||||
if i.is_border() && !i.outgoing_lanes.is_empty() {
|
||||
if i.is_incoming_border() {
|
||||
tabs.push((
|
||||
"Arrivals",
|
||||
Tab::IntersectionArrivals(id, DataOptions::new()),
|
||||
|
@ -45,9 +45,7 @@ pub struct InfoPanel {
|
||||
cached_actions: Vec<Key>,
|
||||
}
|
||||
|
||||
// TODO We need a separate, weaker form of PartialEq for this to detect when we're on the "current"
|
||||
// tab.
|
||||
#[derive(Clone, PartialEq)]
|
||||
#[derive(Clone)]
|
||||
pub enum Tab {
|
||||
// What trips are open? For finished trips, show the timeline in the current simulation if
|
||||
// true, prebaked if false.
|
||||
@ -85,27 +83,76 @@ impl Tab {
|
||||
pub 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::Building(b) => Tab::BldgInfo(b),
|
||||
ID::Lane(l) => match app.session.info_panel_tab["lane"] {
|
||||
"info" => Tab::LaneInfo(l),
|
||||
"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::Car(c) => {
|
||||
if let Some(p) = app.primary.sim.agent_to_person(AgentID::Car(c)) {
|
||||
Tab::PersonTrips(
|
||||
match app.session.info_panel_tab["person"] {
|
||||
"trips" => Tab::PersonTrips(
|
||||
p,
|
||||
OpenTrip::single(app.primary.sim.agent_to_trip(AgentID::Car(c)).unwrap()),
|
||||
)
|
||||
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 {
|
||||
Tab::BusStatus(c)
|
||||
match app.session.info_panel_tab["bus"] {
|
||||
"status" => Tab::BusStatus(c),
|
||||
"delays" => Tab::BusDelays(c),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
Tab::ParkedCar(c)
|
||||
}
|
||||
}
|
||||
ID::Pedestrian(p) => Tab::PersonTrips(
|
||||
app.primary
|
||||
ID::Pedestrian(p) => {
|
||||
let person = app
|
||||
.primary
|
||||
.sim
|
||||
.agent_to_person(AgentID::Pedestrian(p))
|
||||
.unwrap(),
|
||||
.unwrap();
|
||||
match app.session.info_panel_tab["person"] {
|
||||
"trips" => Tab::PersonTrips(
|
||||
person,
|
||||
OpenTrip::single(
|
||||
app.primary
|
||||
.sim
|
||||
@ -113,13 +160,17 @@ impl Tab {
|
||||
.unwrap(),
|
||||
),
|
||||
),
|
||||
"bio" => Tab::PersonBio(person),
|
||||
"schedule" => Tab::PersonSchedule(person),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
ID::PedCrowd(members) => Tab::Crowd(members),
|
||||
ID::BusStop(bs) => Tab::BusStop(bs),
|
||||
ID::Area(a) => Tab::Area(a),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Temporary hack until object actions go away.
|
||||
fn to_id(&self, app: &App) -> Option<ID> {
|
||||
match self {
|
||||
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::BusStop(bs) => Some(ID::BusStop(*bs)),
|
||||
// TODO If a parked car becomes in use while the panel is open, should update the panel
|
||||
// better.
|
||||
// TODO If a parked car becomes in use while the panel is open, should update the
|
||||
// panel better.
|
||||
Tab::ParkedCar(c) => match app.primary.sim.lookup_parked_car(*c)?.spot {
|
||||
ParkingSpot::Onstreet(_, _) => Some(ID::Car(*c)),
|
||||
ParkingSpot::Offstreet(b, _) => Some(ID::Building(b)),
|
||||
@ -174,15 +225,40 @@ impl Tab {
|
||||
| Tab::IntersectionDelay(_, ref mut opts)
|
||||
| Tab::IntersectionArrivals(_, 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!(),
|
||||
}
|
||||
if &new_tab == self {
|
||||
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 {
|
||||
pub fn new(
|
||||
ctx: &mut EventCtx,
|
||||
app: &App,
|
||||
app: &mut App,
|
||||
mut tab: Tab,
|
||||
ctx_actions: &mut dyn ContextualActions,
|
||||
) -> InfoPanel {
|
||||
let (k, v) = tab.variant();
|
||||
app.session.info_panel_tab.insert(k, v);
|
||||
|
||||
let mut details = Details {
|
||||
unzoomed: GeomBatch::new(),
|
||||
zoomed: GeomBatch::new(),
|
||||
@ -561,7 +640,7 @@ fn make_tabs(
|
||||
) -> Widget {
|
||||
let mut row = Vec::new();
|
||||
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());
|
||||
} else {
|
||||
hyperlinks.insert(name.to_string(), link);
|
||||
|
@ -421,7 +421,7 @@ impl Map {
|
||||
pub fn all_incoming_borders(&self) -> Vec<&Intersection> {
|
||||
let mut result: Vec<&Intersection> = Vec::new();
|
||||
for i in &self.intersections {
|
||||
if i.is_border() && !i.outgoing_lanes.is_empty() {
|
||||
if i.is_incoming_border() {
|
||||
result.push(i);
|
||||
}
|
||||
}
|
||||
|
@ -53,6 +53,9 @@ impl Intersection {
|
||||
pub fn is_border(&self) -> bool {
|
||||
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 {
|
||||
self.intersection_type == IntersectionType::Construction
|
||||
|
Loading…
Reference in New Issue
Block a user