diff --git a/game/src/common/mod.rs b/game/src/common/mod.rs index b87e8dfb71..0f2874ea72 100644 --- a/game/src/common/mod.rs +++ b/game/src/common/mod.rs @@ -19,7 +19,7 @@ pub use self::warp::Warping; use crate::app::App; use crate::game::Transition; use crate::helpers::{list_names, ID}; -use crate::info::{InfoPanel, InfoTab}; +use crate::info::InfoPanel; use crate::sandbox::SpeedControls; use ezgui::{ lctrl, Color, EventCtx, GeomBatch, GfxCtx, Key, Line, ScreenDims, ScreenPt, ScreenRectangle, @@ -68,11 +68,10 @@ impl CommonState { { app.per_obj.info_panel_open = true; let actions = app.per_obj.consume(); - self.info_panel = Some(InfoPanel::new( - id.clone(), - InfoTab::Nil, + self.info_panel = Some(InfoPanel::launch( ctx, app, + id.clone(), actions, maybe_speed, )); @@ -285,11 +284,12 @@ impl CommonState { // Meant to be used for launching from other states pub fn launch_info_panel(&mut self, id: ID, ctx: &mut EventCtx, app: &mut App) { - self.info_panel = Some(InfoPanel::new(id, InfoTab::Nil, ctx, app, Vec::new(), None)); + self.info_panel = Some(InfoPanel::launch(ctx, app, id, Vec::new(), None)); app.per_obj.info_panel_open = true; } pub fn info_panel_open(&self) -> Option { - self.info_panel.as_ref().map(|i| i.id.clone()) + panic!("TODO") + //self.info_panel.as_ref().map(|i| i.id.clone()) } } diff --git a/game/src/info/debug.rs b/game/src/info/debug.rs index 7f8f7766b3..89f5c17a48 100644 --- a/game/src/info/debug.rs +++ b/game/src/info/debug.rs @@ -1,23 +1,16 @@ use crate::app::App; -use crate::info::make_table; +use crate::info::{header_btns, make_table, Details}; use crate::render::ExtraShapeID; use ezgui::{EventCtx, Line, Widget}; use map_model::AreaID; -pub fn area( - ctx: &EventCtx, - app: &App, - id: AreaID, - header_btns: Widget, - action_btns: Vec, -) -> Vec { +pub fn area(ctx: &EventCtx, app: &App, _: &mut Details, id: AreaID) -> Vec { let mut rows = vec![]; rows.push(Widget::row(vec![ Line(format!("Area #{}", id.0)).small_heading().draw(ctx), - header_btns, + header_btns(ctx), ])); - rows.extend(action_btns); let a = app.primary.map.get_a(id); let mut kv = Vec::new(); @@ -29,22 +22,15 @@ pub fn area( rows } -pub fn extra_shape( - ctx: &EventCtx, - app: &App, - id: ExtraShapeID, - header_btns: Widget, - action_btns: Vec, -) -> Vec { +pub fn extra_shape(ctx: &EventCtx, app: &App, _: &mut Details, id: ExtraShapeID) -> Vec { let mut rows = vec![]; rows.push(Widget::row(vec![ Line(format!("Extra GIS shape #{}", id.0)) .small_heading() .draw(ctx), - header_btns, + header_btns(ctx), ])); - rows.extend(action_btns); let es = app.primary.draw_map.get_es(id); let mut kv = Vec::new(); diff --git a/game/src/info/intersection.rs b/game/src/info/intersection.rs index b9ae7b4acf..ea5c6246cc 100644 --- a/game/src/info/intersection.rs +++ b/game/src/info/intersection.rs @@ -1,132 +1,99 @@ use crate::app::App; -use crate::helpers::{rotating_color_map, ID}; -use crate::info::{make_tabs, throughput, InfoTab}; +use crate::helpers::rotating_color_map; +use crate::info::{header_btns, make_tabs, throughput, Details, Tab}; use abstutil::prettyprint_usize; use ezgui::{EventCtx, Line, Plot, PlotOptions, Series, Text, Widget}; use geom::{Duration, Statistic, Time}; use map_model::{IntersectionID, IntersectionType}; use sim::{Analytics, TripEndpoint}; -use std::collections::{BTreeSet, HashMap}; - -#[derive(Clone, PartialEq)] -pub enum Tab { - Throughput, - Delay, -} - -pub fn info( - ctx: &EventCtx, - app: &App, - id: IntersectionID, - tab: InfoTab, - header_btns: Widget, - action_btns: Vec, - hyperlinks: &mut HashMap, -) -> Vec { - let mut rows = vec![]; +use std::collections::BTreeSet; +pub fn info(ctx: &EventCtx, app: &App, details: &mut Details, id: IntersectionID) -> Vec { + let mut rows = header(ctx, app, details, id, Tab::IntersectionInfo(id)); let i = app.primary.map.get_i(id); - let label = match i.intersection_type { - IntersectionType::StopSign => format!("Intersection #{} (Stop signs)", id.0), - IntersectionType::TrafficSignal => format!("Intersection #{} (Traffic signals)", id.0), - IntersectionType::Border => format!("Border #{}", id.0), - IntersectionType::Construction => format!("Intersection #{} (under construction)", id.0), - }; - rows.push(Widget::row(vec![ - Line(label).small_heading().draw(ctx), - header_btns, - ])); + let mut txt = Text::from(Line("Connecting")); + let mut road_names = BTreeSet::new(); + for r in &i.roads { + road_names.insert(app.primary.map.get_r(*r).get_name()); + } + for r in road_names { + // TODO The spacing is ignored, so use - + txt.add(Line(format!("- {}", r))); + } + rows.push(txt.draw(ctx)); - rows.push(make_tabs( - ctx, - hyperlinks, - ID::Intersection(id), - tab.clone(), - { - let mut tabs = vec![ - ("Info", InfoTab::Nil), - ("Traffic", InfoTab::Intersection(Tab::Throughput)), - ]; - if app.primary.map.get_i(id).is_traffic_signal() { - tabs.push(("Delay", InfoTab::Intersection(Tab::Delay))); - } - tabs - }, - )); - - match tab { - InfoTab::Nil => { - rows.extend(action_btns); - - let mut txt = Text::from(Line("Connecting")); - let mut road_names = BTreeSet::new(); - for r in &i.roads { - road_names.insert(app.primary.map.get_r(*r).get_name()); - } - for r in road_names { - // TODO The spacing is ignored, so use - - txt.add(Line(format!("- {}", r))); - } - rows.push(txt.draw(ctx)); - - // TODO Rethink - let trip_lines = app - .primary - .sim - .count_trips(TripEndpoint::Border(id)) - .describe(); - if !trip_lines.is_empty() { - let mut txt = Text::new(); - for line in trip_lines { - txt.add(Line(line)); - } - rows.push(txt.draw(ctx)); - } + // TODO Rethink + let trip_lines = app + .primary + .sim + .count_trips(TripEndpoint::Border(id)) + .describe(); + if !trip_lines.is_empty() { + let mut txt = Text::new(); + for line in trip_lines { + txt.add(Line(line)); } - InfoTab::Intersection(Tab::Throughput) => { - let mut txt = Text::new(); - - txt.add(Line("Throughput")); - txt.add( - Line(format!( - "Since midnight: {} agents crossed", - prettyprint_usize( - app.primary - .sim - .get_analytics() - .thruput_stats - .count_per_intersection - .get(id) - ) - )) - .secondary(), - ); - txt.add(Line(format!("In 20 minute buckets:")).secondary()); - rows.push(txt.draw(ctx)); - - rows.push( - throughput(ctx, app, move |a, t| { - a.throughput_intersection(t, id, Duration::minutes(20)) - }) - .margin(10), - ); - } - InfoTab::Intersection(Tab::Delay) => { - assert!(app.primary.map.get_i(id).is_traffic_signal()); - let mut txt = Text::from(Line("Delay")); - txt.add(Line(format!("In 20 minute buckets:")).secondary()); - rows.push(txt.draw(ctx)); - - rows.push(delay(ctx, app, id, Duration::minutes(20)).margin(10)); - } - _ => unreachable!(), + rows.push(txt.draw(ctx)); } rows } -fn delay(ctx: &EventCtx, app: &App, i: IntersectionID, bucket: Duration) -> Widget { +pub fn traffic( + ctx: &EventCtx, + app: &App, + details: &mut Details, + id: IntersectionID, +) -> Vec { + let mut rows = header(ctx, app, details, id, Tab::IntersectionTraffic(id)); + let i = app.primary.map.get_i(id); + + let mut txt = Text::new(); + + txt.add(Line("Throughput")); + txt.add( + Line(format!( + "Since midnight: {} agents crossed", + prettyprint_usize( + app.primary + .sim + .get_analytics() + .thruput_stats + .count_per_intersection + .get(id) + ) + )) + .secondary(), + ); + txt.add(Line(format!("In 20 minute buckets:")).secondary()); + rows.push(txt.draw(ctx)); + + rows.push( + throughput(ctx, app, move |a, t| { + a.throughput_intersection(t, id, Duration::minutes(20)) + }) + .margin(10), + ); + + rows +} + +pub fn delay(ctx: &EventCtx, app: &App, details: &mut Details, id: IntersectionID) -> Vec { + let mut rows = header(ctx, app, details, id, Tab::IntersectionDelay(id)); + let i = app.primary.map.get_i(id); + + assert!(i.is_traffic_signal()); + let mut txt = Text::from(Line("Delay")); + txt.add(Line(format!("In 20 minute buckets:")).secondary()); + rows.push(txt.draw(ctx)); + + rows.push(delay_plot(ctx, app, id, Duration::minutes(20)).margin(10)); + + rows +} + +fn delay_plot(ctx: &EventCtx, app: &App, i: IntersectionID, bucket: Duration) -> Widget { let get_data = |a: &Analytics, t: Time| { let mut series: Vec<(Statistic, Vec<(Time, Duration)>)> = Statistic::all() .into_iter() @@ -170,3 +137,39 @@ fn delay(ctx: &EventCtx, app: &App, i: IntersectionID, bucket: Duration) -> Widg Plot::new_duration(ctx, all_series, PlotOptions::new()) } + +fn header( + ctx: &EventCtx, + app: &App, + details: &mut Details, + id: IntersectionID, + tab: Tab, +) -> Vec { + let mut rows = vec![]; + + let i = app.primary.map.get_i(id); + + let label = match i.intersection_type { + IntersectionType::StopSign => format!("Intersection #{} (Stop signs)", id.0), + IntersectionType::TrafficSignal => format!("Intersection #{} (Traffic signals)", id.0), + IntersectionType::Border => format!("Border #{}", id.0), + IntersectionType::Construction => format!("Intersection #{} (under construction)", id.0), + }; + rows.push(Widget::row(vec![ + Line(label).small_heading().draw(ctx), + header_btns(ctx), + ])); + + rows.push(make_tabs(ctx, &mut details.hyperlinks, tab, { + let mut tabs = vec![ + ("Info", Tab::IntersectionInfo(id)), + ("Traffic", Tab::IntersectionTraffic(id)), + ]; + if i.is_traffic_signal() { + tabs.push(("Delay", Tab::IntersectionDelay(id))); + } + tabs + })); + + rows +} diff --git a/game/src/info/lane.rs b/game/src/info/lane.rs index 6afe7513da..d9c8de15ef 100644 --- a/game/src/info/lane.rs +++ b/game/src/info/lane.rs @@ -1,27 +1,136 @@ use crate::app::App; -use crate::helpers::ID; -use crate::info::{make_table, make_tabs, throughput, InfoTab}; +use crate::info::{header_btns, make_table, make_tabs, throughput, Details, Tab}; use abstutil::prettyprint_usize; use ezgui::{EventCtx, Line, Text, TextExt, Widget}; use geom::Duration; use map_model::LaneID; -use std::collections::HashMap; -#[derive(Clone, PartialEq)] -pub enum Tab { - Debug, - Throughput, +pub fn info(ctx: &EventCtx, app: &App, details: &mut Details, id: LaneID) -> Vec { + let mut rows = header(ctx, app, details, id, Tab::LaneInfo(id)); + let map = &app.primary.map; + let l = map.get_l(id); + let r = map.get_r(l.parent); + + let mut kv = Vec::new(); + + if !l.is_sidewalk() { + kv.push(("Type", l.lane_type.describe().to_string())); + } + + if l.is_parking() { + kv.push(( + "Parking", + format!("{} spots, parallel parking", l.number_parking_spots()), + )); + } else { + kv.push(("Speed limit", r.get_speed_limit().to_string())); + } + + kv.push(("Length", l.length().describe_rounded())); + + rows.extend(make_table(ctx, kv)); + + rows } -pub fn info( - ctx: &EventCtx, - app: &App, - id: LaneID, - tab: InfoTab, - header_btns: Widget, - action_btns: Vec, - hyperlinks: &mut HashMap, -) -> Vec { +pub fn debug(ctx: &EventCtx, app: &App, details: &mut Details, id: LaneID) -> Vec { + let mut rows = header(ctx, app, details, id, Tab::LaneDebug(id)); + let map = &app.primary.map; + let l = map.get_l(id); + let r = map.get_r(l.parent); + + let mut kv = Vec::new(); + + kv.push(("Parent".to_string(), r.id.to_string())); + + if l.is_driving() { + kv.push(( + "Parking blackhole redirect".to_string(), + format!("{:?}", l.parking_blackhole), + )); + } + + if let Some(types) = l.get_turn_restrictions(r) { + kv.push(("Turn restrictions".to_string(), format!("{:?}", types))); + } + for (restriction, to) in &r.turn_restrictions { + kv.push(( + format!("Restriction from this road to {}", to), + format!("{:?}", restriction), + )); + } + + // TODO Simplify and expose everywhere after there's better data + kv.push(( + "Elevation change".to_string(), + format!( + "{} to {}", + map.get_i(l.src_i).elevation, + map.get_i(l.dst_i).elevation + ), + )); + kv.push(( + "Incline / grade".to_string(), + format!("{:.1}%", l.percent_grade(map) * 100.0), + )); + kv.push(( + "Elevation details".to_string(), + format!( + "{} over {}", + map.get_i(l.dst_i).elevation - map.get_i(l.src_i).elevation, + l.length() + ), + )); + + rows.extend(make_table(ctx, kv)); + + let mut txt = Text::from(Line("")); + txt.add(Line("Raw OpenStreetMap data")); + rows.push(txt.draw(ctx)); + + rows.extend(make_table(ctx, r.osm_tags.clone().into_iter().collect())); + + rows +} + +pub fn traffic(ctx: &EventCtx, app: &App, details: &mut Details, id: LaneID) -> Vec { + let mut rows = header(ctx, app, details, id, Tab::LaneTraffic(id)); + let map = &app.primary.map; + let l = map.get_l(id); + let r = map.get_r(l.parent); + + // Since this applies to the entire road, ignore lane type. + let mut txt = Text::from(Line("")); + txt.add(Line("Throughput (entire road)")); + txt.add( + Line(format!( + "Since midnight: {} agents crossed", + prettyprint_usize( + app.primary + .sim + .get_analytics() + .thruput_stats + .count_per_road + .get(r.id) + ) + )) + .secondary(), + ); + txt.add(Line(format!("In 20 minute buckets:")).secondary()); + rows.push(txt.draw(ctx)); + + let r = map.get_l(id).parent; + rows.push( + throughput(ctx, app, move |a, t| { + a.throughput_road(t, r, Duration::minutes(20)) + }) + .margin(10), + ); + + rows +} + +fn header(ctx: &EventCtx, app: &App, details: &mut Details, id: LaneID, tab: Tab) -> Vec { let mut rows = vec![]; let map = &app.primary.map; @@ -33,128 +142,20 @@ pub fn info( Line(format!("{} #{}", label, id.0)) .small_heading() .draw(ctx), - header_btns, + header_btns(ctx), ])); rows.push(format!("@ {}", r.get_name()).draw_text(ctx)); rows.push(make_tabs( ctx, - hyperlinks, - ID::Lane(id), - tab.clone(), + &mut details.hyperlinks, + tab, vec![ - ("Info", InfoTab::Nil), - ("Traffic", InfoTab::Lane(Tab::Throughput)), - ("Debug", InfoTab::Lane(Tab::Debug)), + ("Info", Tab::LaneInfo(id)), + ("Traffic", Tab::LaneTraffic(id)), + ("Debug", Tab::LaneDebug(id)), ], )); - match tab { - InfoTab::Nil => { - rows.extend(action_btns); - - let mut kv = Vec::new(); - - if !l.is_sidewalk() { - kv.push(("Type", l.lane_type.describe().to_string())); - } - - if l.is_parking() { - kv.push(( - "Parking", - format!("{} spots, parallel parking", l.number_parking_spots()), - )); - } else { - kv.push(("Speed limit", r.get_speed_limit().to_string())); - } - - kv.push(("Length", l.length().describe_rounded())); - - rows.extend(make_table(ctx, kv)); - } - InfoTab::Lane(Tab::Debug) => { - let mut kv = Vec::new(); - - kv.push(("Parent".to_string(), r.id.to_string())); - - if l.is_driving() { - kv.push(( - "Parking blackhole redirect".to_string(), - format!("{:?}", l.parking_blackhole), - )); - } - - if let Some(types) = l.get_turn_restrictions(r) { - kv.push(("Turn restrictions".to_string(), format!("{:?}", types))); - } - for (restriction, to) in &r.turn_restrictions { - kv.push(( - format!("Restriction from this road to {}", to), - format!("{:?}", restriction), - )); - } - - // TODO Simplify and expose everywhere after there's better data - kv.push(( - "Elevation change".to_string(), - format!( - "{} to {}", - map.get_i(l.src_i).elevation, - map.get_i(l.dst_i).elevation - ), - )); - kv.push(( - "Incline / grade".to_string(), - format!("{:.1}%", l.percent_grade(map) * 100.0), - )); - kv.push(( - "Elevation details".to_string(), - format!( - "{} over {}", - map.get_i(l.dst_i).elevation - map.get_i(l.src_i).elevation, - l.length() - ), - )); - - rows.extend(make_table(ctx, kv)); - - let mut txt = Text::from(Line("")); - txt.add(Line("Raw OpenStreetMap data")); - rows.push(txt.draw(ctx)); - - rows.extend(make_table(ctx, r.osm_tags.clone().into_iter().collect())); - } - InfoTab::Lane(Tab::Throughput) => { - // Since this applies to the entire road, ignore lane type. - let mut txt = Text::from(Line("")); - txt.add(Line("Throughput (entire road)")); - txt.add( - Line(format!( - "Since midnight: {} agents crossed", - prettyprint_usize( - app.primary - .sim - .get_analytics() - .thruput_stats - .count_per_road - .get(r.id) - ) - )) - .secondary(), - ); - txt.add(Line(format!("In 20 minute buckets:")).secondary()); - rows.push(txt.draw(ctx)); - - let r = app.primary.map.get_l(id).parent; - rows.push( - throughput(ctx, app, move |a, t| { - a.throughput_road(t, r, Duration::minutes(20)) - }) - .margin(10), - ); - } - _ => unreachable!(), - } - rows } diff --git a/game/src/info/mod.rs b/game/src/info/mod.rs index ae65c9f734..da1c09c71b 100644 --- a/game/src/info/mod.rs +++ b/game/src/info/mod.rs @@ -1,70 +1,128 @@ -mod agents; -mod building; -mod bus_stop; +//mod building; +//mod bus; mod debug; mod intersection; mod lane; -mod person; -mod trip; +//mod person; use crate::app::App; use crate::colors; use crate::common::Warping; -use crate::game::{msg, State, Transition, WizardState}; +use crate::game::Transition; use crate::helpers::ID; -use crate::render::MIN_ZOOM_FOR_DETAIL; -use crate::sandbox::{SandboxMode, SpeedControls}; +use crate::render::{ExtraShapeID, MIN_ZOOM_FOR_DETAIL}; +use crate::sandbox::SpeedControls; use ezgui::{ hotkey, Btn, Color, Composite, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Plot, PlotOptions, Series, Text, TextExt, VerticalAlignment, Widget, }; -use geom::{Circle, Distance, Time}; -use sim::{AgentID, Analytics, TripID, TripMode, TripResult, VehicleType}; +use geom::Time; +use map_model::{AreaID, BuildingID, BusStopID, IntersectionID, LaneID}; +use sim::{AgentID, Analytics, CarID, PedestrianID, PersonID, TripMode, VehicleType}; use std::collections::{BTreeMap, HashMap}; pub struct InfoPanel { - pub id: ID, - tab: InfoTab, + tab: Tab, time: Time, composite: Composite, - also_draw: Drawable, - trip_details: Option, + unzoomed: Drawable, + zoomed: Drawable, actions: Vec<(Key, String)>, - hyperlinks: HashMap, + hyperlinks: HashMap, warpers: HashMap, } -// TODO Safer to expand out ID cases here -// The PartialEq is ONLY used for determining when we're on the current tab. So maybe omit data. #[derive(Clone, PartialEq)] -pub enum InfoTab { - Nil, - Bldg(building::Tab), - Lane(lane::Tab), - Intersection(intersection::Tab), - Agent(agents::Tab), - Trip(trip::Tab), - Person(person::Tab), +pub enum Tab { + PersonStatus(PersonID), + PersonTrips(PersonID), + PersonBio(PersonID), + + Bus(CarID), + BusStop(BusStopID), + + ParkedCar(CarID), + + BldgInfo(BuildingID), + BldgDebug(BuildingID), + BldgPeople(BuildingID), + + Crowd(Vec), + + Area(AreaID), + ExtraShape(ExtraShapeID), + + IntersectionInfo(IntersectionID), + IntersectionTraffic(IntersectionID), + IntersectionDelay(IntersectionID), + + LaneInfo(LaneID), + LaneDebug(LaneID), + LaneTraffic(LaneID), } -pub struct TripDetails { - id: TripID, - unzoomed: Drawable, - zoomed: Drawable, +impl Tab { + 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::Turn(_) => unreachable!(), + ID::Building(b) => Tab::BldgInfo(b), + ID::Car(c) => { + if let Some(p) = app.primary.sim.agent_to_person(AgentID::Car(c)) { + Tab::PersonStatus(p) + } else if c.1 == VehicleType::Bus { + Tab::Bus(c) + } else { + Tab::ParkedCar(c) + } + } + ID::Pedestrian(p) => Tab::PersonStatus( + app.primary + .sim + .agent_to_person(AgentID::Pedestrian(p)) + .unwrap(), + ), + ID::PedCrowd(members) => Tab::Crowd(members), + ID::ExtraShape(es) => Tab::ExtraShape(es), + ID::BusStop(bs) => Tab::BusStop(bs), + ID::Area(a) => Tab::Area(a), + ID::Trip(_) => unreachable!(), + ID::Person(_) => unreachable!(), + } + } +} + +// TODO Name sucks +pub struct Details { + pub unzoomed: GeomBatch, + pub zoomed: GeomBatch, + pub hyperlinks: HashMap, + pub warpers: HashMap, } impl InfoPanel { - pub fn new( - id: ID, - tab: InfoTab, + pub fn launch( ctx: &mut EventCtx, app: &App, + id: ID, + actions: Vec<(Key, String)>, + maybe_speed: Option<&mut SpeedControls>, + ) -> InfoPanel { + InfoPanel::new(ctx, app, Tab::from_id(app, id), actions, maybe_speed) + } + + fn new( + ctx: &mut EventCtx, + app: &App, + tab: Tab, mut actions: Vec<(Key, String)>, maybe_speed: Option<&mut SpeedControls>, ) -> InfoPanel { - if maybe_speed.map(|s| s.is_paused()).unwrap_or(false) + /*if maybe_speed.map(|s| s.is_paused()).unwrap_or(false) && id.agent_id().is_some() && actions .get(0) @@ -72,8 +130,9 @@ impl InfoPanel { .unwrap_or(true) { actions.insert(0, (Key::F, "follow agent".to_string())); - } + }*/ + // TODO dont forget these let action_btns = actions .iter() .map(|(key, label)| { @@ -84,11 +143,17 @@ impl InfoPanel { .build_def(ctx, hotkey(*key)) .margin(5) }) - .collect(); + .collect::>(); - let mut batch = GeomBatch::new(); - // TODO Handle transitions between peds and crowds better - if let Some(obj) = app.primary.draw_map.get_obj( + let mut details = Details { + unzoomed: GeomBatch::new(), + zoomed: GeomBatch::new(), + hyperlinks: HashMap::new(), + warpers: HashMap::new(), + }; + + // Highlight something? + /*if let Some(obj) = app.primary.draw_map.get_obj( id.clone(), app, &mut app.primary.draw_map.agents.borrow_mut(), @@ -132,146 +197,35 @@ impl InfoPanel { ); } } - } + }*/ - let header_btns = Widget::row(vec![ - Btn::svg_def("../data/system/assets/tools/location.svg") - .build(ctx, "jump to object", hotkey(Key::J)) - .margin(5), - Btn::text_fg("X").build(ctx, "close info", hotkey(Key::Escape)), - ]) - .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) => ( - lane::info( - ctx, - app, - id, - tab.clone(), - header_btns, - action_btns, - &mut hyperlinks, - ), - None, - ), - ID::Intersection(id) => ( - intersection::info( - ctx, - app, - id, - tab.clone(), - header_btns, - action_btns, - &mut hyperlinks, - ), - None, - ), - ID::Turn(_) => unreachable!(), - ID::Building(id) => ( - building::info( - ctx, - app, - id, - tab.clone(), - header_btns, - action_btns, - &mut batch, - &mut hyperlinks, - ), - None, - ), - ID::Car(id) => agents::car_info( - ctx, - app, - id, - tab.clone(), - header_btns, - action_btns, - &mut batch, - &mut hyperlinks, - &mut warpers, - ), - ID::Pedestrian(id) => agents::ped_info( - ctx, - app, - id, - tab.clone(), - header_btns, - action_btns, - &mut hyperlinks, - &mut warpers, - ), - ID::PedCrowd(members) => { - assert!(action_btns.is_empty()); - ( - agents::crowd_info( - ctx, - app, - members, - tab.clone(), - header_btns, - &mut hyperlinks, - &mut warpers, - ), - None, - ) - } - ID::BusStop(id) => (bus_stop::info(ctx, app, id, header_btns, action_btns), None), - ID::Area(id) => (debug::area(ctx, app, id, header_btns, action_btns), None), - ID::ExtraShape(id) => ( - debug::extra_shape(ctx, app, id, header_btns, action_btns), - None, - ), - ID::Trip(id) => match app.primary.sim.trip_to_agent(id).ok() { - Some(AgentID::Car(c)) => agents::car_info( - ctx, - app, - c, - tab.clone(), - header_btns, - Vec::new(), - &mut batch, - &mut hyperlinks, - &mut warpers, - ), - Some(AgentID::Pedestrian(p)) => agents::ped_info( - ctx, - app, - p, - tab.clone(), - header_btns, - Vec::new(), - &mut hyperlinks, - &mut warpers, - ), - None => trip::inactive_info( - ctx, - app, - id, - tab.clone(), - action_btns, - &mut hyperlinks, - &mut warpers, - ), - }, - ID::Person(id) => ( - person::info( - ctx, - app, - id, - tab.clone(), - Some(header_btns), - action_btns, - &mut hyperlinks, - &mut warpers, - ), - None, - ), + let col = match tab { + /*Tab::PersonStatus(p) => person::status(ctx, app, &mut details, p), + Tab::PersonTrips(p) => person::trips(ctx, app, &mut details, p), + Tab::PersonBio(p) => person::bio(ctx, app, &mut details, p), + + Tab::Bus(c) => bus::bus(ctx, app, &mut details, c), + Tab::BusStop(bs) => bus::stop(ctx, app, &mut details, bs), + + Tab::ParkedCar(c) => person::parked_car(ctx, app, &mut details, c), + + Tab::BldgInfo(b) => building::info(ctx, app, &mut details, b), + Tab::BldgDebug(b) => building::debug(ctx, app, &mut details, b), + Tab::BldgPeople(b) => building::people(ctx, app, &mut details, b), + + Tab::Crowd(members) => person::crowd(ctx, app, &mut details, members),*/ + Tab::Area(a) => debug::area(ctx, app, &mut details, a), + Tab::ExtraShape(es) => debug::extra_shape(ctx, app, &mut details, es), + Tab::IntersectionInfo(i) => intersection::info(ctx, app, &mut details, i), + Tab::IntersectionTraffic(i) => intersection::traffic(ctx, app, &mut details, i), + Tab::IntersectionDelay(i) => intersection::delay(ctx, app, &mut details, i), + Tab::LaneInfo(l) => lane::info(ctx, app, &mut details, l), + Tab::LaneDebug(l) => lane::debug(ctx, app, &mut details, l), + Tab::LaneTraffic(l) => lane::traffic(ctx, app, &mut details, l), + _ => panic!("TODO"), }; + /* // Follow the agent. When the sim is paused, this lets the player naturally pan away, // because the InfoPanel isn't being updated. if let Some(pt) = id @@ -279,13 +233,11 @@ impl InfoPanel { .and_then(|a| app.primary.sim.canonical_pt_for_agent(a, &app.primary.map)) { ctx.canvas.center_on_map_pt(pt); - } + }*/ InfoPanel { - id, tab, actions, - trip_details, time: app.primary.sim.time(), composite: Composite::new(Widget::col(col).bg(colors::PANEL_BG).padding(10)) .aligned( @@ -294,9 +246,10 @@ impl InfoPanel { ) .max_size_percent(35, 60) .build(ctx), - also_draw: batch.upload(ctx), - hyperlinks, - warpers, + unzoomed: details.unzoomed.upload(ctx), + zoomed: details.zoomed.upload(ctx), + hyperlinks: details.hyperlinks, + warpers: details.warpers, } } @@ -317,86 +270,17 @@ impl InfoPanel { // Live update? if app.primary.sim.time() != self.time { - if let Some(a) = self.id.agent_id() { - if let Some(ref details) = self.trip_details { - match app.primary.sim.trip_to_agent(details.id) { - TripResult::Ok(a2) => { - if a != a2 { - if !app.primary.sim.does_agent_exist(a) { - *self = InfoPanel::new( - ID::from_agent(a2), - InfoTab::Nil, - ctx, - app, - Vec::new(), - maybe_speed, - ); - return ( - false, - Some(Transition::Push(msg( - "The trip is transitioning to a new mode", - vec![format!( - "{} is now {}, following them instead", - agent_name(a), - agent_name(a2) - )], - ))), - ); - } - - return (true, Some(Transition::Push(trip_transition(a, a2)))); - } - } - TripResult::TripDone => { - *self = InfoPanel::new( - ID::Trip(details.id), - InfoTab::Nil, - ctx, - app, - Vec::new(), - maybe_speed, - ); - return ( - false, - Some(Transition::Push(msg( - "Trip complete", - vec![format!( - "{} has finished their trip. Say goodbye!", - agent_name(a) - )], - ))), - ); - } - TripResult::TripNotStarted | TripResult::TripDoesntExist => unreachable!(), - // Just wait a moment for trip_transition to kick in... - TripResult::ModeChange => {} - } - } - } - // TODO Detect crowds changing here maybe - - let preserve_scroll = self.composite.preserve_scroll(); - *self = InfoPanel::new( - self.id.clone(), - self.tab.clone(), - ctx, - app, - self.actions.clone(), - maybe_speed, - ); - self.composite.restore_scroll(ctx, preserve_scroll); - return (false, None); + // TODO } match self.composite.event(ctx) { Some(Outcome::Clicked(action)) => { - if let Some((new_id, tab)) = self.hyperlinks.get(&action).cloned() { + if let Some(new_tab) = self.hyperlinks.get(&action).cloned() { *self = InfoPanel::new( - new_id.clone(), - tab, ctx, app, - if self.id == new_id { + new_tab.clone(), + if self.tab == new_tab { self.actions.clone() } else { Vec::new() @@ -407,7 +291,7 @@ impl InfoPanel { } else if action == "close info" { (true, None) } else if action == "jump to object" { - ( + /*( false, Some(Transition::Push(Warping::new( ctx, @@ -416,7 +300,8 @@ impl InfoPanel { Some(self.id.clone()), &mut app.primary, ))), - ) + )*/ + (false, None) } else if action == "follow agent" { maybe_speed.unwrap().resume_realtime(ctx); (false, None) @@ -434,20 +319,10 @@ impl InfoPanel { &mut app.primary, ))), ) - } else if action == "Info" { - // Genericish - *self = InfoPanel::new( - self.id.clone(), - InfoTab::Nil, - ctx, - app, - self.actions.clone(), - maybe_speed, - ); - return (false, None); } else { - app.primary.current_selection = Some(self.id.clone()); - (true, Some(Transition::ApplyObjectAction(action))) + /*app.primary.current_selection = Some(self.id.clone()); + (true, Some(Transition::ApplyObjectAction(action)))*/ + (false, None) } } None => (false, None), @@ -456,14 +331,11 @@ impl InfoPanel { pub fn draw(&self, g: &mut GfxCtx) { self.composite.draw(g); - if let Some(ref details) = self.trip_details { - if g.canvas.cam_zoom < MIN_ZOOM_FOR_DETAIL { - g.redraw(&details.unzoomed); - } else { - g.redraw(&details.zoomed); - } + if g.canvas.cam_zoom < MIN_ZOOM_FOR_DETAIL { + g.redraw(&self.unzoomed); + } else { + g.redraw(&self.zoomed); } - g.redraw(&self.also_draw); } } @@ -527,35 +399,6 @@ fn color_for_mode(m: TripMode, app: &App) -> Color { } } -fn trip_transition(from: AgentID, to: AgentID) -> Box { - WizardState::new(Box::new(move |wiz, ctx, _| { - let orig = format!("keep following {}", agent_name(from)); - let change = format!("follow {} instead", agent_name(to)); - - let id = if wiz - .wrap(ctx) - .choose_string("The trip is transitioning to a new mode", || { - vec![orig.clone(), change.clone()] - })? - == orig - { - ID::from_agent(from) - } else { - ID::from_agent(to) - }; - Some(Transition::PopWithData(Box::new(move |state, app, ctx| { - state - .downcast_mut::() - .unwrap() - .controls - .common - .as_mut() - .unwrap() - .launch_info_panel(id, ctx, app); - }))) - })) -} - fn agent_name(a: AgentID) -> String { match a { AgentID::Car(c) => match c.1 { @@ -569,17 +412,16 @@ fn agent_name(a: AgentID) -> String { fn make_tabs( ctx: &EventCtx, - hyperlinks: &mut HashMap, - id: ID, - current_tab: InfoTab, - tabs: Vec<(&str, InfoTab)>, + hyperlinks: &mut HashMap, + current_tab: Tab, + tabs: Vec<(&str, Tab)>, ) -> Widget { let mut row = Vec::new(); for (name, link) in tabs { if current_tab == link { row.push(Btn::text_bg2(name).inactive(ctx)); } else { - hyperlinks.insert(name.to_string(), (id.clone(), link)); + hyperlinks.insert(name.to_string(), link); row.push(Btn::text_bg2(name).build_def(ctx, None)); } } @@ -588,9 +430,9 @@ fn make_tabs( Widget::row(row).bg(Color::WHITE) } -fn make_browser (ID, InfoTab)>( +fn make_browser Tab>( ctx: &EventCtx, - hyperlinks: &mut HashMap, + hyperlinks: &mut HashMap, noun: &str, total: usize, idx: usize, @@ -616,3 +458,13 @@ fn make_browser (ID, InfoTab)>( ]) .centered() } + +fn header_btns(ctx: &EventCtx) -> Widget { + Widget::row(vec![ + Btn::svg_def("../data/system/assets/tools/location.svg") + .build(ctx, "jump to object", hotkey(Key::J)) + .margin(5), + Btn::text_fg("X").build(ctx, "close info", hotkey(Key::Escape)), + ]) + .align_right() +} diff --git a/sim/src/sim.rs b/sim/src/sim.rs index 30e14bb5e1..1f3cac5d40 100644 --- a/sim/src/sim.rs +++ b/sim/src/sim.rs @@ -961,6 +961,9 @@ impl Sim { pub fn trip_to_person(&self, id: TripID) -> PersonID { self.trips.trip_to_person(id) } + pub fn agent_to_person(&self, id: AgentID) -> Option { + self.agent_to_trip(id).map(|t| self.trip_to_person(t)) + } pub fn get_person(&self, id: PersonID) -> &Person { self.trips.get_person(id)