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 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 §or.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 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 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 if let Some(l) = server_ctx.hover.1 {
187 map.selected_linedefs.retain(|&selected| selected != l);
188 changed = true;
189 }
190 } else {
191 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 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 let use_manual_mode = !ui.ctrl; if use_manual_mode {
236 let _linedef_id =
239 map.create_linedef_manual(start_vertex, end_vertex);
240
241 if let Some(sector_id) = map.close_polygon_manual() {
243 let mut surface = Surface::new(sector_id);
245 surface.calculate_geometry(map);
246 map.surfaces.insert(surface.id, surface);
247
248 map.clear_temp();
250 set_current_gid_pos = false;
251 }
252 } else {
253 let ids = map.create_linedef(start_vertex, end_vertex);
256
257 if let Some(sector_id) = ids.1 {
258 let mut surface = Surface::new(sector_id);
260 surface.calculate_geometry(map);
261 map.surfaces.insert(surface.id, surface);
262
263 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 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 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 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 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 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 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 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 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 map.add_to_selection(selection.0, selection.1, selection.2);
607 } else if ui.alt {
608 map.remove_from_selection(selection.0, selection.1, selection.2);
610 } else {
611 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(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 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 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}