mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-28 12:12:00 +03:00
add border stop/start to route info panel. move the route drawing there, stop being a weird layer.
This commit is contained in:
parent
4c86f568d0
commit
a4e09ca91f
@ -239,6 +239,16 @@ impl<'a> ColorNetwork<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_l(&mut self, l: LaneID, color: Color) {
|
||||
self.unzoomed
|
||||
.push(color, self.map.get_parent(l).get_thick_polygon(self.map));
|
||||
let lane = self.map.get_l(l);
|
||||
self.zoomed.push(
|
||||
color.alpha(0.4),
|
||||
lane.lane_center_pts.make_polygons(lane.width),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn add_r(&mut self, r: RoadID, color: Color) {
|
||||
self.unzoomed
|
||||
.push(color, self.map.get_r(r).get_thick_polygon(self.map));
|
||||
|
@ -1,10 +1,11 @@
|
||||
use crate::app::App;
|
||||
use crate::common::ColorNetwork;
|
||||
use crate::helpers::ID;
|
||||
use crate::info::{header_btns, make_tabs, Details, Tab};
|
||||
use abstutil::{prettyprint_usize, Counter};
|
||||
use ezgui::{Btn, Color, EventCtx, Line, RewriteColor, Text, TextExt, Widget};
|
||||
use geom::{Circle, Distance, Time};
|
||||
use map_model::{BusRouteID, BusStopID, PathConstraints};
|
||||
use map_model::{BusRouteID, BusStopID, PathConstraints, PathStep};
|
||||
use sim::{AgentID, CarID};
|
||||
|
||||
pub fn stop(ctx: &mut EventCtx, app: &App, details: &mut Details, id: BusStopID) -> Vec<Widget> {
|
||||
@ -21,7 +22,7 @@ pub fn stop(ctx: &mut EventCtx, app: &App, details: &mut Details, id: BusStopID)
|
||||
|
||||
let all_arrivals = &sim.get_analytics().bus_arrivals;
|
||||
for r in app.primary.map.get_routes_serving_stop(id) {
|
||||
let buses = app.primary.sim.status_of_buses(r.id);
|
||||
let buses = app.primary.sim.status_of_buses(r.id, &app.primary.map);
|
||||
if buses.is_empty() {
|
||||
rows.push(format!("Route {}: no buses running", r.short_name).draw_text(ctx));
|
||||
} else {
|
||||
@ -164,7 +165,8 @@ fn bus_header(
|
||||
}
|
||||
|
||||
pub fn route(ctx: &mut EventCtx, app: &App, details: &mut Details, id: BusRouteID) -> Vec<Widget> {
|
||||
let route = app.primary.map.get_br(id);
|
||||
let map = &app.primary.map;
|
||||
let route = map.get_br(id);
|
||||
let mut rows = vec![];
|
||||
|
||||
rows.push(Widget::row(vec![
|
||||
@ -179,7 +181,8 @@ pub fn route(ctx: &mut EventCtx, app: &App, details: &mut Details, id: BusRouteI
|
||||
.draw(ctx),
|
||||
);
|
||||
|
||||
let buses = app.primary.sim.status_of_buses(id);
|
||||
let buses = app.primary.sim.status_of_buses(id, map);
|
||||
let mut bus_locations = Vec::new();
|
||||
if buses.is_empty() {
|
||||
if route.route_type == PathConstraints::Bus {
|
||||
rows.push("No buses running".draw_text(ctx));
|
||||
@ -187,11 +190,12 @@ pub fn route(ctx: &mut EventCtx, app: &App, details: &mut Details, id: BusRouteI
|
||||
rows.push("No trains running".draw_text(ctx));
|
||||
}
|
||||
} else {
|
||||
for (bus, _, _) in buses {
|
||||
for (bus, _, _, pt) in buses {
|
||||
rows.push(Btn::text_fg(bus.to_string()).build_def(ctx, None));
|
||||
details
|
||||
.hyperlinks
|
||||
.insert(bus.to_string(), Tab::BusStatus(bus));
|
||||
bus_locations.push(pt);
|
||||
}
|
||||
}
|
||||
|
||||
@ -228,8 +232,21 @@ pub fn route(ctx: &mut EventCtx, app: &App, details: &mut Details, id: BusRouteI
|
||||
);
|
||||
|
||||
rows.push(format!("{} stops", route.stops.len()).draw_text(ctx));
|
||||
if let Some(l) = route.start_border {
|
||||
let i = map.get_i(map.get_l(l).src_i);
|
||||
let name = format!("Starts at {}", i.name(map));
|
||||
rows.push(Widget::row(vec![
|
||||
Btn::svg(
|
||||
"system/assets/timeline/goal_pos.svg",
|
||||
RewriteColor::Change(Color::WHITE, app.cs.hovering),
|
||||
)
|
||||
.build(ctx, &name, None),
|
||||
name.clone().draw_text(ctx),
|
||||
]));
|
||||
details.warpers.insert(name, ID::Intersection(i.id));
|
||||
}
|
||||
for (idx, bs) in route.stops.iter().enumerate() {
|
||||
let bs = app.primary.map.get_bs(*bs);
|
||||
let bs = map.get_bs(*bs);
|
||||
let name = format!("Stop {}: {}", idx + 1, bs.name);
|
||||
rows.push(Widget::row(vec![
|
||||
Btn::svg(
|
||||
@ -250,6 +267,61 @@ pub fn route(ctx: &mut EventCtx, app: &App, details: &mut Details, id: BusRouteI
|
||||
]));
|
||||
details.warpers.insert(name, ID::BusStop(bs.id));
|
||||
}
|
||||
if let Some(l) = route.end_border {
|
||||
let i = map.get_i(map.get_l(l).dst_i);
|
||||
let name = format!("Ends at {}", i.name(map));
|
||||
rows.push(Widget::row(vec![
|
||||
Btn::svg(
|
||||
"system/assets/timeline/goal_pos.svg",
|
||||
RewriteColor::Change(Color::WHITE, app.cs.hovering),
|
||||
)
|
||||
.build(ctx, &name, None),
|
||||
name.clone().draw_text(ctx),
|
||||
]));
|
||||
details.warpers.insert(name, ID::Intersection(i.id));
|
||||
}
|
||||
|
||||
// Draw the route, label stops, and show location of buses
|
||||
{
|
||||
let mut colorer = ColorNetwork::new(app);
|
||||
for req in route.all_steps(map) {
|
||||
for step in map.pathfind(req).unwrap().get_steps() {
|
||||
if let PathStep::Lane(l) = step {
|
||||
colorer.add_l(*l, app.cs.unzoomed_bus);
|
||||
}
|
||||
}
|
||||
}
|
||||
details.unzoomed.append(colorer.unzoomed);
|
||||
details.zoomed.append(colorer.zoomed);
|
||||
|
||||
for pt in bus_locations {
|
||||
details.unzoomed.push(
|
||||
Color::BLUE,
|
||||
Circle::new(pt, Distance::meters(20.0)).to_polygon(),
|
||||
);
|
||||
details.zoomed.push(
|
||||
Color::BLUE.alpha(0.5),
|
||||
Circle::new(pt, Distance::meters(5.0)).to_polygon(),
|
||||
);
|
||||
}
|
||||
|
||||
for bs in &route.stops {
|
||||
let bs = map.get_bs(*bs);
|
||||
details.unzoomed.append(
|
||||
Text::from(Line(&bs.name))
|
||||
.with_bg()
|
||||
.render_to_batch(ctx.prerender)
|
||||
.centered_on(bs.sidewalk_pos.pt(map)),
|
||||
);
|
||||
details.zoomed.append(
|
||||
Text::from(Line(&bs.name))
|
||||
.with_bg()
|
||||
.render_to_batch(ctx.prerender)
|
||||
.scale(0.1)
|
||||
.centered_on(bs.sidewalk_pos.pt(map)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
rows
|
||||
}
|
||||
|
@ -2,11 +2,10 @@ use crate::app::App;
|
||||
use crate::common::ColorDiscrete;
|
||||
use crate::layer::{Layer, LayerOutcome};
|
||||
use ezgui::{
|
||||
hotkey, Btn, Checkbox, Color, Composite, Drawable, EventCtx, GeomBatch, GfxCtx,
|
||||
HorizontalAlignment, Key, Line, Outcome, Text, TextExt, VerticalAlignment, Widget,
|
||||
hotkey, Btn, Checkbox, Composite, Drawable, EventCtx, GfxCtx, HorizontalAlignment, Key,
|
||||
Outcome, TextExt, VerticalAlignment, Widget,
|
||||
};
|
||||
use geom::{Circle, Distance, Pt2D, Time};
|
||||
use map_model::{BusRouteID, PathConstraints, PathStep};
|
||||
use map_model::{PathConstraints, PathStep};
|
||||
|
||||
pub struct TransitNetwork {
|
||||
composite: Composite,
|
||||
@ -149,132 +148,3 @@ impl TransitNetwork {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO This maybe shouldn't be a layer
|
||||
pub struct ShowTransitRoute {
|
||||
time: Time,
|
||||
route: BusRouteID,
|
||||
labels: Vec<(Text, Pt2D)>,
|
||||
bus_locations: Vec<Pt2D>,
|
||||
|
||||
composite: Composite,
|
||||
unzoomed: Drawable,
|
||||
zoomed: Drawable,
|
||||
}
|
||||
|
||||
impl Layer for ShowTransitRoute {
|
||||
fn name(&self) -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
fn event(
|
||||
&mut self,
|
||||
ctx: &mut EventCtx,
|
||||
app: &mut App,
|
||||
minimap: &Composite,
|
||||
) -> Option<LayerOutcome> {
|
||||
if app.primary.sim.time() != self.time {
|
||||
*self = ShowTransitRoute::new(ctx, app, self.route);
|
||||
}
|
||||
|
||||
Layer::simple_event(ctx, minimap, &mut self.composite)
|
||||
}
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
if g.canvas.cam_zoom < app.opts.min_zoom_for_detail {
|
||||
g.redraw(&self.unzoomed);
|
||||
} else {
|
||||
g.redraw(&self.zoomed);
|
||||
}
|
||||
self.composite.draw(g);
|
||||
|
||||
// TODO Do this once
|
||||
let mut screen_batch = GeomBatch::new();
|
||||
for (label, pt) in &self.labels {
|
||||
screen_batch.append(
|
||||
label
|
||||
.clone()
|
||||
.render_g(g)
|
||||
.centered_on(g.canvas.map_to_screen(*pt).to_pt()),
|
||||
);
|
||||
}
|
||||
let draw = g.upload(screen_batch);
|
||||
g.fork_screenspace();
|
||||
g.redraw(&draw);
|
||||
g.unfork();
|
||||
|
||||
let mut batch = GeomBatch::new();
|
||||
let radius = Distance::meters(20.0) / g.canvas.cam_zoom;
|
||||
for pt in &self.bus_locations {
|
||||
batch.push(Color::BLUE, Circle::new(*pt, radius).to_polygon());
|
||||
}
|
||||
batch.draw(g);
|
||||
}
|
||||
fn draw_minimap(&self, g: &mut GfxCtx) {
|
||||
g.redraw(&self.unzoomed);
|
||||
}
|
||||
}
|
||||
|
||||
impl ShowTransitRoute {
|
||||
pub fn new(ctx: &mut EventCtx, app: &App, id: BusRouteID) -> ShowTransitRoute {
|
||||
let map = &app.primary.map;
|
||||
let route = app.primary.map.get_br(id);
|
||||
|
||||
let mut bus_locations = Vec::new();
|
||||
for (_, pt) in app.primary.sim.location_of_buses(id, map) {
|
||||
bus_locations.push(pt);
|
||||
}
|
||||
|
||||
let mut categories = vec![("route", app.cs.unzoomed_bus)];
|
||||
if route.start_border.is_some() {
|
||||
categories.push(("start", Color::RED));
|
||||
}
|
||||
if route.end_border.is_some() {
|
||||
categories.push(("end", Color::GREEN));
|
||||
}
|
||||
let mut colorer = ColorDiscrete::new(app, categories);
|
||||
if let Some(l) = route.start_border {
|
||||
colorer.add_i(map.get_l(l).src_i, "start");
|
||||
}
|
||||
if let Some(l) = route.end_border {
|
||||
colorer.add_i(map.get_l(l).dst_i, "end");
|
||||
}
|
||||
for req in route.all_steps(map) {
|
||||
for step in map.pathfind(req).unwrap().get_steps() {
|
||||
if let PathStep::Lane(l) = step {
|
||||
colorer.add_l(*l, "route");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut labels = Vec::new();
|
||||
for bs in &route.stops {
|
||||
let bs = map.get_bs(*bs);
|
||||
labels.push((
|
||||
Text::from(Line(&bs.name)).with_bg(),
|
||||
bs.sidewalk_pos.pt(map),
|
||||
));
|
||||
}
|
||||
|
||||
let (unzoomed, zoomed, legend) = colorer.build(ctx);
|
||||
ShowTransitRoute {
|
||||
time: app.primary.sim.time(),
|
||||
route: id,
|
||||
labels,
|
||||
unzoomed,
|
||||
zoomed,
|
||||
composite: Composite::new(Widget::col(vec![
|
||||
Widget::row(vec![
|
||||
Widget::draw_svg(ctx, "system/assets/tools/layers.svg"),
|
||||
Line(&route.full_name).draw(ctx),
|
||||
Btn::plaintext("X")
|
||||
.build(ctx, "close", hotkey(Key::Escape))
|
||||
.align_right(),
|
||||
]),
|
||||
format!("{} buses", bus_locations.len()).draw_text(ctx),
|
||||
legend,
|
||||
]))
|
||||
.aligned(HorizontalAlignment::Right, VerticalAlignment::Center)
|
||||
.build(ctx),
|
||||
bus_locations,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ use ezgui::{
|
||||
pub use gameplay::{spawn_agents_around, GameplayMode, TutorialPointer, TutorialState};
|
||||
use geom::{Polygon, Time};
|
||||
use map_model::MapEdits;
|
||||
use sim::{AgentType, VehicleType};
|
||||
use sim::AgentType;
|
||||
pub use speed::TimeWarpScreen;
|
||||
pub use speed::{SpeedControls, TimePanel};
|
||||
|
||||
@ -423,12 +423,6 @@ impl ContextualActions for Actions {
|
||||
actions.push((Key::E, "edit lane".to_string()));
|
||||
}
|
||||
}
|
||||
ID::Car(c) => {
|
||||
if c.1 == VehicleType::Bus || c.1 == VehicleType::Train {
|
||||
// TODO Hide the button if the layer is open
|
||||
actions.push((Key::R, "show route".to_string()));
|
||||
}
|
||||
}
|
||||
ID::Building(_) => {
|
||||
if app.opts.dev {
|
||||
actions.push((Key::I, "explore isochrone from here".to_string()));
|
||||
@ -470,15 +464,6 @@ impl ContextualActions for Actions {
|
||||
Box::new(EditMode::new(ctx, app, self.gameplay.clone())),
|
||||
Box::new(LaneEditor::new(ctx, app, l, self.gameplay.clone())),
|
||||
),
|
||||
(ID::Car(c), "show route") => {
|
||||
*close_panel = false;
|
||||
app.layer = Some(Box::new(crate::layer::transit::ShowTransitRoute::new(
|
||||
ctx,
|
||||
app,
|
||||
app.primary.sim.bus_route_id(c).unwrap(),
|
||||
)));
|
||||
Transition::Keep
|
||||
}
|
||||
(ID::Building(b), "explore isochrone from here") => {
|
||||
Transition::Push(IsochroneViewer::new(ctx, app, b))
|
||||
}
|
||||
|
@ -1069,23 +1069,20 @@ impl Sim {
|
||||
self.intersections.get_blocked_by(a)
|
||||
}
|
||||
|
||||
pub fn location_of_buses(&self, route: BusRouteID, map: &Map) -> Vec<(CarID, Pt2D)> {
|
||||
let mut results = Vec::new();
|
||||
for (car, _) in self.transit.buses_for_route(route) {
|
||||
// TODO This is a slow, indirect method!
|
||||
results.push((
|
||||
car,
|
||||
self.canonical_pt_for_agent(AgentID::Car(car), map).unwrap(),
|
||||
));
|
||||
}
|
||||
results
|
||||
}
|
||||
|
||||
// (bus, stop index it's coming from, percent to next stop)
|
||||
pub fn status_of_buses(&self, route: BusRouteID) -> Vec<(CarID, Option<usize>, f64)> {
|
||||
// (bus, stop index it's coming from, percent to next stop, location)
|
||||
pub fn status_of_buses(
|
||||
&self,
|
||||
route: BusRouteID,
|
||||
map: &Map,
|
||||
) -> Vec<(CarID, Option<usize>, f64, Pt2D)> {
|
||||
let mut results = Vec::new();
|
||||
for (bus, stop_idx) in self.transit.buses_for_route(route) {
|
||||
results.push((bus, stop_idx, self.driving.percent_along_route(bus)));
|
||||
results.push((
|
||||
bus,
|
||||
stop_idx,
|
||||
self.driving.percent_along_route(bus),
|
||||
self.canonical_pt_for_agent(AgentID::Car(bus), map).unwrap(),
|
||||
));
|
||||
}
|
||||
results
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user