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 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",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()),
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user