diff --git a/game/src/edit/bulk.rs b/game/src/edit/bulk.rs index 010f57da05..62d69a85f6 100644 --- a/game/src/edit/bulk.rs +++ b/game/src/edit/bulk.rs @@ -88,7 +88,7 @@ impl State for RouteSelect { let roads = if let Some(roads) = pathfind(&app.primary.map, i1, i2) { for r in &roads { batch.push( - Color::RED, + Color::RED.alpha(0.5), app.primary .map .get_r(*r) @@ -219,7 +219,7 @@ impl BulkEdit { .align_right(), ]) .margin_below(5), - Btn::text_fg("Quit").build_def(ctx, None), + Btn::text_fg("Quit").build_def(ctx, hotkey(Key::Escape)), ]) .bg(app.cs.panel_bg) .padding(10), @@ -374,7 +374,7 @@ impl State for PaintSelect { let mut batch = GeomBatch::new(); for r in &self.roads { batch.push( - Color::BLUE, + Color::BLUE.alpha(0.5), app.primary .map .get_r(*r) diff --git a/game/src/info/intersection.rs b/game/src/info/intersection.rs index 4d440ce9b6..75fe4d3c0f 100644 --- a/game/src/info/intersection.rs +++ b/game/src/info/intersection.rs @@ -1,7 +1,10 @@ use crate::app::App; use crate::info::{header_btns, make_tabs, throughput, DataOptions, Details, Tab}; use abstutil::prettyprint_usize; -use ezgui::{EventCtx, Line, LinePlot, PlotOptions, Series, Text, Widget}; +use ezgui::{ + Color, EventCtx, GeomBatch, Line, LinePlot, PlotOptions, RewriteColor, Series, Text, Widget, +}; +use geom::{Angle, ArrowCap, Distance, PolyLine}; use geom::{Duration, Statistic, Time}; use map_model::{IntersectionID, IntersectionType}; use sim::Analytics; @@ -96,6 +99,71 @@ pub fn delay( rows } +pub fn current_demand( + ctx: &mut EventCtx, + app: &App, + details: &mut Details, + id: IntersectionID, +) -> Vec { + let mut rows = header(ctx, app, details, id, Tab::IntersectionDemand(id)); + + let mut total_demand = 0; + let mut demand_per_group: Vec<(&PolyLine, usize)> = Vec::new(); + for g in app.primary.map.get_traffic_signal(id).turn_groups.values() { + let demand = app + .primary + .sim + .get_analytics() + .demand + .get(&g.id) + .cloned() + .unwrap_or(0); + if demand > 0 { + total_demand += demand; + demand_per_group.push((&g.geom, demand)); + } + } + + let mut batch = GeomBatch::new(); + let polygon = app.primary.map.get_i(id).polygon.clone(); + let bounds = polygon.get_bounds(); + // Pick a zoom so that we fit a fixed width in pixels + let zoom = 300.0 / bounds.width(); + batch.push(app.cs.normal_intersection, polygon); + + for (pl, demand) in demand_per_group { + let percent = (demand as f64) / (total_demand as f64); + batch.push( + Color::RED, + pl.make_arrow(percent * Distance::meters(3.0), ArrowCap::Triangle) + .unwrap(), + ); + batch.add_transformed( + Text::from(Line(prettyprint_usize(demand))).render_ctx(ctx), + pl.middle(), + 0.15, + Angle::ZERO, + RewriteColor::NoOp, + ); + } + let mut transformed_batch = GeomBatch::new(); + for (color, poly) in batch.consume() { + transformed_batch.fancy_push( + color, + poly.translate(-bounds.min_x, -bounds.min_y).scale(zoom), + ); + } + + let mut txt = Text::from(Line(format!( + "How many active trips will cross this intersection?" + ))); + txt.add(Line(format!("Total: {}", prettyprint_usize(total_demand))).secondary()); + rows.push(txt.draw(ctx)); + rows.push(Widget::draw_batch(ctx, transformed_batch)); + + rows +} + fn delay_plot(ctx: &EventCtx, app: &App, i: IntersectionID, opts: &DataOptions) -> Widget { let get_data = |a: &Analytics, t: Time| { let mut series: Vec<(Statistic, Vec<(Time, Duration)>)> = Statistic::all() @@ -173,6 +241,7 @@ fn header( ]; if i.is_traffic_signal() { tabs.push(("Delay", Tab::IntersectionDelay(id, DataOptions::new(app)))); + tabs.push(("Current demand", Tab::IntersectionDemand(id))); } tabs })); diff --git a/game/src/info/mod.rs b/game/src/info/mod.rs index 1f93ea2f17..f6097b1375 100644 --- a/game/src/info/mod.rs +++ b/game/src/info/mod.rs @@ -67,6 +67,7 @@ pub enum Tab { IntersectionInfo(IntersectionID), IntersectionTraffic(IntersectionID, DataOptions), IntersectionDelay(IntersectionID, DataOptions), + IntersectionDemand(IntersectionID), LaneInfo(LaneID), LaneDebug(LaneID), @@ -134,7 +135,8 @@ impl Tab { Tab::Area(a) => Some(ID::Area(a)), Tab::IntersectionInfo(i) | Tab::IntersectionTraffic(i, _) - | Tab::IntersectionDelay(i, _) => Some(ID::Intersection(i)), + | Tab::IntersectionDelay(i, _) + | Tab::IntersectionDemand(i) => Some(ID::Intersection(i)), Tab::LaneInfo(l) | Tab::LaneDebug(l) | Tab::LaneTraffic(l, _) => Some(ID::Lane(l)), } } @@ -209,6 +211,10 @@ impl InfoPanel { Tab::IntersectionDelay(i, ref opts) => { (intersection::delay(ctx, app, &mut details, i, opts), false) } + Tab::IntersectionDemand(i) => ( + intersection::current_demand(ctx, app, &mut details, i), + false, + ), Tab::LaneInfo(l) => (lane::info(ctx, app, &mut details, l), true), Tab::LaneDebug(l) => (lane::debug(ctx, app, &mut details, l), false), Tab::LaneTraffic(l, ref opts) => { diff --git a/game/src/layer/mod.rs b/game/src/layer/mod.rs index 0a7b5d92a9..e9e9307c47 100644 --- a/game/src/layer/mod.rs +++ b/game/src/layer/mod.rs @@ -28,9 +28,9 @@ pub trait Layer { fn draw_minimap(&self, g: &mut GfxCtx); } +// TODO Just return a bool for closed? Less readable... pub enum LayerOutcome { Close, - Transition(Transition), } pub struct PickLayer { @@ -50,10 +50,6 @@ impl PickLayer { app.layer = None; return None; } - Some(LayerOutcome::Transition(t)) => { - app.layer = Some(layer); - return Some(t); - } None => {} } app.layer = Some(layer); diff --git a/game/src/layer/traffic.rs b/game/src/layer/traffic.rs index 2638cfe35e..a806a725f5 100644 --- a/game/src/layer/traffic.rs +++ b/game/src/layer/traffic.rs @@ -1,16 +1,13 @@ use crate::app::App; -use crate::common::{ColorLegend, Colorer, Warping}; -use crate::game::Transition; -use crate::helpers::ID; +use crate::common::{ColorLegend, Colorer}; use crate::layer::{Layer, LayerOutcome}; -use abstutil::{prettyprint_usize, Counter}; +use abstutil::Counter; use ezgui::{ - hotkey, Btn, Checkbox, Color, Composite, Drawable, EventCtx, GeomBatch, GfxCtx, - HorizontalAlignment, Key, Line, Outcome, RewriteColor, Text, TextExt, VerticalAlignment, - Widget, + hotkey, Btn, Checkbox, Color, Composite, Drawable, EventCtx, GfxCtx, HorizontalAlignment, Key, + Outcome, TextExt, VerticalAlignment, Widget, }; -use geom::{Angle, ArrowCap, Distance, Duration, PolyLine, Time}; -use map_model::{IntersectionID, Traversable}; +use geom::{Duration, Time}; +use map_model::Traversable; pub struct Dynamic { time: Time, @@ -465,113 +462,3 @@ impl Throughput { } } } - -// TODO Does this make sense as a layer? Should it be an info panel tab? -pub struct IntersectionDemand { - time: Time, - i: IntersectionID, - draw: Drawable, - composite: Composite, -} - -impl Layer for IntersectionDemand { - fn name(&self) -> Option<&'static str> { - None - } - fn event( - &mut self, - ctx: &mut EventCtx, - app: &mut App, - minimap: &Composite, - ) -> Option { - if app.primary.sim.time() != self.time { - *self = IntersectionDemand::new(ctx, app, self.i); - } - - self.composite.align_above(ctx, minimap); - match self.composite.event(ctx) { - Some(Outcome::Clicked(x)) => match x.as_ref() { - "intersection demand" => { - let id = ID::Intersection(self.i); - return Some(LayerOutcome::Transition(Transition::Push(Warping::new( - ctx, - id.canonical_point(&app.primary).unwrap(), - Some(10.0), - Some(id.clone()), - &mut app.primary, - )))); - } - "X" => { - return Some(LayerOutcome::Close); - } - _ => unreachable!(), - }, - None => {} - } - None - } - fn draw(&self, g: &mut GfxCtx, _: &App) { - self.composite.draw(g); - g.redraw(&self.draw); - } - fn draw_minimap(&self, _: &mut GfxCtx) {} -} - -impl IntersectionDemand { - pub fn new(ctx: &mut EventCtx, app: &App, i: IntersectionID) -> IntersectionDemand { - let mut batch = GeomBatch::new(); - - let mut total_demand = 0; - let mut demand_per_group: Vec<(&PolyLine, usize)> = Vec::new(); - for g in app.primary.map.get_traffic_signal(i).turn_groups.values() { - let demand = app - .primary - .sim - .get_analytics() - .demand - .get(&g.id) - .cloned() - .unwrap_or(0); - if demand > 0 { - total_demand += demand; - demand_per_group.push((&g.geom, demand)); - } - } - - for (pl, demand) in demand_per_group { - let percent = (demand as f64) / (total_demand as f64); - batch.push( - Color::RED, - pl.make_arrow(percent * Distance::meters(5.0), ArrowCap::Triangle) - .unwrap(), - ); - batch.add_transformed( - Text::from(Line(prettyprint_usize(demand))).render_ctx(ctx), - pl.middle(), - 0.08, - Angle::ZERO, - RewriteColor::NoOp, - ); - } - - let col = vec![ - Widget::row(vec![ - "intersection demand".draw_text(ctx), - Btn::svg_def("../data/system/assets/tools/location.svg") - .build(ctx, "intersection demand", None) - .margin(5), - Btn::text_fg("X").build_def(ctx, None).align_right(), - ]), - ColorLegend::row(ctx, Color::RED, "current demand"), - ]; - - IntersectionDemand { - time: app.primary.sim.time(), - i, - draw: batch.upload(ctx), - composite: Composite::new(Widget::col(col).bg(app.cs.panel_bg)) - .aligned(HorizontalAlignment::Right, VerticalAlignment::Center) - .build(ctx), - } - } -} diff --git a/game/src/sandbox/mod.rs b/game/src/sandbox/mod.rs index 937b93b8dc..5fd2fcf1ac 100644 --- a/game/src/sandbox/mod.rs +++ b/game/src/sandbox/mod.rs @@ -383,7 +383,6 @@ impl ContextualActions for Actions { ID::Intersection(i) => { if app.primary.map.get_i(i).is_traffic_signal() { actions.push((Key::F, "explore traffic signal details".to_string())); - actions.push((Key::C, "show current demand".to_string())); actions.push((Key::E, "edit traffic signal".to_string())); } if app.primary.map.get_i(i).is_stop_sign() { @@ -423,12 +422,6 @@ impl ContextualActions for Actions { (ID::Intersection(i), "explore traffic signal details") => { Transition::Push(ShowTrafficSignal::new(ctx, app, i)) } - (ID::Intersection(i), "show current demand") => { - app.layer = Some(Box::new(crate::layer::traffic::IntersectionDemand::new( - ctx, app, i, - ))); - Transition::Keep - } (ID::Intersection(i), "edit traffic signal") => Transition::PushTwice( Box::new(EditMode::new(ctx, app, self.gameplay.clone())), Box::new(TrafficSignalEditor::new(i, ctx, app)),