game/edit/
crosswalks.rs

1use geom::Distance;
2use map_model::{IntersectionID, TurnID, TurnType};
3use widgetry::mapspace::{ObjectID, World, WorldOutcome};
4use widgetry::{
5    Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State, TextExt,
6    VerticalAlignment, Widget,
7};
8
9use crate::app::App;
10use crate::app::Transition;
11use crate::edit::apply_map_edits;
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14struct ID(TurnID);
15
16impl ObjectID for ID {}
17
18pub struct CrosswalkEditor {
19    id: IntersectionID,
20    world: World<ID>,
21    panel: Panel,
22}
23
24impl CrosswalkEditor {
25    pub fn new_state(ctx: &mut EventCtx, app: &mut App, id: IntersectionID) -> Box<dyn State<App>> {
26        app.primary.current_selection = None;
27
28        let map = &app.primary.map;
29        let mut world = World::new();
30        for turn in &map.get_i(id).turns {
31            if turn.turn_type.pedestrian_crossing() {
32                let width = Distance::meters(3.0);
33                let hitbox = if let Some(line) = turn.crosswalk_line() {
34                    line.make_polygons(width)
35                } else {
36                    turn.geom.make_polygons(width)
37                };
38                world
39                    .add(ID(turn.id))
40                    .hitbox(hitbox)
41                    .draw_color(Color::RED.alpha(0.5))
42                    .hover_alpha(0.3)
43                    .clickable()
44                    .build(ctx);
45            }
46        }
47
48        Box::new(Self {
49            id,
50            world,
51            panel: Panel::new_builder(Widget::col(vec![
52            Line("Crosswalks editor").small_heading().into_widget(ctx),
53            "Click a crosswalk to toggle it between marked and unmarked".text_widget(ctx),
54            Line("Pedestrians can cross using both, but have priority over vehicles at marked zebra crossings").secondary().into_widget(ctx),
55            ctx.style()
56                .btn_solid_primary
57                .text("Finish")
58                .hotkey(Key::Escape)
59                .build_def(ctx),
60        ]))
61        .aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
62        .build(ctx),
63        })
64    }
65}
66
67impl State<App> for CrosswalkEditor {
68    fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
69        if let WorldOutcome::ClickedObject(ID(turn)) = self.world.event(ctx) {
70            let mut edits = app.primary.map.get_edits().clone();
71            edits
72                .commands
73                .push(app.primary.map.edit_intersection_cmd(self.id, |new| {
74                    new.crosswalks.insert(
75                        turn,
76                        if new.crosswalks[&turn] == TurnType::Crosswalk {
77                            TurnType::UnmarkedCrossing
78                        } else {
79                            TurnType::Crosswalk
80                        },
81                    );
82                }));
83            apply_map_edits(ctx, app, edits);
84            return Transition::Replace(Self::new_state(ctx, app, self.id));
85        }
86
87        if let Outcome::Clicked(ref x) = self.panel.event(ctx) {
88            match x.as_ref() {
89                "Finish" => {
90                    return Transition::Pop;
91                }
92                _ => unreachable!(),
93            }
94        }
95
96        Transition::Keep
97    }
98
99    fn draw(&self, g: &mut GfxCtx, _: &App) {
100        self.panel.draw(g);
101        self.world.draw(g);
102    }
103}