map_editor/
edit.rs

1use abstutil::Tags;
2use geom::{ArrowCap, Distance};
3use osm2streets::RoadID;
4use widgetry::{
5    Choice, Color, DrawBaselayer, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key,
6    Line, Panel, SimpleState, Spinner, State, Text, TextExt, Transition, VerticalAlignment, Widget,
7};
8
9use crate::App;
10
11pub struct EditRoad {
12    r: RoadID,
13    show_direction: Drawable,
14}
15
16impl EditRoad {
17    pub(crate) fn new_state(ctx: &mut EventCtx, app: &App, r: RoadID) -> Box<dyn State<App>> {
18        let road = &app.model.map.streets.roads[&r];
19
20        let mut batch = GeomBatch::new();
21        if app.model.intersection_geom {
22            batch.push(
23                Color::BLACK,
24                road.center_line
25                    .make_arrow(Distance::meters(1.0), ArrowCap::Triangle),
26            );
27        } else {
28            batch.push(
29                Color::BLACK,
30                road.reference_line
31                    .make_arrow(Distance::meters(1.0), ArrowCap::Triangle),
32            );
33        }
34
35        let tags = app
36            .model
37            .map
38            .road_to_osm_tags(r)
39            .cloned()
40            .unwrap_or_else(Tags::empty);
41
42        let mut txt = Text::new();
43        for (k, v) in tags.inner() {
44            txt.add_line(Line(format!("{} = {}", k, v)).secondary());
45        }
46        txt.add_line(Line(format!(
47            "Length before trimming: {}",
48            road.untrimmed_length()
49        )));
50        if app.model.intersection_geom {
51            txt.add_line(Line(format!(
52                "Length after trimming: {}",
53                road.center_line.length()
54            )));
55        }
56        for (rt, to) in &road.turn_restrictions {
57            info!("Simple turn restriction {:?} to {}", rt, to);
58        }
59        for (via, to) in &road.complicated_turn_restrictions {
60            info!("Complicated turn restriction via {} to {}", via, to);
61        }
62        let info = txt.into_widget(ctx);
63
64        let controls = Widget::col(vec![
65            Widget::row(vec![
66                "lanes:forward".text_widget(ctx).margin_right(20),
67                Spinner::widget(
68                    ctx,
69                    "lanes:forward",
70                    (1, 5),
71                    tags.get("lanes:forward")
72                        .and_then(|x| x.parse::<usize>().ok())
73                        .unwrap_or(1),
74                    1,
75                ),
76            ]),
77            Widget::row(vec![
78                "lanes:backward".text_widget(ctx).margin_right(20),
79                Spinner::widget(
80                    ctx,
81                    "lanes:backward",
82                    (0, 5),
83                    tags.get("lanes:backward")
84                        .and_then(|x| x.parse::<usize>().ok())
85                        .unwrap_or_else(|| if tags.is("oneway", "yes") { 0 } else { 1 }),
86                    1,
87                ),
88            ]),
89            Widget::row(vec![
90                "sidewalk".text_widget(ctx).margin_right(20),
91                Widget::dropdown(
92                    ctx,
93                    "sidewalk",
94                    if tags.is("sidewalk", "both") {
95                        "both"
96                    } else if tags.is("sidewalk", "none") {
97                        "none"
98                    } else if tags.is("sidewalk", "left") {
99                        "left"
100                    } else if tags.is("sidewalk", "right") {
101                        "right"
102                    } else {
103                        "both"
104                    }
105                    .to_string(),
106                    Choice::strings(vec!["both", "none", "left", "right"]),
107                ),
108            ]),
109            Widget::row(vec![
110                "parking".text_widget(ctx).margin_right(20),
111                Widget::dropdown(
112                    ctx,
113                    "parking",
114                    // TODO Not all possibilities represented here; very simplified.
115                    if tags.is("parking:lane:both", "parallel") {
116                        "both"
117                    } else if tags.is_any("parking:lane:both", vec!["no_parking", "no_stopping"]) {
118                        "none"
119                    } else if tags.is("parking:lane:left", "parallel") {
120                        "left"
121                    } else if tags.is("parking:lane:right", "parallel") {
122                        "right"
123                    } else {
124                        "none"
125                    }
126                    .to_string(),
127                    Choice::strings(vec!["both", "none", "left", "right"]),
128                ),
129            ]),
130            Widget::row(vec![
131                "Width scale".text_widget(ctx).margin_right(20),
132                Spinner::widget(ctx, "width_scale", (0.5, 10.0), 1.0, 0.5),
133            ]),
134        ]);
135
136        let col = vec![
137            Widget::row(vec![
138                Line("Editing road").small_heading().into_widget(ctx),
139                ctx.style().btn_close_widget(ctx),
140            ]),
141            Widget::row(vec![info, controls]),
142            ctx.style()
143                .btn_solid_primary
144                .text("Apply")
145                .hotkey(Key::Enter)
146                .build_def(ctx),
147        ];
148        let panel = Panel::new_builder(Widget::col(col))
149            .aligned(HorizontalAlignment::Left, VerticalAlignment::Top)
150            .build(ctx);
151        <dyn SimpleState<_>>::new_state(
152            panel,
153            Box::new(EditRoad {
154                r,
155                show_direction: ctx.upload(batch),
156            }),
157        )
158    }
159}
160
161impl SimpleState<App> for EditRoad {
162    fn on_click(
163        &mut self,
164        ctx: &mut EventCtx,
165        app: &mut App,
166        x: &str,
167        panel: &mut Panel,
168    ) -> Transition<App> {
169        match x {
170            "close" => Transition::Pop,
171            "Apply" => {
172                app.model.road_deleted(self.r);
173
174                let mut tags = app
175                    .model
176                    .map
177                    .road_to_osm_tags(self.r)
178                    .cloned()
179                    .unwrap_or_else(Tags::empty);
180
181                tags.remove("lanes");
182                tags.remove("oneway");
183                let fwd: usize = panel.spinner("lanes:forward");
184                let back: usize = panel.spinner("lanes:backward");
185                if back == 0 {
186                    tags.insert("oneway", "yes");
187                    tags.insert("lanes", fwd.to_string());
188                } else {
189                    tags.insert("lanes", (fwd + back).to_string());
190                    tags.insert("lanes:forward", fwd.to_string());
191                    tags.insert("lanes:backward", back.to_string());
192                }
193
194                tags.insert("sidewalk", panel.dropdown_value::<String, &str>("sidewalk"));
195
196                tags.remove("parking:lane:both");
197                tags.remove("parking:lane:left");
198                tags.remove("parking:lane:right");
199                match panel.dropdown_value::<String, &str>("parking").as_ref() {
200                    "both" => {
201                        tags.insert("parking:lane:both", "parallel");
202                    }
203                    "none" => {
204                        tags.insert("parking:lane:both", "none");
205                    }
206                    "left" => {
207                        tags.insert("parking:lane:left", "parallel");
208                        tags.insert("parking:lane:right", "none");
209                    }
210                    "right" => {
211                        tags.insert("parking:lane:left", "none");
212                        tags.insert("parking:lane:right", "parallel");
213                    }
214                    _ => unreachable!(),
215                }
216
217                let road = app.model.map.streets.roads.get_mut(&self.r).unwrap();
218                road.lane_specs_ltr =
219                    osm2streets::get_lane_specs_ltr(&tags, &app.model.map.streets.config);
220                let scale = panel.spinner("width_scale");
221                for lane in &mut road.lane_specs_ltr {
222                    lane.width *= scale;
223                }
224
225                app.model.road_added(ctx, self.r);
226                Transition::Pop
227            }
228            _ => unreachable!(),
229        }
230    }
231
232    fn panel_changed(
233        &mut self,
234        ctx: &mut EventCtx,
235        app: &mut App,
236        panel: &mut Panel,
237    ) -> Option<Transition<App>> {
238        let scale = panel.spinner("width_scale");
239        app.model.road_deleted(self.r);
240        let tags = app
241            .model
242            .map
243            .road_to_osm_tags(self.r)
244            .cloned()
245            .unwrap_or_else(Tags::empty);
246        let road = app.model.map.streets.roads.get_mut(&self.r).unwrap();
247        road.lane_specs_ltr = osm2streets::get_lane_specs_ltr(&tags, &app.model.map.streets.config);
248        for lane in &mut road.lane_specs_ltr {
249            lane.width *= scale;
250        }
251        app.model.road_added(ctx, self.r);
252        Some(Transition::Keep)
253    }
254
255    fn other_event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition<App> {
256        ctx.canvas_movement();
257        Transition::Keep
258    }
259
260    fn draw(&self, g: &mut GfxCtx, _: &App) {
261        g.redraw(&self.show_direction);
262    }
263
264    fn draw_baselayer(&self) -> DrawBaselayer {
265        DrawBaselayer::PreviousState
266    }
267}