game/sandbox/
misc_tools.rs

1use 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
15/// Draws a preview of the path for the agent under the mouse cursor.
16pub struct RoutePreview {
17    // (the agent we're hovering on, the sim time, whether we're zoomed in, the drawn path)
18    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                // Only draw the preview when zoomed in. If we wanted to do this unzoomed, we'd
45                // want a different style; the dashed lines don't show up well.
46                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
74// TODO Refactor with SignalPicker
75pub 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}