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}