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 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}