Skip to main content

rustapi/actions/
edit_vertex.rs

1use crate::prelude::*;
2use rusterix::{PixelSource, Value};
3use std::str::FromStr;
4
5pub const EDIT_VERTEX_ACTION_ID: &str = "260fcd81-c456-4ad4-894c-85e7552c856f";
6
7pub struct EditVertex {
8    id: TheId,
9    nodeui: TheNodeUI,
10    show_terrain: bool,
11}
12
13impl EditVertex {
14    fn build_nodeui(show_terrain: bool) -> TheNodeUI {
15        let mut nodeui: TheNodeUI = TheNodeUI::default();
16
17        let item = TheNodeUIItem::Text(
18            "actionVertexName".into(),
19            "".into(),
20            "".into(),
21            "".into(),
22            None,
23            false,
24        );
25        nodeui.add_item(item);
26
27        let item = TheNodeUIItem::FloatEditSlider(
28            "actionVertexX".into(),
29            "".into(),
30            "".into(),
31            0.0,
32            0.0..=0.0,
33            false,
34        );
35        nodeui.add_item(item);
36
37        let item = TheNodeUIItem::FloatEditSlider(
38            "actionVertexY".into(),
39            "".into(),
40            "".into(),
41            0.0,
42            0.0..=0.0,
43            false,
44        );
45        nodeui.add_item(item);
46
47        let item = TheNodeUIItem::FloatEditSlider(
48            "actionVertexZ".into(),
49            "".into(),
50            "".into(),
51            0.0,
52            0.0..=0.0,
53            false,
54        );
55        nodeui.add_item(item);
56
57        if show_terrain {
58            nodeui.add_item(TheNodeUIItem::OpenTree("terrain".into()));
59            nodeui.add_item(TheNodeUIItem::Checkbox(
60                "actionTerrain".into(),
61                "".into(),
62                "".into(),
63                false,
64            ));
65
66            let item = TheNodeUIItem::FloatEditSlider(
67                "actionTerrainSmoothness".into(),
68                "".into(),
69                "".into(),
70                0.0,
71                0.0..=0.0,
72                false,
73            );
74            nodeui.add_item(item);
75
76            nodeui.add_item(TheNodeUIItem::Icons(
77                "actionTerrainTile".into(),
78                "".into(),
79                "".into(),
80                vec![(
81                    TheRGBABuffer::new(TheDim::sized(36, 36)),
82                    "".to_string(),
83                    Uuid::nil(),
84                )],
85            ));
86
87            nodeui.add_item(TheNodeUIItem::Text(
88                "actionTerrainTileId".into(),
89                "".into(),
90                "".into(),
91                "".into(),
92                None,
93                false,
94            ));
95
96            nodeui.add_item(TheNodeUIItem::FloatEditSlider(
97                "actionTerrainTileFalloff".into(),
98                "".into(),
99                "".into(),
100                1.0,
101                0.0..=16.0,
102                false,
103            ));
104            nodeui.add_item(TheNodeUIItem::CloseTree);
105        }
106
107        nodeui.add_item(TheNodeUIItem::OpenTree("billboard".into()));
108        nodeui.add_item(TheNodeUIItem::Icons(
109            "actionTile".into(),
110            "".into(),
111            "".into(),
112            vec![(
113                TheRGBABuffer::new(TheDim::sized(36, 36)),
114                "".to_string(),
115                Uuid::nil(),
116            )],
117        ));
118        nodeui.add_item(TheNodeUIItem::Text(
119            "actionTileId".into(),
120            "".into(),
121            "".into(),
122            "".into(),
123            None,
124            false,
125        ));
126        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
127            "actionSize".into(),
128            "".into(),
129            "".into(),
130            1.0,
131            0.0..=0.0,
132            false,
133        ));
134        nodeui.add_item(TheNodeUIItem::CloseTree);
135
136        nodeui
137    }
138}
139
140impl Action for EditVertex {
141    fn new() -> Self
142    where
143        Self: Sized,
144    {
145        let nodeui = Self::build_nodeui(true);
146
147        // let item = TheNodeUIItem::Markdown("desc".into(), fl!("action_edit_vertex_desc"));
148        // nodeui.add_item(item);
149
150        Self {
151            id: TheId::named_with_id(
152                &fl!("action_edit_vertex"),
153                Uuid::from_str(EDIT_VERTEX_ACTION_ID).unwrap(),
154            ),
155            nodeui,
156            show_terrain: true,
157        }
158    }
159
160    fn id(&self) -> TheId {
161        self.id.clone()
162    }
163
164    fn info(&self) -> String {
165        fl!("action_edit_vertex_desc")
166    }
167
168    fn role(&self) -> ActionRole {
169        ActionRole::Editor
170    }
171
172    fn accel(&self) -> Option<TheAccelerator> {
173        None
174    }
175
176    fn is_applicable(&self, map: &Map, _ctx: &mut TheContext, _server_ctx: &ServerContext) -> bool {
177        map.selected_vertices.len() == 1
178    }
179
180    fn load_params(&mut self, map: &Map) {
181        if let Some(vertex_id) = map.selected_vertices.first() {
182            if let Some(vertex) = map.find_vertex(*vertex_id) {
183                self.nodeui
184                    .set_text_value("actionVertexName", vertex.name.clone());
185                self.nodeui.set_bool_value(
186                    "actionTerrain",
187                    vertex.properties.get_bool_default("terrain_control", false),
188                );
189                self.nodeui.set_f32_value(
190                    "actionTerrainSmoothness",
191                    vertex.properties.get_float_default("smoothness", 1.0),
192                );
193                self.nodeui.set_f32_value(
194                    "actionTerrainTileFalloff",
195                    vertex
196                        .properties
197                        .get_float_default("terrain_tile_falloff", 1.0),
198                );
199                let terrain_tile_id = if let Some(Value::Source(PixelSource::TileId(id))) =
200                    vertex.properties.get("terrain_source")
201                {
202                    *id
203                } else {
204                    Uuid::nil()
205                };
206                self.nodeui.set_text_value(
207                    "actionTerrainTileId",
208                    if terrain_tile_id == Uuid::nil() {
209                        String::new()
210                    } else {
211                        terrain_tile_id.to_string()
212                    },
213                );
214                if let Some(item) = self.nodeui.get_item_mut("actionTerrainTile")
215                    && let TheNodeUIItem::Icons(_, _, _, items) = item
216                    && items.len() == 1
217                {
218                    items[0].2 = terrain_tile_id;
219                }
220
221                self.nodeui.set_f32_value(
222                    "actionSize",
223                    vertex.properties.get_float_default("source_size", 1.0),
224                );
225                let billboard_tile_id = if let Some(Value::Source(PixelSource::TileId(id))) =
226                    vertex.properties.get("source")
227                {
228                    *id
229                } else {
230                    Uuid::nil()
231                };
232                self.nodeui.set_text_value(
233                    "actionTileId",
234                    if billboard_tile_id == Uuid::nil() {
235                        String::new()
236                    } else {
237                        billboard_tile_id.to_string()
238                    },
239                );
240                if let Some(item) = self.nodeui.get_item_mut("actionTile")
241                    && let TheNodeUIItem::Icons(_, _, _, items) = item
242                    && items.len() == 1
243                {
244                    items[0].2 = billboard_tile_id;
245                }
246
247                self.nodeui.set_f32_value("actionVertexX", vertex.x);
248                self.nodeui.set_f32_value("actionVertexY", vertex.z);
249                self.nodeui.set_f32_value("actionVertexZ", vertex.y);
250            }
251        }
252    }
253
254    fn load_params_project(&mut self, project: &Project, server_ctx: &mut ServerContext) {
255        let show_terrain = server_ctx.get_map_context() == MapContext::Region;
256        if show_terrain != self.show_terrain {
257            let name = self
258                .nodeui
259                .get_text_value("actionVertexName")
260                .unwrap_or_default();
261            let x = self.nodeui.get_f32_value("actionVertexX").unwrap_or(0.0);
262            let y = self.nodeui.get_f32_value("actionVertexY").unwrap_or(0.0);
263            let z = self.nodeui.get_f32_value("actionVertexZ").unwrap_or(0.0);
264            let tile_size = self.nodeui.get_f32_value("actionSize").unwrap_or(1.0);
265            let tile_id_text = self
266                .nodeui
267                .get_text_value("actionTileId")
268                .unwrap_or_default();
269            let tile_id = self
270                .nodeui
271                .get_tile_id("actionTile", 0)
272                .unwrap_or(Uuid::nil());
273            let terrain_control = self.nodeui.get_bool_value("actionTerrain").unwrap_or(false);
274            let terrain_smoothness = self
275                .nodeui
276                .get_f32_value("actionTerrainSmoothness")
277                .unwrap_or(1.0);
278            let terrain_tile_falloff = self
279                .nodeui
280                .get_f32_value("actionTerrainTileFalloff")
281                .unwrap_or(1.0);
282            let terrain_tile_id_text = self
283                .nodeui
284                .get_text_value("actionTerrainTileId")
285                .unwrap_or_default();
286            let terrain_tile_id = self
287                .nodeui
288                .get_tile_id("actionTerrainTile", 0)
289                .unwrap_or(Uuid::nil());
290
291            self.nodeui = Self::build_nodeui(show_terrain);
292            self.show_terrain = show_terrain;
293
294            self.nodeui.set_text_value("actionVertexName", name);
295            self.nodeui.set_f32_value("actionVertexX", x);
296            self.nodeui.set_f32_value("actionVertexY", y);
297            self.nodeui.set_f32_value("actionVertexZ", z);
298            self.nodeui.set_f32_value("actionSize", tile_size);
299            self.nodeui.set_text_value("actionTileId", tile_id_text);
300            if let Some(item) = self.nodeui.get_item_mut("actionTile")
301                && let TheNodeUIItem::Icons(_, _, _, items) = item
302                && items.len() == 1
303            {
304                items[0].2 = tile_id;
305            }
306            self.nodeui.set_bool_value("actionTerrain", terrain_control);
307            self.nodeui
308                .set_f32_value("actionTerrainSmoothness", terrain_smoothness);
309            self.nodeui
310                .set_f32_value("actionTerrainTileFalloff", terrain_tile_falloff);
311            self.nodeui
312                .set_text_value("actionTerrainTileId", terrain_tile_id_text);
313            if let Some(item) = self.nodeui.get_item_mut("actionTerrainTile")
314                && let TheNodeUIItem::Icons(_, _, _, items) = item
315                && items.len() == 1
316            {
317                items[0].2 = terrain_tile_id;
318            }
319        }
320
321        let mut tile_icon = TheRGBABuffer::new(TheDim::sized(36, 36));
322        let mut tile_id = Uuid::nil();
323        let mut terrain_tile_icon = TheRGBABuffer::new(TheDim::sized(36, 36));
324        let mut terrain_tile_id = Uuid::nil();
325
326        if let Some(map) = project.get_map(server_ctx) {
327            if let Some(vertex_id) = map.selected_vertices.first() {
328                if let Some(vertex) = map.find_vertex(*vertex_id) {
329                    if let Some(Value::Source(PixelSource::TileId(id))) =
330                        vertex.properties.get("source")
331                    {
332                        if let Some(tile) = project.tiles.get(id)
333                            && !tile.is_empty()
334                        {
335                            tile_icon = tile.textures[0].to_rgba();
336                            tile_id = *id;
337                        }
338                    }
339                    if let Some(Value::Source(PixelSource::TileId(id))) =
340                        vertex.properties.get("terrain_source")
341                    {
342                        if let Some(tile) = project.tiles.get(id)
343                            && !tile.is_empty()
344                        {
345                            terrain_tile_icon = tile.textures[0].to_rgba();
346                            terrain_tile_id = *id;
347                        }
348                    }
349                }
350            }
351        }
352
353        if let Some(item) = self.nodeui.get_item_mut("actionTile") {
354            match item {
355                TheNodeUIItem::Icons(_, _, _, items) => {
356                    if items.len() == 1 {
357                        items[0].0 = tile_icon;
358                        items[0].2 = tile_id;
359                    }
360                }
361                _ => {}
362            }
363        }
364
365        if let Some(item) = self.nodeui.get_item_mut("actionTerrainTile") {
366            match item {
367                TheNodeUIItem::Icons(_, _, _, items) => {
368                    if items.len() == 1 {
369                        items[0].0 = terrain_tile_icon;
370                        items[0].2 = terrain_tile_id;
371                    }
372                }
373                _ => {}
374            }
375        }
376    }
377
378    fn apply(
379        &self,
380        map: &mut Map,
381        _ui: &mut TheUI,
382        _ctx: &mut TheContext,
383        server_ctx: &mut ServerContext,
384    ) -> Option<ProjectUndoAtom> {
385        let mut changed = false;
386        let prev = map.clone();
387
388        let name = self
389            .nodeui
390            .get_text_value("actionVertexName")
391            .unwrap_or(String::new());
392
393        let terrain_control = self.nodeui.get_bool_value("actionTerrain").unwrap_or(false);
394
395        let terrain_smoothness = self
396            .nodeui
397            .get_f32_value("actionTerrainSmoothness")
398            .unwrap_or(1.0);
399        let terrain_tile_falloff = self
400            .nodeui
401            .get_f32_value("actionTerrainTileFalloff")
402            .unwrap_or(1.0);
403        let terrain_tile_id = self
404            .nodeui
405            .get_tile_id("actionTerrainTile", 0)
406            .unwrap_or(Uuid::nil());
407        let terrain_tile_id_text = self
408            .nodeui
409            .get_text_value("actionTerrainTileId")
410            .unwrap_or_default();
411        let terrain_tile_id = if let Ok(id) = Uuid::parse_str(terrain_tile_id_text.trim()) {
412            id
413        } else {
414            terrain_tile_id
415        };
416        let is_region_mode = server_ctx.get_map_context() == MapContext::Region;
417
418        let tile_size = self.nodeui.get_f32_value("actionSize").unwrap_or(1.0);
419
420        let tile_id = self
421            .nodeui
422            .get_tile_id("actionTile", 0)
423            .unwrap_or(Uuid::nil());
424        let tile_id_text = self
425            .nodeui
426            .get_text_value("actionTileId")
427            .unwrap_or_default();
428        let tile_id = if let Ok(id) = Uuid::parse_str(tile_id_text.trim()) {
429            id
430        } else {
431            tile_id
432        };
433
434        let x = self.nodeui.get_f32_value("actionVertexX").unwrap_or(0.0);
435        let y = self.nodeui.get_f32_value("actionVertexY").unwrap_or(0.0);
436        let z = self.nodeui.get_f32_value("actionVertexZ").unwrap_or(0.0);
437
438        if let Some(vertex_id) = map.selected_vertices.first() {
439            if let Some(vertex) = map.find_vertex_mut(*vertex_id) {
440                if is_region_mode {
441                    let ex_terrain_control =
442                        vertex.properties.get_bool_default("terrain_control", false);
443
444                    if ex_terrain_control != terrain_control {
445                        vertex
446                            .properties
447                            .set("terrain_control", Value::Bool(terrain_control));
448                        changed = true;
449                    }
450
451                    let ex_terrain_smoothness =
452                        vertex.properties.get_float_default("smoothness", 1.0);
453                    if ex_terrain_smoothness != terrain_smoothness {
454                        vertex
455                            .properties
456                            .set("smoothness", Value::Float(terrain_smoothness));
457                        changed = true;
458                    }
459
460                    let ex_terrain_tile_falloff = vertex
461                        .properties
462                        .get_float_default("terrain_tile_falloff", 1.0);
463                    if (ex_terrain_tile_falloff - terrain_tile_falloff).abs() > f32::EPSILON {
464                        vertex.properties.set(
465                            "terrain_tile_falloff",
466                            Value::Float(terrain_tile_falloff.max(0.0)),
467                        );
468                        changed = true;
469                    }
470
471                    match terrain_tile_id {
472                        id if id != Uuid::nil() => {
473                            let has_changed = match vertex.properties.get("terrain_source") {
474                                Some(Value::Source(PixelSource::TileId(existing))) => {
475                                    *existing != id
476                                }
477                                _ => true,
478                            };
479                            if has_changed {
480                                vertex
481                                    .properties
482                                    .set("terrain_source", Value::Source(PixelSource::TileId(id)));
483                                changed = true;
484                            }
485                        }
486                        _ => {
487                            if vertex.properties.contains("terrain_source") {
488                                vertex.properties.remove("terrain_source");
489                                changed = true;
490                            }
491                        }
492                    }
493                }
494
495                let ex_tile_size = vertex.properties.get_float_default("source_size", 1.0);
496                if ex_tile_size != tile_size {
497                    vertex
498                        .properties
499                        .set("source_size", Value::Float(tile_size));
500                    changed = true;
501                }
502
503                if tile_id != Uuid::nil() {
504                    let has_changed = match vertex.properties.get("source") {
505                        Some(Value::Source(PixelSource::TileId(existing))) => *existing != tile_id,
506                        _ => true,
507                    };
508                    if has_changed {
509                        vertex.properties.set(
510                            "source",
511                            Value::Source(rusterix::PixelSource::TileId(tile_id)),
512                        );
513                        changed = true;
514                    }
515                } else if vertex.properties.contains("source") {
516                    vertex.properties.remove("source");
517                    changed = true;
518                }
519
520                if name != vertex.name {
521                    vertex.name = name;
522                    changed = true;
523                }
524                if x != vertex.x {
525                    vertex.x = x;
526                    changed = true;
527                }
528                // World space to vertex space mapping
529                if y != vertex.z {
530                    vertex.z = y;
531                    changed = true;
532                }
533                if z != vertex.y {
534                    vertex.y = z;
535                    changed = true;
536                }
537            }
538        }
539
540        if changed {
541            Some(ProjectUndoAtom::MapEdit(
542                server_ctx.pc,
543                Box::new(prev),
544                Box::new(map.clone()),
545            ))
546        } else {
547            None
548        }
549    }
550
551    fn params(&self) -> TheNodeUI {
552        self.nodeui.clone()
553    }
554
555    fn handle_event(
556        &mut self,
557        event: &TheEvent,
558        project: &mut Project,
559        _ui: &mut TheUI,
560        ctx: &mut TheContext,
561        _server_ctx: &mut ServerContext,
562    ) -> bool {
563        if let TheEvent::TileDropped(id, tile_id, index) = event {
564            if let Some(item) = self.nodeui.get_item_mut(&id.name) {
565                match item {
566                    TheNodeUIItem::Icons(_, _, _, items) => {
567                        if *index < items.len() {
568                            if let Some(tile) = project.tiles.get(tile_id)
569                                && !tile.is_empty()
570                            {
571                                items[*index].0 = tile.textures[0].to_rgba();
572                                items[*index].2 = *tile_id;
573                                if id.name == "actionTile" {
574                                    self.nodeui
575                                        .set_text_value("actionTileId", tile_id.to_string());
576                                }
577                                if id.name == "actionTerrainTile" {
578                                    self.nodeui
579                                        .set_text_value("actionTerrainTileId", tile_id.to_string());
580                                }
581                                ctx.ui.send(TheEvent::Custom(
582                                    TheId::named("Update Action List"),
583                                    TheValue::Empty,
584                                ));
585                                return true;
586                            }
587                        }
588                    }
589                    _ => {}
590                }
591            }
592        }
593        self.nodeui.handle_event(event)
594    }
595}