game/edit/traffic_signals/
picker.rs

1use std::collections::BTreeSet;
2
3use crate::ID;
4use map_model::IntersectionID;
5use widgetry::{
6    hotkeys, Color, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel,
7    State, VerticalAlignment, Widget,
8};
9
10use crate::app::App;
11use crate::app::Transition;
12use crate::common::CommonState;
13use crate::edit::TrafficSignalEditor;
14use crate::sandbox::gameplay::GameplayMode;
15
16pub struct SignalPicker {
17    members: BTreeSet<IntersectionID>,
18    panel: Panel,
19    mode: GameplayMode,
20}
21
22impl SignalPicker {
23    pub fn new_state(
24        ctx: &mut EventCtx,
25        members: BTreeSet<IntersectionID>,
26        mode: GameplayMode,
27    ) -> Box<dyn State<App>> {
28        Box::new(SignalPicker {
29            panel: Panel::new_builder(Widget::col(vec![
30                Widget::row(vec![
31                    Line("Select multiple traffic signals")
32                        .small_heading()
33                        .into_widget(ctx),
34                    ctx.style().btn_close_widget(ctx),
35                ]),
36                make_btn(ctx, members.len()),
37            ]))
38            .aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
39            .build(ctx),
40            members,
41            mode,
42        })
43    }
44}
45
46impl State<App> for SignalPicker {
47    fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
48        ctx.canvas_movement();
49        if ctx.redo_mouseover() {
50            app.primary.current_selection =
51                app.mouseover_unzoomed_intersections(ctx).filter(|id| {
52                    app.primary
53                        .map
54                        .maybe_get_traffic_signal(id.as_intersection())
55                        .is_some()
56                });
57        }
58        if let Some(ID::Intersection(i)) = app.primary.current_selection {
59            if !self.members.contains(&i) && app.per_obj.left_click(ctx, "add this intersection") {
60                self.members.insert(i);
61                let btn = make_btn(ctx, self.members.len());
62                self.panel.replace(ctx, "edit", btn);
63            } else if self.members.contains(&i)
64                && app.per_obj.left_click(ctx, "remove this intersection")
65            {
66                self.members.remove(&i);
67                let btn = make_btn(ctx, self.members.len());
68                self.panel.replace(ctx, "edit", btn);
69            }
70        }
71
72        if let Outcome::Clicked(x) = self.panel.event(ctx) {
73            match x.as_ref() {
74                "close" => {
75                    return Transition::Pop;
76                }
77                "edit" => {
78                    return Transition::Replace(TrafficSignalEditor::new_state(
79                        ctx,
80                        app,
81                        self.members.clone(),
82                        self.mode.clone(),
83                    ));
84                }
85                _ => unreachable!(),
86            }
87        }
88
89        Transition::Keep
90    }
91
92    fn draw(&self, g: &mut GfxCtx, app: &App) {
93        self.panel.draw(g);
94        CommonState::draw_osd(g, app);
95
96        let mut batch = GeomBatch::new();
97        for i in &self.members {
98            batch.push(
99                Color::RED.alpha(0.8),
100                app.primary.map.get_i(*i).polygon.clone(),
101            );
102        }
103        let draw = g.upload(batch);
104        g.redraw(&draw);
105    }
106}
107
108fn make_btn(ctx: &mut EventCtx, num: usize) -> Widget {
109    let title = match num {
110        0 => "Edit 0 signals".to_string(),
111        1 => "Edit 1 signal".to_string(),
112        _ => format!("Edit {} signals", num),
113    };
114    ctx.style()
115        .btn_solid_primary
116        .text(title)
117        .disabled(num == 0)
118        .hotkey(hotkeys(vec![Key::Enter, Key::E]))
119        .build_widget(ctx, "edit")
120}