vmf_forge/vmf/
common.rs

1//! This module provides common structures and functions used across the VMF parser.
2
3use indexmap::IndexMap;
4#[cfg(feature = "serialization")]
5use serde::{Deserialize, Serialize};
6
7use crate::utils::{get_key_ref, take_and_parse_key, take_key_or_default};
8use crate::{
9    VmfBlock, VmfSerializable,
10    errors::{VmfError, VmfResult},
11    utils::To01String,
12};
13
14/// Represents the editor data of a VMF entity or solid.
15#[derive(Debug, Clone, PartialEq, Eq)]
16#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
17pub struct Editor {
18    /// The color of the entity in the editor, in "R G B" format.
19    pub color: String,
20    /// The ID of the visgroup this entity is in, if any.
21    #[cfg_attr(
22        feature = "serialization",
23        serde(default, skip_serializing_if = "Option::is_none")
24    )]
25    pub visgroup_id: Option<i32>,
26    /// The ID of the group this entity is in, if any.
27    #[cfg_attr(
28        feature = "serialization",
29        serde(default, skip_serializing_if = "Option::is_none")
30    )]
31    pub group_id: Option<i32>,
32    /// Whether the entity is shown in the visgroup.
33    pub visgroup_shown: bool,
34    /// Whether the entity should automatically be shown in the visgroup.
35    pub visgroup_auto_shown: bool,
36    /// Comments associated with the entity, if any.
37    #[cfg_attr(
38        feature = "serialization",
39        serde(default, skip_serializing_if = "Option::is_none")
40    )]
41    pub comments: Option<String>,
42    /// The logical position of the entity in the editor, in "[x y]" format.
43    #[cfg_attr(
44        feature = "serialization",
45        serde(default, skip_serializing_if = "Option::is_none")
46    )]
47    pub logical_pos: Option<String>,
48}
49
50impl Default for Editor {
51    fn default() -> Self {
52        Self {
53            color: "255 255 255".to_string(),
54            visgroup_id: None,
55            group_id: None,
56            visgroup_shown: true,
57            visgroup_auto_shown: true,
58            comments: None,
59            logical_pos: None,
60        }
61    }
62}
63
64impl TryFrom<VmfBlock> for Editor {
65    type Error = VmfError;
66
67    fn try_from(mut block: VmfBlock) -> VmfResult<Self> {
68        let kv = &mut block.key_values;
69
70        Ok(Self {
71            color: take_key_or_default(kv, "color", "255 255 255".to_string()),
72            visgroup_id: take_and_parse_key::<i32>(kv, "visgroupid").ok(),
73            group_id: take_and_parse_key::<i32>(kv, "groupid").ok(),
74            visgroup_shown: get_key_ref(kv, "visgroupshown").is_ok_and(|v| v == "1"),
75            visgroup_auto_shown: get_key_ref(kv, "visgroupautoshown").is_ok_and(|v| v == "1"),
76            comments: kv.swap_remove("comments"),
77            logical_pos: kv.swap_remove("logicalpos"),
78        })
79    }
80}
81
82impl From<Editor> for VmfBlock {
83    fn from(val: Editor) -> VmfBlock {
84        let mut key_values = IndexMap::new();
85        key_values.insert("color".to_string(), val.color);
86        if let Some(visgroup_id) = val.visgroup_id {
87            key_values.insert("visgroupid".to_string(), visgroup_id.to_string());
88        }
89        if let Some(group_id) = val.group_id {
90            key_values.insert("groupid".to_string(), group_id.to_string());
91        }
92        key_values.insert(
93            "visgroupshown".to_string(),
94            val.visgroup_shown.to_01_string(),
95        );
96        key_values.insert(
97            "visgroupautoshown".to_string(),
98            val.visgroup_auto_shown.to_01_string(),
99        );
100        if let Some(comments) = val.comments {
101            key_values.insert("comments".to_string(), comments);
102        }
103        if let Some(logical_pos) = val.logical_pos {
104            key_values.insert("logicalpos".to_string(), logical_pos);
105        }
106
107        VmfBlock {
108            name: "editor".to_string(),
109            key_values,
110            blocks: Vec::new(),
111        }
112    }
113}
114
115impl VmfSerializable for Editor {
116    fn to_vmf_string(&self, indent_level: usize) -> String {
117        let indent = "\t".repeat(indent_level);
118        let mut output = String::with_capacity(128);
119
120        output.push_str(&format!("{0}editor\n{0}{{\n", indent));
121        output.push_str(&format!("{}\t\"color\" \"{}\"\n", indent, self.color));
122        if let Some(visgroup_id) = self.visgroup_id {
123            output.push_str(&format!("{}\t\"visgroupid\" \"{}\"\n", indent, visgroup_id));
124        }
125        if let Some(group_id) = self.group_id {
126            output.push_str(&format!("{}\t\"groupid\" \"{}\"\n", indent, group_id));
127        }
128        output.push_str(&format!(
129            "{}\t\"visgroupshown\" \"{}\"\n",
130            indent,
131            self.visgroup_shown.to_01_string()
132        ));
133        output.push_str(&format!(
134            "{}\t\"visgroupautoshown\" \"{}\"\n",
135            indent,
136            self.visgroup_auto_shown.to_01_string()
137        ));
138        if let Some(comments) = &self.comments {
139            output.push_str(&format!("{}\t\"comments\" \"{}\"\n", indent, comments));
140        }
141        if let Some(logical_pos) = &self.logical_pos {
142            output.push_str(&format!("{}\t\"logicalpos\" \"{}\"\n", indent, logical_pos));
143        }
144
145        output.push_str(&format!("{}}}\n", indent));
146        output
147    }
148}