game/sandbox/
misc_tools.rs1use std::collections::BTreeSet;
2
3use crate::ID;
4use geom::{Distance, Time};
5use map_model::IntersectionID;
6use sim::AgentID;
7use widgetry::{
8 Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel,
9 State, VerticalAlignment, Widget,
10};
11
12use crate::app::{App, Transition};
13use crate::common::CommonState;
14
15pub struct RoutePreview {
17 preview: Option<(AgentID, Time, bool, Drawable)>,
19}
20
21impl RoutePreview {
22 pub fn new() -> RoutePreview {
23 RoutePreview { preview: None }
24 }
25}
26
27impl RoutePreview {
28 pub fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Option<Transition> {
29 if let Some(agent) = app
30 .primary
31 .current_selection
32 .as_ref()
33 .and_then(|id| id.agent_id())
34 {
35 let now = app.primary.sim.time();
36 let zoomed = ctx.canvas.is_zoomed();
37 if self
38 .preview
39 .as_ref()
40 .map(|(a, t, z, _)| agent != *a || now != *t || zoomed != *z)
41 .unwrap_or(true)
42 {
43 let mut batch = GeomBatch::new();
44 if zoomed {
47 if let Some(trace) = app.primary.sim.trace_route(agent, &app.primary.map) {
48 batch.extend(
49 app.cs.route,
50 trace.dashed_lines(
51 Distance::meters(0.75),
52 Distance::meters(1.0),
53 Distance::meters(0.4),
54 ),
55 );
56 }
57 }
58 self.preview = Some((agent, now, zoomed, batch.upload(ctx)));
59 }
60 return None;
61 }
62 self.preview = None;
63
64 None
65 }
66
67 pub fn draw(&self, g: &mut GfxCtx) {
68 if let Some((_, _, _, ref d)) = self.preview {
69 g.redraw(d);
70 }
71 }
72}
73
74pub struct TrafficRecorder {
76 members: BTreeSet<IntersectionID>,
77 panel: Panel,
78}
79
80impl TrafficRecorder {
81 pub fn new_state(ctx: &mut EventCtx, members: BTreeSet<IntersectionID>) -> Box<dyn State<App>> {
82 Box::new(TrafficRecorder {
83 panel: Panel::new_builder(Widget::col(vec![
84 Widget::row(vec![
85 Line("Select the bounding intersections for recording traffic")
86 .small_heading()
87 .into_widget(ctx),
88 ctx.style().btn_close_widget(ctx),
89 ]),
90 make_btn(ctx, members.len()),
91 ]))
92 .aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
93 .build(ctx),
94 members,
95 })
96 }
97}
98
99impl State<App> for TrafficRecorder {
100 fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
101 ctx.canvas_movement();
102 if ctx.redo_mouseover() {
103 app.primary.current_selection = app.mouseover_unzoomed_intersections(ctx);
104 }
105 if let Some(ID::Intersection(i)) = app.primary.current_selection {
106 if !self.members.contains(&i) && app.per_obj.left_click(ctx, "add this intersection") {
107 self.members.insert(i);
108 let btn = make_btn(ctx, self.members.len());
109 self.panel.replace(ctx, "record", btn);
110 } else if self.members.contains(&i)
111 && app.per_obj.left_click(ctx, "remove this intersection")
112 {
113 self.members.remove(&i);
114 let btn = make_btn(ctx, self.members.len());
115 self.panel.replace(ctx, "record", btn);
116 }
117 }
118
119 if let Outcome::Clicked(x) = self.panel.event(ctx) {
120 match x.as_ref() {
121 "close" => {
122 return Transition::Pop;
123 }
124 "record" => {
125 app.primary.sim.record_traffic_for(self.members.clone());
126 return Transition::Pop;
127 }
128 _ => unreachable!(),
129 }
130 }
131
132 Transition::Keep
133 }
134
135 fn draw(&self, g: &mut GfxCtx, app: &App) {
136 self.panel.draw(g);
137 CommonState::draw_osd(g, app);
138
139 let mut batch = GeomBatch::new();
140 for i in &self.members {
141 batch.push(
142 Color::RED.alpha(0.8),
143 app.primary.map.get_i(*i).polygon.clone(),
144 );
145 }
146 let draw = g.upload(batch);
147 g.redraw(&draw);
148 }
149}
150
151fn make_btn(ctx: &mut EventCtx, num: usize) -> Widget {
152 let title = match num {
153 0 => "Record 0 intersections".to_string(),
154 1 => "Record 1 intersection".to_string(),
155 _ => format!("Record {} intersections", num),
156 };
157 ctx.style()
158 .btn_solid_primary
159 .text(title)
160 .disabled(num == 0)
161 .hotkey(Key::Enter)
162 .build_widget(ctx, "record")
163}