Skip to main content

rustapi/actions/
mod.rs

1pub use crate::prelude::*;
2
3pub mod add_arch;
4pub mod apply_tile;
5pub mod clear_palette;
6pub mod clear_profile;
7pub mod clear_tile;
8pub mod copy_tile_id;
9pub mod copy_vcode;
10pub mod create_campfire;
11pub mod create_center_vertex;
12pub mod create_fence;
13pub mod create_linedef;
14pub mod create_palisade;
15pub mod create_prop;
16pub mod create_roof;
17pub mod create_sector;
18pub mod create_stairs;
19pub mod duplicate;
20pub mod duplicate_tile;
21pub mod edit_linedef;
22pub mod edit_maximize;
23pub mod edit_sector;
24pub mod edit_tile_meta;
25pub mod edit_vertex;
26pub mod editing_camera;
27pub mod editing_slice;
28pub mod export_vcode;
29pub mod extrude_linedef;
30pub mod extrude_sector;
31pub mod firstp_camera;
32pub mod gate_door;
33pub mod import_palette;
34pub mod import_vcode;
35pub mod iso_camera;
36pub mod minimize;
37pub mod new_tile;
38pub mod orbit_camera;
39pub mod paste_vcode;
40pub mod recess;
41pub mod relief;
42pub mod remap_tile;
43pub mod set_editing_surface;
44pub mod set_tile_material;
45pub mod split;
46pub mod toggle_editing_geo;
47pub mod toggle_rect_geo;
48pub mod window;
49
50#[derive(PartialEq)]
51pub enum ActionRole {
52    Camera,
53    Editor,
54    Dock,
55}
56
57impl ActionRole {
58    pub fn to_color(&self) -> [u8; 4] {
59        match self {
60            ActionRole::Camera => [160, 175, 190, 255],
61            ActionRole::Editor => [195, 170, 150, 255],
62            ActionRole::Dock => [200, 195, 150, 255],
63            // ActionRole::Profile => [160, 185, 160, 255],
64        }
65    }
66}
67
68#[allow(unused)]
69pub trait Action: Send + Sync {
70    fn new() -> Self
71    where
72        Self: Sized;
73
74    fn id(&self) -> TheId;
75    fn info(&self) -> String;
76    fn role(&self) -> ActionRole;
77
78    fn accel(&self) -> Option<TheAccelerator> {
79        None
80    }
81
82    fn is_applicable(&self, map: &Map, ctx: &mut TheContext, server_ctx: &ServerContext) -> bool;
83
84    fn load_params(&mut self, map: &Map) {}
85    fn load_params_project(&mut self, project: &Project, server_ctx: &mut ServerContext) {}
86
87    fn apply(
88        &self,
89        map: &mut Map,
90        ui: &mut TheUI,
91        ctx: &mut TheContext,
92        server_ctx: &mut ServerContext,
93    ) -> Option<ProjectUndoAtom> {
94        None
95    }
96
97    fn apply_project(
98        &self,
99        project: &mut Project,
100        ui: &mut TheUI,
101        ctx: &mut TheContext,
102        server_ctx: &mut ServerContext,
103    ) {
104    }
105
106    fn params(&self) -> TheNodeUI;
107
108    fn handle_event(
109        &mut self,
110        event: &TheEvent,
111        project: &mut Project,
112        ui: &mut TheUI,
113        ctx: &mut TheContext,
114        server_ctx: &mut ServerContext,
115    ) -> bool;
116}
117
118fn normalize_toml_key(key: &str) -> String {
119    let mut out = String::new();
120    let mut prev_is_sep = false;
121
122    for (i, ch) in key.chars().enumerate() {
123        if ch.is_ascii_alphanumeric() {
124            if ch.is_ascii_uppercase() {
125                if i > 0 && !prev_is_sep {
126                    out.push('_');
127                }
128                out.push(ch.to_ascii_lowercase());
129            } else {
130                out.push(ch.to_ascii_lowercase());
131            }
132            prev_is_sep = false;
133        } else if !prev_is_sep && !out.is_empty() {
134            out.push('_');
135            prev_is_sep = true;
136        }
137    }
138
139    out.trim_matches('_').to_string()
140}
141
142fn action_param_key(id: &str) -> String {
143    let key = normalize_toml_key(id);
144    key.strip_prefix("action_").unwrap_or(&key).to_string()
145}
146
147fn round_f64_3(v: f64) -> f64 {
148    (v * 1000.0).round() / 1000.0
149}
150
151fn root_table_prefix(nodeui: &TheNodeUI) -> Option<String> {
152    let mut section_stack: Vec<String> = vec![];
153    let mut prefix: Option<String> = None;
154    let mut saw = false;
155
156    for (_, item) in nodeui.list_items() {
157        match item {
158            TheNodeUIItem::OpenTree(name) => section_stack.push(normalize_toml_key(name)),
159            TheNodeUIItem::CloseTree => {
160                section_stack.pop();
161            }
162            TheNodeUIItem::Text(id, _, _, _, _, _)
163            | TheNodeUIItem::Selector(id, _, _, _, _)
164            | TheNodeUIItem::FloatEditSlider(id, _, _, _, _, _)
165            | TheNodeUIItem::FloatSlider(id, _, _, _, _, _, _)
166            | TheNodeUIItem::IntEditSlider(id, _, _, _, _, _)
167            | TheNodeUIItem::PaletteSlider(id, _, _, _, _, _)
168            | TheNodeUIItem::IntSlider(id, _, _, _, _, _, _)
169            | TheNodeUIItem::ColorPicker(id, _, _, _, _)
170            | TheNodeUIItem::Checkbox(id, _, _, _) => {
171                if section_stack.is_empty() {
172                    let key = action_param_key(id);
173                    if let Some((p, _)) = key.split_once('_') {
174                        let p = p.to_string();
175                        match &prefix {
176                            None => prefix = Some(p),
177                            Some(curr) if curr == &p => {}
178                            Some(_) => return None,
179                        }
180                        saw = true;
181                    } else {
182                        return None;
183                    }
184                }
185            }
186            TheNodeUIItem::Button(_, _, _, _)
187            | TheNodeUIItem::Markdown(_, _)
188            | TheNodeUIItem::Separator(_)
189            | TheNodeUIItem::Icons(_, _, _, _) => {}
190        }
191    }
192
193    if saw { prefix } else { None }
194}
195
196fn display_key_for_storage(
197    action_key: &str,
198    section_stack: &[String],
199    root_prefix: Option<&str>,
200) -> String {
201    if let Some(section) = section_stack.last() {
202        let needle = section.clone() + "_";
203        if let Some(pos) = action_key.find(&needle) {
204            let start = pos + needle.len();
205            if start < action_key.len() {
206                return action_key[start..].to_string();
207            }
208        }
209        if let Some(stripped) = action_key.strip_prefix(&needle) {
210            return stripped.to_string();
211        }
212        return action_key.to_string();
213    }
214
215    if let Some(prefix) = root_prefix {
216        let needle = prefix.to_string() + "_";
217        if let Some(stripped) = action_key.strip_prefix(&needle) {
218            return stripped.to_string();
219        }
220    }
221
222    action_key.to_string()
223}
224
225fn candidate_input_keys(
226    action_key: &str,
227    section_stack: &[String],
228    root_prefix: Option<&str>,
229) -> Vec<String> {
230    let mut keys = vec![display_key_for_storage(
231        action_key,
232        section_stack,
233        root_prefix,
234    )];
235    keys.push(action_key.to_string());
236
237    if let Some(section) = section_stack.last() {
238        let needle = section.clone() + "_";
239        if let Some(stripped) = action_key.strip_prefix(&needle) {
240            keys.push(stripped.to_string());
241        }
242        if let Some(pos) = action_key.find(&needle) {
243            let start = pos + needle.len();
244            if start < action_key.len() {
245                keys.push(action_key[start..].to_string());
246            }
247        }
248    }
249
250    keys.sort();
251    keys.dedup();
252    keys
253}
254
255fn special_action_section_key(action_key: &str) -> Option<(&'static str, &'static str)> {
256    match action_key {
257        "iso_hide_on_enter" => Some(("iso", "hide_on_enter")),
258        _ => None,
259    }
260}
261
262fn section_table<'a>(table: &'a toml::Table, path: &[String]) -> Option<&'a toml::Table> {
263    if path.is_empty() {
264        return Some(table);
265    }
266
267    let key = &path[0];
268    let value = table.get(key)?;
269    let sub = value.as_table()?;
270    section_table(sub, &path[1..])
271}
272
273/// Converts action parameter UI to TOML.
274/// OpenTree / CloseTree items define nested TOML sections.
275pub fn nodeui_to_toml(nodeui: &TheNodeUI) -> String {
276    fn upsert(
277        entries: &mut Vec<(String, toml::Value, Option<String>)>,
278        key: String,
279        value: toml::Value,
280        comment: Option<String>,
281    ) {
282        if let Some((_, existing, existing_comment)) =
283            entries.iter_mut().find(|(k, _, _)| *k == key)
284        {
285            *existing = value;
286            *existing_comment = comment;
287        } else {
288            entries.push((key, value, comment));
289        }
290    }
291
292    fn selector_options_comment(values: &[String]) -> String {
293        let quoted = values
294            .iter()
295            .map(|v| format!("\"{}\"", v.replace('"', "\\\"")))
296            .collect::<Vec<_>>()
297            .join(", ");
298        format!("# {quoted}")
299    }
300
301    fn parse_string_array(value: &str) -> Vec<String> {
302        if let Ok(toml_value) = value.parse::<toml::Value>()
303            && let toml::Value::Array(items) = toml_value
304        {
305            let parsed: Vec<String> = items
306                .iter()
307                .filter_map(|item| item.as_str().map(|s| s.trim().to_string()))
308                .filter(|s| !s.is_empty())
309                .collect();
310            if !parsed.is_empty() {
311                return parsed;
312            }
313        }
314
315        value
316            .split(',')
317            .map(|item| item.trim())
318            .filter(|item| !item.is_empty())
319            .map(|item| item.trim_matches('"').to_string())
320            .collect()
321    }
322
323    fn section_entries_mut<'a>(
324        sections: &'a mut Vec<(String, Vec<(String, toml::Value, Option<String>)>)>,
325        name: &str,
326    ) -> &'a mut Vec<(String, toml::Value, Option<String>)> {
327        if let Some(index) = sections.iter().position(|(n, _)| n == name) {
328            return &mut sections[index].1;
329        }
330        sections.push((name.to_string(), Vec::new()));
331        let last = sections.len() - 1;
332        &mut sections[last].1
333    }
334
335    let mut root_action_entries: Vec<(String, toml::Value, Option<String>)> = vec![];
336    let mut sections: Vec<(String, Vec<(String, toml::Value, Option<String>)>)> = vec![];
337    let mut section_stack: Vec<String> = vec![];
338    let mut has_editable_values = false;
339    let root_prefix = root_table_prefix(nodeui);
340
341    for (_, item) in nodeui.list_items() {
342        match item {
343            TheNodeUIItem::OpenTree(name) => {
344                section_stack.push(normalize_toml_key(name));
345            }
346            TheNodeUIItem::CloseTree => {
347                section_stack.pop();
348            }
349            TheNodeUIItem::Text(id, _, _, value, _, _) => {
350                let action_key = action_param_key(id);
351                let (target_section, key) = if section_stack.is_empty() {
352                    if let Some((section, special_key)) = special_action_section_key(&action_key) {
353                        (Some(section.to_string()), special_key.to_string())
354                    } else {
355                        (
356                            None,
357                            display_key_for_storage(
358                                &action_key,
359                                &section_stack,
360                                root_prefix.as_deref(),
361                            ),
362                        )
363                    }
364                } else {
365                    (
366                        Some(section_stack.join(".")),
367                        display_key_for_storage(
368                            &action_key,
369                            &section_stack,
370                            root_prefix.as_deref(),
371                        ),
372                    )
373                };
374                let val = if action_key == "iso_hide_on_enter" {
375                    toml::Value::Array(
376                        parse_string_array(value)
377                            .into_iter()
378                            .map(toml::Value::String)
379                            .collect(),
380                    )
381                } else {
382                    toml::Value::String(value.clone())
383                };
384                if let Some(section_name) = target_section {
385                    let entries = section_entries_mut(&mut sections, &section_name);
386                    upsert(entries, key, val, None);
387                } else {
388                    upsert(&mut root_action_entries, key, val, None);
389                }
390                has_editable_values = true;
391            }
392            TheNodeUIItem::Selector(id, _, _, values, index) => {
393                let action_key = action_param_key(id);
394                let key =
395                    display_key_for_storage(&action_key, &section_stack, root_prefix.as_deref());
396                let selected = if (*index as usize) < values.len() {
397                    toml::Value::String(values[*index as usize].clone())
398                } else {
399                    toml::Value::Integer(*index as i64)
400                };
401                let comment = Some(selector_options_comment(values));
402                if section_stack.is_empty() {
403                    upsert(&mut root_action_entries, key, selected, comment);
404                } else {
405                    let section_name = section_stack.join(".");
406                    let entries = section_entries_mut(&mut sections, &section_name);
407                    upsert(entries, key, selected, comment);
408                }
409                has_editable_values = true;
410            }
411            TheNodeUIItem::FloatEditSlider(id, _, _, value, _, _)
412            | TheNodeUIItem::FloatSlider(id, _, _, value, _, _, _) => {
413                let action_key = action_param_key(id);
414                let key =
415                    display_key_for_storage(&action_key, &section_stack, root_prefix.as_deref());
416                let val = toml::Value::Float(round_f64_3(*value as f64));
417                if section_stack.is_empty() {
418                    upsert(&mut root_action_entries, key, val, None);
419                } else {
420                    let section_name = section_stack.join(".");
421                    let entries = section_entries_mut(&mut sections, &section_name);
422                    upsert(entries, key, val, None);
423                }
424                has_editable_values = true;
425            }
426            TheNodeUIItem::IntEditSlider(id, _, _, value, _, _)
427            | TheNodeUIItem::PaletteSlider(id, _, _, value, _, _)
428            | TheNodeUIItem::IntSlider(id, _, _, value, _, _, _) => {
429                let action_key = action_param_key(id);
430                let key =
431                    display_key_for_storage(&action_key, &section_stack, root_prefix.as_deref());
432                let val = toml::Value::Integer(*value as i64);
433                if section_stack.is_empty() {
434                    upsert(&mut root_action_entries, key, val, None);
435                } else {
436                    let section_name = section_stack.join(".");
437                    let entries = section_entries_mut(&mut sections, &section_name);
438                    upsert(entries, key, val, None);
439                }
440                has_editable_values = true;
441            }
442            TheNodeUIItem::ColorPicker(id, _, _, value, _) => {
443                let action_key = action_param_key(id);
444                let key =
445                    display_key_for_storage(&action_key, &section_stack, root_prefix.as_deref());
446                let val = toml::Value::String(value.to_hex());
447                if section_stack.is_empty() {
448                    upsert(&mut root_action_entries, key, val, None);
449                } else {
450                    let section_name = section_stack.join(".");
451                    let entries = section_entries_mut(&mut sections, &section_name);
452                    upsert(entries, key, val, None);
453                }
454                has_editable_values = true;
455            }
456            TheNodeUIItem::Checkbox(id, _, _, value) => {
457                let action_key = action_param_key(id);
458                let key =
459                    display_key_for_storage(&action_key, &section_stack, root_prefix.as_deref());
460                let val = toml::Value::Boolean(*value);
461                if section_stack.is_empty() {
462                    upsert(&mut root_action_entries, key, val, None);
463                } else {
464                    let section_name = section_stack.join(".");
465                    let entries = section_entries_mut(&mut sections, &section_name);
466                    upsert(entries, key, val, None);
467                }
468                has_editable_values = true;
469            }
470            TheNodeUIItem::Button(_, _, _, _)
471            | TheNodeUIItem::Markdown(_, _)
472            | TheNodeUIItem::Separator(_)
473            | TheNodeUIItem::Icons(_, _, _, _) => {}
474        }
475    }
476
477    if !has_editable_values {
478        return String::new();
479    }
480
481    let mut out = String::new();
482
483    if !root_action_entries.is_empty() {
484        out.push_str("[action]\n");
485        for (key, value, comment) in &root_action_entries {
486            if let Some(comment) = comment {
487                out.push_str(comment);
488                out.push('\n');
489            }
490            out.push_str(&format!("{key} = {value}\n"));
491        }
492    }
493
494    for (section, entries) in &sections {
495        if entries.is_empty() {
496            continue;
497        }
498        if !out.is_empty() {
499            out.push('\n');
500        }
501        out.push_str(&format!("[{section}]\n"));
502        for (key, value, comment) in entries {
503            if let Some(comment) = comment {
504                out.push_str(comment);
505                out.push('\n');
506            }
507            out.push_str(&format!("{key} = {value}\n"));
508        }
509    }
510
511    out
512}
513
514/// Applies TOML values back to action parameter UI.
515/// Unknown keys/sections are ignored.
516pub fn apply_toml_to_nodeui(nodeui: &mut TheNodeUI, source: &str) -> Result<(), String> {
517    let root: toml::Table = toml::from_str(source).map_err(|e| e.to_string())?;
518    let action_root = root
519        .get("action")
520        .and_then(|v| v.as_table())
521        .unwrap_or(&root);
522    let mut section_stack: Vec<String> = vec![];
523    let items: Vec<TheNodeUIItem> = nodeui.list_items().map(|(_, item)| item.clone()).collect();
524    let root_prefix = root_table_prefix(nodeui);
525
526    for item in items {
527        match item {
528            TheNodeUIItem::OpenTree(name) => {
529                section_stack.push(normalize_toml_key(&name));
530            }
531            TheNodeUIItem::CloseTree => {
532                section_stack.pop();
533            }
534            TheNodeUIItem::Text(id, _, _, _, _, _) => {
535                let action_key = action_param_key(&id);
536                let table = if section_stack.is_empty() {
537                    if let Some((section, _)) = special_action_section_key(&action_key) {
538                        section_table(&root, &[section.to_string()]).or(Some(action_root))
539                    } else {
540                        Some(action_root)
541                    }
542                } else {
543                    section_table(&root, &section_stack)
544                        .or_else(|| section_table(action_root, &section_stack))
545                };
546                if let Some(table) = table {
547                    let mut keys =
548                        candidate_input_keys(&action_key, &section_stack, root_prefix.as_deref());
549                    if section_stack.is_empty()
550                        && let Some((_, special_key)) = special_action_section_key(&action_key)
551                    {
552                        keys.push(special_key.to_string());
553                        keys.sort();
554                        keys.dedup();
555                    }
556                    for key in keys {
557                        if let Some(value) = table.get(&key) {
558                            match value {
559                                toml::Value::String(v) => {
560                                    nodeui.set_text_value(&id, v.clone());
561                                    break;
562                                }
563                                toml::Value::Integer(v) => {
564                                    nodeui.set_text_value(&id, v.to_string());
565                                    break;
566                                }
567                                toml::Value::Float(v) => {
568                                    nodeui.set_text_value(&id, v.to_string());
569                                    break;
570                                }
571                                toml::Value::Array(items) if action_key == "iso_hide_on_enter" => {
572                                    let joined = items
573                                        .iter()
574                                        .filter_map(|item| item.as_str())
575                                        .collect::<Vec<_>>()
576                                        .join(", ");
577                                    nodeui.set_text_value(&id, joined);
578                                    break;
579                                }
580                                _ => {}
581                            }
582                        }
583                    }
584                }
585            }
586            TheNodeUIItem::Selector(id, _, _, values, _) => {
587                let action_key = action_param_key(&id);
588                let table = if section_stack.is_empty() {
589                    Some(action_root)
590                } else {
591                    section_table(&root, &section_stack)
592                        .or_else(|| section_table(action_root, &section_stack))
593                };
594                if let Some(table) = table {
595                    for key in
596                        candidate_input_keys(&action_key, &section_stack, root_prefix.as_deref())
597                    {
598                        if let Some(value) = table.get(&key) {
599                            match value {
600                                toml::Value::Integer(v) => nodeui.set_i32_value(&id, *v as i32),
601                                toml::Value::String(name) => {
602                                    if let Some(index) = values.iter().position(|v| v == name) {
603                                        nodeui.set_i32_value(&id, index as i32);
604                                    }
605                                }
606                                _ => {}
607                            }
608                            break;
609                        }
610                    }
611                }
612            }
613            TheNodeUIItem::FloatEditSlider(id, _, _, _, _, _)
614            | TheNodeUIItem::FloatSlider(id, _, _, _, _, _, _) => {
615                let action_key = action_param_key(&id);
616                let table = if section_stack.is_empty() {
617                    Some(action_root)
618                } else {
619                    section_table(&root, &section_stack)
620                        .or_else(|| section_table(action_root, &section_stack))
621                };
622                if let Some(table) = table {
623                    for key in
624                        candidate_input_keys(&action_key, &section_stack, root_prefix.as_deref())
625                    {
626                        if let Some(value) = table.get(&key) {
627                            match value {
628                                toml::Value::Float(v) => nodeui.set_f32_value(&id, *v as f32),
629                                toml::Value::Integer(v) => nodeui.set_f32_value(&id, *v as f32),
630                                _ => {}
631                            }
632                            break;
633                        }
634                    }
635                }
636            }
637            TheNodeUIItem::IntEditSlider(id, _, _, _, _, _)
638            | TheNodeUIItem::PaletteSlider(id, _, _, _, _, _)
639            | TheNodeUIItem::IntSlider(id, _, _, _, _, _, _) => {
640                let action_key = action_param_key(&id);
641                let table = if section_stack.is_empty() {
642                    Some(action_root)
643                } else {
644                    section_table(&root, &section_stack)
645                        .or_else(|| section_table(action_root, &section_stack))
646                };
647                if let Some(table) = table {
648                    for key in
649                        candidate_input_keys(&action_key, &section_stack, root_prefix.as_deref())
650                    {
651                        if let Some(value) = table.get(&key) {
652                            if let toml::Value::Integer(v) = value {
653                                nodeui.set_i32_value(&id, *v as i32);
654                            }
655                            break;
656                        }
657                    }
658                }
659            }
660            TheNodeUIItem::ColorPicker(id, _, _, _, _) => {
661                let action_key = action_param_key(&id);
662                let table = if section_stack.is_empty() {
663                    Some(action_root)
664                } else {
665                    section_table(&root, &section_stack)
666                        .or_else(|| section_table(action_root, &section_stack))
667                };
668                if let Some(table) = table {
669                    for key in
670                        candidate_input_keys(&action_key, &section_stack, root_prefix.as_deref())
671                    {
672                        if let Some(toml::Value::String(v)) = table.get(&key) {
673                            if let Some(TheNodeUIItem::ColorPicker(_, _, _, color, _)) =
674                                nodeui.get_item_mut(&id)
675                            {
676                                *color = TheColor::from_hex(v);
677                            }
678                            break;
679                        }
680                    }
681                }
682            }
683            TheNodeUIItem::Checkbox(id, _, _, _) => {
684                let action_key = action_param_key(&id);
685                let table = if section_stack.is_empty() {
686                    Some(action_root)
687                } else {
688                    section_table(&root, &section_stack)
689                        .or_else(|| section_table(action_root, &section_stack))
690                };
691                if let Some(table) = table {
692                    for key in
693                        candidate_input_keys(&action_key, &section_stack, root_prefix.as_deref())
694                    {
695                        if let Some(toml::Value::Boolean(v)) = table.get(&key) {
696                            nodeui.set_bool_value(&id, *v);
697                            break;
698                        }
699                    }
700                }
701            }
702            TheNodeUIItem::Button(_, _, _, _)
703            | TheNodeUIItem::Markdown(_, _)
704            | TheNodeUIItem::Separator(_)
705            | TheNodeUIItem::Icons(_, _, _, _) => {}
706        }
707    }
708
709    Ok(())
710}
711
712/// Converts current NodeUI values into `(id, TheValue)` pairs suitable for
713/// replay through `Action::handle_event` using `TheEvent::ValueChanged`.
714pub fn nodeui_to_value_pairs(nodeui: &TheNodeUI) -> Vec<(String, TheValue)> {
715    let mut out: Vec<(String, TheValue)> = Vec::new();
716    for (_, item) in nodeui.list_items() {
717        match item {
718            TheNodeUIItem::Text(id, _, _, value, _, _) => {
719                out.push((id.clone(), TheValue::Text(value.clone())));
720            }
721            TheNodeUIItem::Selector(id, _, _, _, value) => {
722                out.push((id.clone(), TheValue::Int(*value)));
723            }
724            TheNodeUIItem::FloatEditSlider(id, _, _, value, _, _)
725            | TheNodeUIItem::FloatSlider(id, _, _, value, _, _, _) => {
726                out.push((id.clone(), TheValue::Float(*value)));
727            }
728            TheNodeUIItem::IntEditSlider(id, _, _, value, _, _)
729            | TheNodeUIItem::PaletteSlider(id, _, _, value, _, _)
730            | TheNodeUIItem::IntSlider(id, _, _, value, _, _, _) => {
731                out.push((id.clone(), TheValue::Int(*value)));
732            }
733            TheNodeUIItem::ColorPicker(id, _, _, value, _) => {
734                out.push((id.clone(), TheValue::ColorObject(value.clone())));
735            }
736            TheNodeUIItem::Checkbox(id, _, _, value) => {
737                out.push((id.clone(), TheValue::Bool(*value)));
738            }
739            TheNodeUIItem::Button(_, _, _, _)
740            | TheNodeUIItem::Markdown(_, _)
741            | TheNodeUIItem::Separator(_)
742            | TheNodeUIItem::Icons(_, _, _, _)
743            | TheNodeUIItem::OpenTree(_)
744            | TheNodeUIItem::CloseTree => {}
745        }
746    }
747    out
748}