egui_map_view/layers/
drawing.rs1use crate::layers::Layer;
33use crate::projection::{GeoPos, MapProjection};
34use egui::{Color32, Painter, Pos2, Response, Stroke};
35use serde::{Deserialize, Serialize};
36use std::any::Any;
37
38#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
40pub enum DrawMode {
41 #[default]
43 Disabled,
44 Draw,
46 Erase,
48}
49
50#[derive(Clone, Serialize, Deserialize)]
52#[serde(default)]
53pub struct DrawingLayer {
54 polylines: Vec<Vec<GeoPos>>,
55
56 #[serde(skip)]
57 pub stroke: Stroke,
59
60 #[serde(skip)]
61 pub draw_mode: DrawMode,
63}
64
65impl DrawingLayer {
66 pub fn new(stroke: Stroke) -> Self {
68 Self {
69 polylines: Vec::new(),
70 stroke,
71 draw_mode: DrawMode::default(),
72 }
73 }
74}
75
76impl Default for DrawingLayer {
77 fn default() -> Self {
78 Self {
79 polylines: Vec::new(),
80 stroke: Stroke::new(2.0, Color32::RED),
81 draw_mode: DrawMode::default(),
82 }
83 }
84}
85
86impl DrawingLayer {
87 fn handle_draw_input(&mut self, response: &Response, projection: &MapProjection) -> bool {
88 if response.hovered() {
89 response.ctx.set_cursor_icon(egui::CursorIcon::Crosshair);
90 }
91
92 if response.clicked() {
93 if let Some(pointer_pos) = response.interact_pointer_pos() {
94 let geo_pos = projection.unproject(pointer_pos);
95 if let Some(last_line) = self.polylines.last_mut()
96 && response.ctx.input(|i| i.modifiers.shift)
97 {
98 last_line.push(geo_pos);
99 } else {
100 let geo_pos2 = projection.unproject(pointer_pos + egui::vec2(1.0, 0.0));
102 self.polylines.push(vec![geo_pos, geo_pos2]);
103 }
104 }
105 }
106
107 if response.drag_started() {
108 self.polylines.push(Vec::new());
109 }
110
111 if response.dragged() {
112 if let Some(pointer_pos) = response.interact_pointer_pos() {
113 if let Some(last_line) = self.polylines.last_mut() {
114 let geo_pos = projection.unproject(pointer_pos);
115 last_line.push(geo_pos);
116 }
117 }
118 }
119
120 response.hovered()
123 }
124
125 fn handle_erase_input(&mut self, response: &Response, projection: &MapProjection) -> bool {
126 if response.hovered() {
127 response.ctx.set_cursor_icon(egui::CursorIcon::NotAllowed);
128 }
129
130 if response.dragged() || response.clicked() {
131 if let Some(pointer_pos) = response.interact_pointer_pos() {
132 self.erase_at(pointer_pos, projection);
133 }
134 }
135 response.hovered()
136 }
137
138 fn erase_at(&mut self, pointer_pos: Pos2, projection: &MapProjection) {
139 let erase_radius_screen = self.stroke.width;
140 let erase_radius_sq = erase_radius_screen * erase_radius_screen;
141
142 let old_polylines = std::mem::take(&mut self.polylines);
143 self.polylines = old_polylines
144 .into_iter()
145 .flat_map(|polyline| {
146 split_polyline_by_erase_circle(&polyline, pointer_pos, erase_radius_sq, projection)
147 })
148 .collect();
149 }
150}
151
152impl Layer for DrawingLayer {
153 fn as_any(&self) -> &dyn Any {
154 self
155 }
156
157 fn as_any_mut(&mut self) -> &mut dyn Any {
158 self
159 }
160
161 fn handle_input(&mut self, response: &Response, projection: &MapProjection) -> bool {
162 match self.draw_mode {
163 DrawMode::Disabled => false,
164 DrawMode::Draw => self.handle_draw_input(response, projection),
165 DrawMode::Erase => self.handle_erase_input(response, projection),
166 }
167 }
168
169 fn draw(&self, painter: &Painter, projection: &MapProjection) {
170 for polyline in &self.polylines {
171 if polyline.len() > 1 {
172 let screen_points: Vec<egui::Pos2> =
173 polyline.iter().map(|p| projection.project(*p)).collect();
174 painter.add(egui::Shape::line(screen_points, self.stroke));
175 }
176 }
177 }
178}
179
180fn split_polyline_by_erase_circle(
182 polyline: &[GeoPos],
183 pointer_pos: Pos2,
184 erase_radius_sq: f32,
185 projection: &MapProjection,
186) -> Vec<Vec<GeoPos>> {
187 if polyline.len() < 2 {
188 return vec![];
189 }
190
191 let screen_points: Vec<Pos2> = polyline.iter().map(|p| projection.project(*p)).collect();
192
193 let mut new_polylines = Vec::new();
194 let mut current_line = Vec::new();
195 let mut in_visible_part = true;
196
197 if dist_sq_to_segment(pointer_pos, screen_points[0], screen_points[1]) < erase_radius_sq {
199 in_visible_part = false;
200 } else {
201 current_line.push(polyline[0]);
202 }
203
204 for i in 0..(polyline.len() - 1) {
205 let p2_geo = polyline[i + 1];
206 let p1_screen = screen_points[i];
207 let p2_screen = screen_points[i + 1];
208
209 let segment_is_erased =
210 dist_sq_to_segment(pointer_pos, p1_screen, p2_screen) < erase_radius_sq;
211
212 if in_visible_part {
213 if segment_is_erased {
214 let t = projection_factor(pointer_pos, p1_screen, p2_screen);
216 let split_point_screen = p1_screen.lerp(p2_screen, t);
217 let split_point_geo = projection.unproject(split_point_screen);
218 current_line.push(split_point_geo);
219
220 if current_line.len() > 1 {
221 new_polylines.push(std::mem::take(&mut current_line));
222 }
223 in_visible_part = false;
224 } else {
225 current_line.push(p2_geo);
227 }
228 } else {
229 if !segment_is_erased {
231 let t = projection_factor(pointer_pos, p1_screen, p2_screen);
233 let split_point_screen = p1_screen.lerp(p2_screen, t);
234 let split_point_geo = projection.unproject(split_point_screen);
235
236 current_line.push(split_point_geo);
238 current_line.push(p2_geo);
239 in_visible_part = true;
240 }
241 }
243 }
244
245 if current_line.len() > 1 {
246 new_polylines.push(current_line);
247 }
248
249 new_polylines
250}
251
252fn dist_sq_to_segment(p: Pos2, a: Pos2, b: Pos2) -> f32 {
254 let ab = b - a;
255 let ap = p - a;
256 let l2 = ab.length_sq();
257
258 if l2 == 0.0 {
259 return ap.length_sq();
261 }
262
263 let t = (ap.dot(ab) / l2).clamp(0.0, 1.0);
266
267 let closest_point = a + t * ab;
269
270 p.distance_sq(closest_point)
271}
272
273fn projection_factor(p: Pos2, a: Pos2, b: Pos2) -> f32 {
276 let ab = b - a;
277 let ap = p - a;
278 let l2 = ab.length_sq();
279
280 if l2 == 0.0 {
281 return 0.0;
282 }
283
284 (ap.dot(ab) / l2).clamp(0.0, 1.0)
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291
292 #[test]
293 fn drawing_layer_new() {
294 let layer = DrawingLayer::default();
295 assert_eq!(layer.draw_mode, DrawMode::Disabled);
296 assert!(layer.polylines.is_empty());
297 }
298
299 #[test]
300 fn drawing_layer_as_any() {
301 let layer = DrawingLayer::default();
302 assert!(layer.as_any().is::<DrawingLayer>());
303 }
304
305 #[test]
306 fn drawing_layer_as_any_mut() {
307 let mut layer = DrawingLayer::default();
308 assert!(layer.as_any_mut().is::<DrawingLayer>());
309 }
310
311 #[test]
312 fn drawing_layer_serde() {
313 let mut layer = DrawingLayer::default();
314 layer.draw_mode = DrawMode::Draw; layer.polylines.push(vec![
316 GeoPos { lon: 1.0, lat: 2.0 },
317 GeoPos { lon: 3.0, lat: 4.0 },
318 ]);
319 layer.stroke = Stroke::new(5.0, Color32::BLUE); let json = serde_json::to_string(&layer).unwrap();
322
323 assert!(json.contains(r#""polylines":[[{"lon":1.0,"lat":2.0},{"lon":3.0,"lat":4.0}]]"#));
325 assert!(!json.contains("draw_mode"));
326 assert!(!json.contains("stroke"));
327
328 let deserialized: DrawingLayer = serde_json::from_str(&json).unwrap();
329
330 assert_eq!(deserialized.polylines, layer.polylines);
332
333 assert_eq!(deserialized.draw_mode, DrawMode::Disabled);
336 assert_eq!(deserialized.stroke.width, 2.0);
337 assert_eq!(deserialized.stroke.color, Color32::RED);
338 }
339}