1use std::collections::BTreeSet;
2
3use crate::ID;
4use map_gui::tools::intersections_from_roads;
5use map_model::{IntersectionID, RoadID};
6use widgetry::{Color, Drawable, EventCtx, GeomBatch, GfxCtx, Key, RewriteColor, Widget};
7
8use crate::app::App;
9use crate::common::CommonState;
10
11pub struct RoadSelector {
13 pub roads: BTreeSet<RoadID>,
14 pub intersections: BTreeSet<IntersectionID>,
17 pub preview: Option<Drawable>,
18 mode: Mode,
19 dragging: bool,
20}
21
22pub enum Mode {
23 Pan,
25 Route {
27 i1: Option<IntersectionID>,
28 preview_path: Option<(IntersectionID, Vec<RoadID>, Drawable)>,
29 },
30 Paint,
32 Erase,
34}
35
36impl RoadSelector {
37 pub fn new(ctx: &mut EventCtx, app: &mut App, roads: BTreeSet<RoadID>) -> RoadSelector {
38 app.primary.current_selection = None;
39 let mut rs = RoadSelector {
40 roads,
41 intersections: BTreeSet::new(),
42 preview: None,
43 mode: Mode::Pan,
44 dragging: false,
45 };
46 rs.roads_changed(ctx, app);
47 rs
48 }
49
50 pub fn make_controls(&self, ctx: &mut EventCtx) -> Widget {
51 Widget::custom_row(vec![
52 ctx.style()
53 .btn_plain
54 .icon("system/assets/tools/pencil.svg")
55 .disabled(matches!(self.mode, Mode::Paint))
56 .hotkey(Key::P)
57 .build_widget(ctx, "paint"),
58 ctx.style()
59 .btn_plain
60 .icon("system/assets/tools/eraser.svg")
61 .hotkey(Key::Backspace)
62 .disabled(matches!(self.mode, Mode::Erase))
63 .build_widget(ctx, "erase"),
64 ctx.style()
65 .btn_plain
66 .icon("system/assets/tools/pin.svg")
67 .hotkey(Key::R)
68 .disabled(matches!(self.mode, Mode::Route { .. }))
69 .build_widget(ctx, "select along route"),
70 ctx.style()
71 .btn_plain
72 .icon("system/assets/tools/pan.svg")
73 .hotkey(Key::Escape)
74 .disabled(matches!(self.mode, Mode::Pan))
75 .build_widget(ctx, "pan"),
76 ])
77 .evenly_spaced()
78 }
79
80 fn roads_changed(&mut self, ctx: &mut EventCtx, app: &App) {
81 let mut batch = GeomBatch::new();
82 for r in &self.roads {
83 batch.push(
84 Color::BLUE.alpha(0.5),
85 app.primary.map.get_r(*r).get_thick_polygon(),
86 );
87 }
88 self.intersections.clear();
89 for i in intersections_from_roads(&self.roads, &app.primary.map) {
90 self.intersections.insert(i);
91 batch.push(
92 Color::BLUE.alpha(0.5),
93 app.primary.map.get_i(i).polygon.clone(),
94 );
95 }
96 self.preview = Some(ctx.upload(batch));
97 }
98
99 pub fn event(&mut self, ctx: &mut EventCtx, app: &mut App, clicked: Option<&str>) -> bool {
101 if ctx.redo_mouseover() {
102 match self.mode {
103 Mode::Pan => {
104 app.primary.current_selection = None;
105 }
106 Mode::Route { .. } => {
107 app.primary.current_selection = app.mouseover_unzoomed_intersections(ctx);
108 }
109 Mode::Paint | Mode::Erase => {
110 app.primary.current_selection =
111 match app.mouseover_unzoomed_roads_and_intersections(ctx) {
112 Some(ID::Road(r)) => Some(r),
113 Some(ID::Lane(l)) => Some(l.road),
114 _ => None,
115 }
116 .and_then(|r| {
117 if app.primary.map.get_r(r).is_light_rail() {
118 None
119 } else {
120 Some(ID::Road(r))
121 }
122 });
123 }
124 }
125 }
126
127 match self.mode {
128 Mode::Pan | Mode::Route { .. } => {
129 ctx.canvas_movement();
130 }
131 Mode::Paint | Mode::Erase => {
132 if self.dragging && ctx.input.left_mouse_button_released() {
133 self.dragging = false;
134 } else if !self.dragging && ctx.input.left_mouse_button_pressed() {
135 self.dragging = true;
136 }
137 }
138 }
139
140 if self.dragging {
141 if let Some(ID::Road(r)) = app.primary.current_selection {
142 let change = match self.mode {
143 Mode::Paint => {
144 if self.roads.contains(&r) {
145 false
146 } else {
147 self.roads.insert(r);
148 true
149 }
150 }
151 Mode::Erase => {
152 if self.roads.contains(&r) {
153 self.roads.remove(&r);
154 true
155 } else {
156 false
157 }
158 }
159 Mode::Route { .. } | Mode::Pan => unreachable!(),
160 };
161 if change {
162 self.roads_changed(ctx, app);
163 return true;
164 }
165 }
166 }
167
168 if let Some(x) = clicked {
169 match x {
170 "paint" => {
171 self.dragging = false;
172 self.mode = Mode::Paint;
173 return true;
174 }
175 "erase" => {
176 self.dragging = false;
177 self.mode = Mode::Erase;
178 return true;
179 }
180 "pan" => {
181 app.primary.current_selection = None;
182 self.dragging = false;
183 self.mode = Mode::Pan;
184 return true;
185 }
186 "select along route" => {
187 app.primary.current_selection = None;
188 self.dragging = false;
189 self.mode = Mode::Route {
190 i1: None,
191 preview_path: None,
192 };
193 return true;
194 }
195 _ => unreachable!(),
196 }
197 }
198
199 if let Mode::Route {
200 ref mut i1,
201 ref mut preview_path,
202 } = self.mode
203 {
204 if let Some(ID::Intersection(i)) = app.primary.current_selection {
205 if i1.is_none() && app.per_obj.left_click(ctx, "start here") {
206 *i1 = Some(i);
207 }
208 }
209
210 if let Some(i1) = *i1 {
211 if let Some(ID::Intersection(i2)) = app.primary.current_selection {
212 if preview_path
213 .as_ref()
214 .map(|(i, _, _)| *i != i2)
215 .unwrap_or(true)
216 {
217 let mut batch = GeomBatch::new();
218 let roads = if let Some((roads, intersections)) =
219 app.primary.map.simple_path_btwn(i1, i2)
220 {
221 for r in &roads {
222 batch.push(
223 Color::RED.alpha(0.5),
224 app.primary.map.get_r(*r).get_thick_polygon(),
225 );
226 }
227 for i in intersections {
228 batch.push(
229 Color::RED.alpha(0.5),
230 app.primary.map.get_i(i).polygon.clone(),
231 );
232 }
233 roads
234 } else {
235 Vec::new()
236 };
237 *preview_path = Some((i2, roads, ctx.upload(batch)));
238 }
239
240 if preview_path
241 .as_ref()
242 .map(|(_, roads, _)| !roads.is_empty())
243 .unwrap_or(false)
244 && app.per_obj.left_click(ctx, "end here")
245 {
246 let (_, roads, _) = preview_path.take().unwrap();
247 self.roads.extend(roads);
248 self.mode = Mode::Pan;
249 self.roads_changed(ctx, app);
250 return true;
251 }
252 } else {
253 *preview_path = None;
254 }
255 }
256 }
257
258 false
259 }
260
261 pub fn draw(&self, g: &mut GfxCtx, app: &App, show_preview: bool) {
262 if show_preview {
263 if let Some(ref p) = self.preview {
264 g.redraw(p);
265 }
266 }
267 if g.canvas.get_cursor_in_map_space().is_some() {
268 if let Some(cursor) = match self.mode {
269 Mode::Pan => None,
270 Mode::Paint => Some("system/assets/tools/pencil.svg"),
271 Mode::Erase => Some("system/assets/tools/eraser.svg"),
272 Mode::Route { .. } => Some("system/assets/tools/pin.svg"),
273 } {
274 let batch = GeomBatch::load_svg(g, cursor)
275 .centered_on(g.canvas.get_cursor().to_pt())
276 .color(RewriteColor::ChangeAll(Color::GREEN));
277 g.fork_screenspace();
278 batch.draw(g);
279 g.unfork();
280 }
281 }
282
283 if let Mode::Route {
284 ref i1,
285 ref preview_path,
286 } = self.mode
287 {
288 if let Some(i) = i1 {
289 g.draw_polygon(Color::GREEN, app.primary.map.get_i(*i).polygon.clone());
290 }
291 if let Some((_, _, ref p)) = preview_path {
292 g.redraw(p);
293 }
294 }
295
296 CommonState::draw_osd(g, app);
297 }
298}