Skip to main content

rustapi/tools/
linedef.rs

1use crate::actions::edit_linedef::EDIT_LINEDEF_ACTION_ID;
2use crate::editor::RUSTERIX;
3use crate::hud::{Hud, HudMode};
4use crate::prelude::*;
5use MapEvent::*;
6use ToolEvent::*;
7use rusterix::{Surface, prelude::*};
8use scenevm::GeoId;
9use std::str::FromStr;
10use vek::Vec2;
11
12pub struct LinedefTool {
13    id: TheId,
14    click_pos: Vec2<f32>,
15    click_pos_3d: Vec3<f32>,
16    /// The initial ray intersection point on the drag plane at click time
17    click_ray_intersection_3d: Option<Vec3<f32>>,
18    click_selected: bool,
19    drag_changed: bool,
20    rectangle_undo_map: Map,
21    rectangle_mode: bool,
22    was_clicked: bool,
23
24    hud: Hud,
25}
26
27impl Tool for LinedefTool {
28    fn new() -> Self
29    where
30        Self: Sized,
31    {
32        Self {
33            id: TheId::named("Linedef Tool"),
34            click_pos: Vec2::zero(),
35            click_pos_3d: Vec3::zero(),
36            click_ray_intersection_3d: None,
37            click_selected: false,
38            drag_changed: false,
39            rectangle_undo_map: Map::default(),
40            rectangle_mode: false,
41            was_clicked: false,
42
43            hud: Hud::new(HudMode::Linedef),
44        }
45    }
46
47    fn id(&self) -> TheId {
48        self.id.clone()
49    }
50    fn info(&self) -> String {
51        fl!("tool_linedef")
52    }
53    fn icon_name(&self) -> String {
54        str!("line-segment")
55    }
56    fn accel(&self) -> Option<char> {
57        Some('L')
58    }
59
60    fn help_url(&self) -> Option<String> {
61        Some("docs/creator/tools/linedef".to_string())
62    }
63
64    fn tool_event(
65        &mut self,
66        tool_event: ToolEvent,
67        ui: &mut TheUI,
68        ctx: &mut TheContext,
69        project: &mut Project,
70        server_ctx: &mut ServerContext,
71    ) -> bool {
72        match tool_event {
73            Activate => {
74                if let Some(layout) = ui.get_sharedhlayout("Shared Panel Layout") {
75                    layout.set_mode(TheSharedHLayoutMode::Right);
76                    ctx.ui.relayout = true;
77                }
78
79                server_ctx.curr_map_tool_type = MapToolType::Linedef;
80
81                if let Some(map) = project.get_map_mut(server_ctx) {
82                    map.selected_vertices.clear();
83                    map.selected_sectors.clear();
84                }
85
86                ctx.ui.send(TheEvent::Custom(
87                    TheId::named("Map Selection Changed"),
88                    TheValue::Empty,
89                ));
90
91                return true;
92            }
93            DeActivate => {
94                server_ctx.curr_map_tool_type = MapToolType::General;
95                server_ctx.hover_cursor = None;
96                if let Some(region) = project.get_region_mut(&server_ctx.curr_region) {
97                    region.map.clear_temp();
98                }
99                return true;
100            }
101            _ => {}
102        };
103
104        false
105    }
106
107    fn map_event(
108        &mut self,
109        map_event: MapEvent,
110        ui: &mut TheUI,
111        ctx: &mut TheContext,
112        map: &mut Map,
113        server_ctx: &mut ServerContext,
114    ) -> Option<ProjectUndoAtom> {
115        let mut undo_atom: Option<ProjectUndoAtom> = None;
116
117        fn vertex_is_in_rect_sector(map: &Map, vertex_id: u32) -> bool {
118            for sector in &map.sectors {
119                if !sector.properties.get_bool_default("rect", false) {
120                    continue;
121                }
122                for &linedef_id in &sector.linedefs {
123                    if let Some(linedef) = map.find_linedef(linedef_id)
124                        && (linedef.start_vertex == vertex_id || linedef.end_vertex == vertex_id)
125                    {
126                        return true;
127                    }
128                }
129            }
130            false
131        }
132
133        match map_event {
134            MapKey(c) => {
135                match c {
136                    '1'..='9' => map.subdivisions = (c as u8 - b'0') as f32,
137                    '0' => map.subdivisions = 10.0,
138                    _ => {}
139                }
140                RUSTERIX.write().unwrap().set_dirty();
141            }
142            MapClicked(coord) => {
143                if self.hud.clicked(coord.x, coord.y, map, ui, ctx, server_ctx) {
144                    self.was_clicked = false;
145                    RUSTERIX.write().unwrap().set_dirty();
146                    return None;
147                }
148
149                self.was_clicked = true;
150
151                // ---
152
153                // If a vertex is under the cursor in D2, prioritize continuing line creation
154                // over linedef selection. This allows extending existing chains by clicking endpoints.
155                let mut over_vertex = false;
156                if let Some(grid) = &server_ctx.hover_cursor {
157                    if let Some(vertex_id) = map.find_vertex_at(grid.x, grid.y)
158                        && !vertex_is_in_rect_sector(map, vertex_id)
159                    {
160                        over_vertex = true;
161                    }
162                }
163
164                self.click_selected = false;
165                let hovering_vertex_in_2d =
166                    server_ctx.editor_view_mode == EditorViewMode::D2 && over_vertex;
167
168                if map.curr_grid_pos.is_none()
169                    && server_ctx.hover.1.is_some()
170                    && !hovering_vertex_in_2d
171                {
172                    map.selected_entity_item = None;
173                    let mut changed = false;
174
175                    if ui.shift {
176                        // Add
177                        if let Some(l) = server_ctx.hover.1 {
178                            if !map.selected_linedefs.contains(&l) {
179                                map.selected_linedefs.push(l);
180                                changed = true;
181                            }
182                            self.click_selected = true;
183                        }
184                    } else if ui.alt {
185                        // Subtract
186                        if let Some(l) = server_ctx.hover.1 {
187                            map.selected_linedefs.retain(|&selected| selected != l);
188                            changed = true;
189                        }
190                    } else {
191                        // Replace
192                        if let Some(v) = server_ctx.hover.1 {
193                            map.selected_linedefs = vec![v];
194                            changed = true;
195                        } else {
196                            map.selected_linedefs.clear();
197                            changed = true;
198                        }
199                        self.click_selected = true;
200                    }
201
202                    if changed {
203                        server_ctx.curr_action_id =
204                            Some(Uuid::from_str(EDIT_LINEDEF_ACTION_ID).unwrap());
205                        ctx.ui.send(TheEvent::Custom(
206                            TheId::named("Map Selection Changed"),
207                            TheValue::Empty,
208                        ));
209                    }
210                } else if server_ctx.editor_view_mode == EditorViewMode::D2 {
211                    // Line mode
212                    let mut set_current_gid_pos = true;
213                    if let Some(render_view) = ui.get_render_view("PolyView") {
214                        let dim = *render_view.dim();
215                        let grid_pos = server_ctx.local_to_map_grid(
216                            Vec2::new(dim.width as f32, dim.height as f32),
217                            Vec2::new(coord.x as f32, coord.y as f32),
218                            map,
219                            map.subdivisions,
220                        );
221
222                        if let Some(curr_grid_pos) = map.curr_grid_pos {
223                            if curr_grid_pos.x != grid_pos.x || curr_grid_pos.y != grid_pos.y {
224                                let prev = map.clone();
225
226                                let start_vertex =
227                                    map.add_vertex_at(curr_grid_pos.x, curr_grid_pos.y);
228                                let end_vertex = map.add_vertex_at(grid_pos.x, grid_pos.y);
229
230                                // Choose between manual and auto polygon creation modes
231                                // Manual mode (default): Only creates sectors when you close the polygon
232                                // Auto mode (Ctrl/Cmd): Automatically detects and creates sectors when a loop is closed
233                                let use_manual_mode = !ui.ctrl; // Ctrl/Cmd enables auto-detection
234
235                                if use_manual_mode {
236                                    // MANUAL MODE: Only creates sectors when you manually close the polygon
237                                    // Good for drawing in existing grids where auto-detection would trigger too early
238                                    let _linedef_id =
239                                        map.create_linedef_manual(start_vertex, end_vertex);
240
241                                    // Check if the user manually closed the polygon (clicked back to start)
242                                    if let Some(sector_id) = map.close_polygon_manual() {
243                                        // When we close a polygon add a surface
244                                        let mut surface = Surface::new(sector_id);
245                                        surface.calculate_geometry(map);
246                                        map.surfaces.insert(surface.id, surface);
247
248                                        // and delete the temporary data
249                                        map.clear_temp();
250                                        set_current_gid_pos = false;
251                                    }
252                                } else {
253                                    // AUTO MODE: Automatically detects and creates sectors when a loop is closed
254                                    // Good for quick polygon creation in empty areas
255                                    let ids = map.create_linedef(start_vertex, end_vertex);
256
257                                    if let Some(sector_id) = ids.1 {
258                                        // When we close a polygon add a surface
259                                        let mut surface = Surface::new(sector_id);
260                                        surface.calculate_geometry(map);
261                                        map.surfaces.insert(surface.id, surface);
262
263                                        // and delete the temporary data
264                                        map.clear_temp();
265                                        set_current_gid_pos = false;
266                                    }
267                                }
268
269                                undo_atom = Some(ProjectUndoAtom::MapEdit(
270                                    server_ctx.pc,
271                                    Box::new(prev),
272                                    Box::new(map.clone()),
273                                ));
274                            }
275                        }
276
277                        if set_current_gid_pos {
278                            map.curr_grid_pos = Some(vek::Vec2::new(grid_pos.x, grid_pos.y));
279                        }
280                    }
281                }
282
283                self.click_pos = Vec2::new(coord.x as f32, coord.y as f32);
284                self.click_ray_intersection_3d = None;
285
286                // For 3D dragging, use the average position of selected linedef vertices
287                if self.click_selected && !map.selected_linedefs.is_empty() {
288                    let mut sum_pos = Vec3::zero();
289                    let mut count = 0;
290                    for line_id in &map.selected_linedefs {
291                        if let Some(line) = map.find_linedef(*line_id) {
292                            if let Some(v1) = map.find_vertex(line.start_vertex) {
293                                sum_pos += v1.as_vec3_world();
294                                count += 1;
295                            }
296                            if let Some(v2) = map.find_vertex(line.end_vertex) {
297                                sum_pos += v2.as_vec3_world();
298                                count += 1;
299                            }
300                        }
301                    }
302                    if count > 0 {
303                        self.click_pos_3d = sum_pos / count as f32;
304                    } else {
305                        self.click_pos_3d = server_ctx.geo_hit_pos;
306                    }
307
308                    // Compute initial ray intersection on the drag plane at click time
309                    // This ensures dragging is relative to this point, not the vertex average
310                    if server_ctx.editor_view_mode != EditorViewMode::D2 {
311                        if let Some(render_view) = ui.get_render_view("PolyView") {
312                            let dim = *render_view.dim();
313                            let screen_uv = [
314                                coord.x as f32 / dim.width as f32,
315                                coord.y as f32 / dim.height as f32,
316                            ];
317
318                            let rusterix = RUSTERIX.read().unwrap();
319                            let ray = rusterix.client.camera_d3.create_ray(
320                                Vec2::new(screen_uv[0], 1.0 - screen_uv[1]),
321                                Vec2::new(dim.width as f32, dim.height as f32),
322                                Vec2::zero(),
323                            );
324                            drop(rusterix);
325
326                            let plane = server_ctx.gizmo_mode;
327                            let plane_normal = match plane {
328                                GizmoMode::XZ => Vec3::new(0.0, 1.0, 0.0),
329                                GizmoMode::XY => Vec3::new(0.0, 0.0, 1.0),
330                                GizmoMode::YZ => Vec3::new(1.0, 0.0, 0.0),
331                            };
332
333                            let denom: f32 = plane_normal.dot(ray.dir);
334                            if denom.abs() > 0.0001 {
335                                let t = (self.click_pos_3d - ray.origin).dot(plane_normal) / denom;
336                                if t >= 0.0 {
337                                    self.click_ray_intersection_3d = Some(ray.origin + ray.dir * t);
338                                }
339                            }
340                        }
341                    }
342                } else {
343                    self.click_pos_3d = server_ctx.geo_hit_pos;
344                }
345
346                self.rectangle_undo_map = map.clone();
347                self.rectangle_mode = false;
348            }
349            MapDragged(coord) => {
350                if self.hud.dragged(coord.x, coord.y, map, ui, ctx, server_ctx) {
351                    crate::editor::RUSTERIX.write().unwrap().set_dirty();
352                    return None;
353                }
354
355                if self.click_selected {
356                    // Dragging selected lines
357                    if let Some(render_view) = ui.get_render_view("PolyView") {
358                        let dim = *render_view.dim();
359
360                        if server_ctx.editor_view_mode == EditorViewMode::D2 {
361                            // 2D dragging
362                            let click_pos = server_ctx.local_to_map_grid(
363                                Vec2::new(dim.width as f32, dim.height as f32),
364                                self.click_pos,
365                                map,
366                                map.subdivisions,
367                            );
368                            let drag_pos = server_ctx.local_to_map_grid(
369                                Vec2::new(dim.width as f32, dim.height as f32),
370                                Vec2::new(coord.x as f32, coord.y as f32),
371                                map,
372                                map.subdivisions,
373                            );
374
375                            let mut selected_vertices = vec![];
376
377                            let drag_delta = click_pos - drag_pos;
378                            for line_id in self.rectangle_undo_map.selected_linedefs.iter() {
379                                if let Some(line) = self.rectangle_undo_map.find_linedef(*line_id) {
380                                    selected_vertices.push(line.start_vertex);
381                                    selected_vertices.push(line.end_vertex);
382                                }
383                            }
384
385                            for vertex_id in selected_vertices.iter() {
386                                if let Some(original_vertex) =
387                                    self.rectangle_undo_map.find_vertex_mut(*vertex_id)
388                                {
389                                    let new_pos = Vec2::new(
390                                        original_vertex.x - drag_delta.x,
391                                        original_vertex.y - drag_delta.y,
392                                    );
393                                    map.update_vertex(*vertex_id, new_pos);
394                                }
395                            }
396                            server_ctx.hover_cursor = Some(drag_pos);
397                            if drag_delta.x != 0.0 || drag_delta.y != 0.0 {
398                                self.drag_changed = true;
399                            }
400                        } else {
401                            // 3D dragging
402                            // Only start dragging after a minimum distance threshold
403                            let drag_distance = self
404                                .click_pos
405                                .distance(Vec2::new(coord.x as f32, coord.y as f32));
406                            if drag_distance < 5.0 {
407                                crate::editor::RUSTERIX.write().unwrap().set_dirty();
408                                return None;
409                            }
410
411                            // Use the initial ray intersection as reference (not vertex average)
412                            // This prevents the "jump" when starting to drag
413                            let click_intersection = match self.click_ray_intersection_3d {
414                                Some(pos) => pos,
415                                None => {
416                                    crate::editor::RUSTERIX.write().unwrap().set_dirty();
417                                    return None;
418                                }
419                            };
420
421                            let start_pos = self.click_pos_3d;
422                            let plane = server_ctx.gizmo_mode;
423
424                            let screen_uv = [
425                                coord.x as f32 / dim.width as f32,
426                                coord.y as f32 / dim.height as f32,
427                            ];
428
429                            let rusterix = RUSTERIX.read().unwrap();
430                            let ray = rusterix.client.camera_d3.create_ray(
431                                Vec2::new(screen_uv[0], 1.0 - screen_uv[1]),
432                                Vec2::new(dim.width as f32, dim.height as f32),
433                                Vec2::zero(),
434                            );
435                            drop(rusterix);
436
437                            let plane_normal = match plane {
438                                GizmoMode::XZ => Vec3::new(0.0, 1.0, 0.0),
439                                GizmoMode::XY => Vec3::new(0.0, 0.0, 1.0),
440                                GizmoMode::YZ => Vec3::new(1.0, 0.0, 0.0),
441                            };
442
443                            let denom: f32 = plane_normal.dot(ray.dir);
444
445                            if denom.abs() > 0.0001 {
446                                let t = (start_pos - ray.origin).dot(plane_normal) / denom;
447                                if t >= 0.0 {
448                                    let current_pos = ray.origin + ray.dir * t;
449
450                                    // Calculate drag delta relative to initial click intersection
451                                    // (not the vertex average position)
452                                    let drag_delta = match plane {
453                                        GizmoMode::XZ => Vec3::new(
454                                            current_pos.x - click_intersection.x,
455                                            0.0,
456                                            current_pos.z - click_intersection.z,
457                                        ),
458                                        GizmoMode::XY => Vec3::new(
459                                            current_pos.x - click_intersection.x,
460                                            current_pos.y - click_intersection.y,
461                                            0.0,
462                                        ),
463                                        GizmoMode::YZ => Vec3::new(
464                                            0.0,
465                                            current_pos.y - click_intersection.y,
466                                            current_pos.z - click_intersection.z,
467                                        ),
468                                    };
469
470                                    let mut selected_vertices = vec![];
471                                    for line_id in self.rectangle_undo_map.selected_linedefs.iter()
472                                    {
473                                        if let Some(line) =
474                                            self.rectangle_undo_map.find_linedef(*line_id)
475                                        {
476                                            if !selected_vertices.contains(&line.start_vertex) {
477                                                selected_vertices.push(line.start_vertex);
478                                            }
479                                            if !selected_vertices.contains(&line.end_vertex) {
480                                                selected_vertices.push(line.end_vertex);
481                                            }
482                                        }
483                                    }
484
485                                    for vertex_id in selected_vertices.iter() {
486                                        if let Some(original_vertex) =
487                                            self.rectangle_undo_map.find_vertex(*vertex_id)
488                                        {
489                                            let new_x = original_vertex.x + drag_delta.x;
490                                            let new_y = original_vertex.y + drag_delta.z;
491                                            let new_z = original_vertex.z + drag_delta.y;
492
493                                            // Snap to grid
494                                            let subdivisions = 1.0 / map.subdivisions;
495                                            let snapped_x =
496                                                (new_x / subdivisions).round() * subdivisions;
497                                            let snapped_y =
498                                                (new_y / subdivisions).round() * subdivisions;
499                                            let snapped_z =
500                                                (new_z / subdivisions).round() * subdivisions;
501
502                                            if let Some(vertex) = map.find_vertex_mut(*vertex_id) {
503                                                vertex.x = snapped_x;
504                                                vertex.y = snapped_y;
505                                                vertex.z = snapped_z;
506                                            }
507                                        }
508                                    }
509
510                                    if drag_delta.x != 0.0
511                                        || drag_delta.y != 0.0
512                                        || drag_delta.z != 0.0
513                                    {
514                                        self.drag_changed = true;
515                                    }
516                                }
517                            }
518                        }
519                    }
520                } else {
521                    if !self.rectangle_mode && self.was_clicked {
522                        let dist = self
523                            .click_pos
524                            .distance(Vec2::new(coord.x as f32, coord.y as f32));
525                        if dist > 10.0 {
526                            self.rectangle_mode = true;
527                            map.clear_temp();
528                        }
529                    }
530
531                    if self.rectangle_mode {
532                        if let Some(render_view) = ui.get_render_view("PolyView") {
533                            let dim = *render_view.dim();
534                            let click_pos = server_ctx.local_to_map_grid(
535                                Vec2::new(dim.width as f32, dim.height as f32),
536                                self.click_pos,
537                                map,
538                                map.subdivisions,
539                            );
540                            let drag_pos = server_ctx.local_to_map_grid(
541                                Vec2::new(dim.width as f32, dim.height as f32),
542                                Vec2::new(coord.x as f32, coord.y as f32),
543                                map,
544                                map.subdivisions,
545                            );
546
547                            let selection = if server_ctx.editor_view_mode == EditorViewMode::D2 {
548                                let top_left = Vec2::new(
549                                    click_pos.x.min(drag_pos.x),
550                                    click_pos.y.min(drag_pos.y),
551                                );
552                                let bottom_right = Vec2::new(
553                                    click_pos.x.max(drag_pos.x),
554                                    click_pos.y.max(drag_pos.y),
555                                );
556
557                                let mut selection =
558                                    server_ctx.geometry_in_rectangle(top_left, bottom_right, map);
559
560                                selection.0 = vec![];
561                                selection.2 = vec![];
562
563                                selection
564                            } else {
565                                let mut selection = (vec![], vec![], vec![]);
566
567                                let click_pos = self.click_pos;
568                                let drag_pos = Vec2::new(coord.x as f32, coord.y as f32);
569
570                                let top_left = Vec2::new(
571                                    click_pos.x.min(drag_pos.x),
572                                    click_pos.y.min(drag_pos.y),
573                                );
574                                let bottom_right = Vec2::new(
575                                    click_pos.x.max(drag_pos.x),
576                                    click_pos.y.max(drag_pos.y),
577                                );
578
579                                let mut rusterix = RUSTERIX.write().unwrap();
580                                rusterix.scene_handler.vm.set_active_vm(2);
581                                let linedefs =
582                                    rusterix.scene_handler.vm.active_vm().pick_geo_ids_in_rect(
583                                        dim.width as u32,
584                                        dim.height as u32,
585                                        top_left,
586                                        bottom_right,
587                                        GeoId::Linedef(0),
588                                        false,
589                                        false,
590                                    );
591                                for l in linedefs {
592                                    if let GeoId::Linedef(l) = l {
593                                        selection.1.push(l);
594                                    }
595                                }
596                                rusterix.scene_handler.vm.set_active_vm(0);
597                                selection
598                            };
599
600                            *map = self.rectangle_undo_map.clone();
601                            map.curr_grid_pos = None;
602                            map.curr_rectangle = Some((click_pos, drag_pos));
603
604                            if ui.shift {
605                                // Add
606                                map.add_to_selection(selection.0, selection.1, selection.2);
607                            } else if ui.alt {
608                                // Remove
609                                map.remove_from_selection(selection.0, selection.1, selection.2);
610                            } else {
611                                // Replace
612                                map.selected_linedefs = selection.1;
613                            }
614                        }
615                    }
616                }
617                crate::editor::RUSTERIX.write().unwrap().set_dirty();
618            }
619            MapUp(_) => {
620                if self.click_selected {
621                    if self.drag_changed {
622                        undo_atom = Some(ProjectUndoAtom::MapEdit(
623                            server_ctx.pc,
624                            Box::new(self.rectangle_undo_map.clone()),
625                            Box::new(map.clone()),
626                        ));
627
628                        ctx.ui.send(TheEvent::Custom(
629                            TheId::named("Map Selection Changed"),
630                            TheValue::Empty,
631                        ));
632                    }
633                } else if self.rectangle_mode && map.curr_rectangle.is_some() {
634                    map.clear_temp();
635                    self.rectangle_mode = false;
636                }
637                self.drag_changed = false;
638                self.click_selected = false;
639                self.was_clicked = false;
640            }
641            MapHover(coord) => {
642                if self.hud.hovered(coord.x, coord.y, map, ui, ctx, server_ctx) {
643                    crate::editor::RUSTERIX.write().unwrap().set_dirty();
644                    return None;
645                }
646
647                if let Some(render_view) = ui.get_render_view("PolyView") {
648                    if server_ctx.editor_view_mode == EditorViewMode::D2 {
649                        let dim = *render_view.dim();
650                        if !self.rectangle_mode {
651                            //map.curr_mouse_pos = Some(Vec2::new(coord.x as f32, coord.y as f32));
652                            map.curr_mouse_pos = Some(server_ctx.local_to_map_grid(
653                                Vec2::new(dim.width as f32, dim.height as f32),
654                                Vec2::new(coord.x as f32, coord.y as f32),
655                                map,
656                                map.subdivisions,
657                            ));
658                        }
659                        let mut hover = server_ctx.geometry_at(
660                            Vec2::new(dim.width as f32, dim.height as f32),
661                            Vec2::new(coord.x as f32, coord.y as f32),
662                            map,
663                        );
664
665                        hover.0 = None;
666                        hover.2 = None;
667
668                        server_ctx.hover = hover;
669                        let cp = server_ctx.local_to_map_grid(
670                            Vec2::new(dim.width as f32, dim.height as f32),
671                            Vec2::new(coord.x as f32, coord.y as f32),
672                            map,
673                            map.subdivisions,
674                        );
675                        ctx.ui.send(TheEvent::Custom(
676                            TheId::named("Cursor Pos Changed"),
677                            TheValue::Float2(cp),
678                        ));
679                        server_ctx.hover_cursor = Some(cp);
680                    } else {
681                        if let Some(geo_id) = server_ctx.geo_hit {
682                            match geo_id {
683                                GeoId::Linedef(id) => {
684                                    server_ctx.hover = (None, Some(id), None);
685                                }
686                                _ => {
687                                    server_ctx.hover = (None, None, None);
688                                }
689                            }
690                        } else {
691                            server_ctx.hover = (None, None, None);
692                        }
693
694                        if let Some(cp) = server_ctx.hover_cursor {
695                            ctx.ui.send(TheEvent::Custom(
696                                TheId::named("Cursor Pos Changed"),
697                                TheValue::Float2(cp),
698                            ));
699                        }
700                    }
701
702                    if let Some(l) = server_ctx.hover.1 {
703                        if let Some(linedef) = map.find_linedef(l) {
704                            ctx.ui.send(TheEvent::SetStatusText(
705                                TheId::empty(),
706                                format!(
707                                    "Linedef {}: V{} - V{}",
708                                    l, linedef.start_vertex, linedef.end_vertex
709                                ),
710                            ));
711                        }
712                    } else {
713                        ctx.ui
714                            .send(TheEvent::SetStatusText(TheId::empty(), "".into()));
715                    }
716                }
717            }
718            MapDelete => {
719                if !map.selected_linedefs.is_empty() {
720                    let prev = map.clone();
721                    let lines = map.selected_linedefs.clone();
722
723                    #[allow(clippy::useless_vec)]
724                    map.delete_elements(&vec![], &lines, &vec![]);
725                    map.selected_linedefs.clear();
726
727                    undo_atom = Some(ProjectUndoAtom::MapEdit(
728                        server_ctx.pc,
729                        Box::new(prev),
730                        Box::new(map.clone()),
731                    ));
732                    ctx.ui.send(TheEvent::Custom(
733                        TheId::named("Map Selection Changed"),
734                        TheValue::Empty,
735                    ));
736                }
737            }
738            MapEscape => {
739                map.clear_temp();
740                if !map.selected_linedefs.is_empty() {
741                    map.selected_linedefs.clear();
742
743                    ctx.ui.send(TheEvent::Custom(
744                        TheId::named("Map Selection Changed"),
745                        TheValue::Empty,
746                    ));
747                }
748                self.was_clicked = false;
749                crate::editor::RUSTERIX.write().unwrap().set_dirty();
750            }
751        }
752        undo_atom
753    }
754
755    fn draw_hud(
756        &mut self,
757        buffer: &mut TheRGBABuffer,
758        map: &mut Map,
759        ctx: &mut TheContext,
760        server_ctx: &mut ServerContext,
761        assets: &Assets,
762    ) {
763        let id = if !map.selected_linedefs.is_empty() {
764            Some(map.selected_linedefs[0])
765        } else {
766            None
767        };
768        self.hud.draw(buffer, map, ctx, server_ctx, id, assets);
769    }
770
771    fn handle_event(
772        &mut self,
773        event: &TheEvent,
774        _ui: &mut TheUI,
775        ctx: &mut TheContext,
776        project: &mut Project,
777        server_ctx: &mut ServerContext,
778    ) -> bool {
779        let redraw = false;
780        #[allow(clippy::single_match)]
781        match event {
782            TheEvent::StateChanged(id, state) => {
783                #[allow(clippy::collapsible_if)]
784                if id.name == "Apply Map Properties" && *state == TheWidgetState::Clicked {
785                    // Apply a source
786                    let mut source: Option<Value> = None;
787                    if let Some(id) = server_ctx.curr_tile_id {
788                        source = Some(Value::Source(PixelSource::TileId(id)));
789                    }
790
791                    /*else if server_ctx.curr_map_tool_helper == MapToolHelper::MaterialPicker {
792                        if let Some(id) = server_ctx.curr_material_id {
793                            source = Some(Value::Source(PixelSource::MaterialId(id)));
794                        }
795                    } */
796                    if let Some(source) = source {
797                        if let Some(map) = project.get_map_mut(server_ctx) {
798                            let _prev = map.clone();
799                            for linedef_id in map.selected_linedefs.clone() {
800                                if let Some(linedef) = map.find_linedef_mut(linedef_id) {
801                                    if self.hud.selected_icon_index == 0 {
802                                        linedef.properties.set("row1_source", source.clone());
803                                    } else if self.hud.selected_icon_index == 1 {
804                                        linedef.properties.set("row2_source", source.clone());
805                                    } else if self.hud.selected_icon_index == 2 {
806                                        linedef.properties.set("row3_source", source.clone());
807                                    } else if self.hud.selected_icon_index == 3 {
808                                        linedef.properties.set("row4_source", source.clone());
809                                    }
810                                }
811                            }
812
813                            if server_ctx.get_map_context() == MapContext::Region {
814                                ctx.ui.send(TheEvent::Custom(
815                                    TheId::named("Render SceneManager Map"),
816                                    TheValue::Empty,
817                                ));
818                            }
819
820                            crate::editor::RUSTERIX.write().unwrap().set_dirty();
821                        }
822                    }
823                } else if id.name == "Remove Map Properties" && *state == TheWidgetState::Clicked {
824                    if let Some(map) = project.get_map_mut(server_ctx) {
825                        let _prev = map.clone();
826                        for linedef_id in map.selected_linedefs.clone() {
827                            if let Some(linedef) = map.find_linedef_mut(linedef_id) {
828                                if self.hud.selected_icon_index == 0 {
829                                    if linedef.properties.contains("row1_light") {
830                                        linedef.properties.remove("row1_light");
831                                    } else {
832                                        linedef
833                                            .properties
834                                            .set("row1_source", Value::Source(PixelSource::Off));
835                                    }
836                                } else if self.hud.selected_icon_index == 1 {
837                                    if linedef.properties.contains("row2_light") {
838                                        linedef.properties.remove("row2_light");
839                                    } else {
840                                        linedef
841                                            .properties
842                                            .set("row2_source", Value::Source(PixelSource::Off));
843                                    }
844                                } else if self.hud.selected_icon_index == 2 {
845                                    if linedef.properties.contains("row3_light") {
846                                        linedef.properties.remove("row3_light");
847                                    } else {
848                                        linedef
849                                            .properties
850                                            .set("row3_source", Value::Source(PixelSource::Off));
851                                    }
852                                } else if self.hud.selected_icon_index == 3 {
853                                    if linedef.properties.contains("row4_light") {
854                                        linedef.properties.remove("row4_light");
855                                    } else {
856                                        linedef
857                                            .properties
858                                            .set("row4_source", Value::Source(PixelSource::Off));
859                                    }
860                                }
861                            }
862                        }
863
864                        crate::editor::RUSTERIX.write().unwrap().set_dirty();
865                        ctx.ui.send(TheEvent::Custom(
866                            TheId::named("Map Selection Changed"),
867                            TheValue::Empty,
868                        ));
869
870                        if server_ctx.get_map_context() == MapContext::Region {
871                            ctx.ui.send(TheEvent::Custom(
872                                TheId::named("Render SceneManager Map"),
873                                TheValue::Empty,
874                            ));
875                        }
876                    }
877                }
878            }
879            _ => {}
880        }
881        redraw
882    }
883}