Skip to main content

rustapi/tools/
sector.rs

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