1use std::collections::BTreeMap;
4
5use dotloom_geometry::{
6 Affine, Shape,
7 units::{LengthUnit, TimeAxis},
8};
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11
12use crate::{Color, EntityId, GroupId, LayerId, PropValue, Style, TypeId};
13
14fn one() -> u32 {
15 1
16}
17
18#[allow(clippy::trivially_copy_pass_by_ref)]
19fn is_one(v: &u32) -> bool {
20 *v == 1
21}
22
23#[allow(clippy::trivially_copy_pass_by_ref)]
24fn is_false(v: &bool) -> bool {
25 !*v
26}
27
28#[allow(clippy::trivially_copy_pass_by_ref)]
29fn is_identity(a: &Affine) -> bool {
30 a.is_identity()
31}
32
33fn yes() -> bool {
34 true
35}
36
37#[allow(clippy::trivially_copy_pass_by_ref)]
38fn is_true(v: &bool) -> bool {
39 *v
40}
41
42#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
49#[serde(rename_all = "camelCase")]
50pub struct Entity {
51 pub id: EntityId,
53 #[serde(rename = "type")]
55 pub type_id: TypeId,
56 #[serde(default = "one", skip_serializing_if = "is_one")]
58 pub type_version: u32,
59 pub layer: LayerId,
61 #[serde(default, skip_serializing_if = "is_identity")]
63 pub transform: Affine,
64 #[serde(default, skip_serializing_if = "Option::is_none")]
66 pub geometry: Option<Shape>,
67 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
69 pub props: BTreeMap<String, PropValue>,
70 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
72 pub data: BTreeMap<String, Value>,
73 #[serde(default, skip_serializing_if = "Option::is_none")]
75 pub name: Option<String>,
76 #[serde(default, skip_serializing_if = "Style::is_default")]
78 pub style: Style,
79 #[serde(default, skip_serializing_if = "is_false")]
81 pub locked: bool,
82 #[serde(default, skip_serializing_if = "is_false")]
84 pub hidden: bool,
85 #[serde(default, skip_serializing_if = "Option::is_none")]
88 pub fallback: Option<Vec<Shape>>,
89 #[serde(flatten)]
91 pub extra: BTreeMap<String, Value>,
92}
93
94impl Entity {
95 #[must_use]
97 pub fn new(id: EntityId, type_id: TypeId, layer: LayerId) -> Self {
98 Self {
99 id,
100 type_id,
101 type_version: 1,
102 layer,
103 transform: Affine::IDENTITY,
104 geometry: None,
105 props: BTreeMap::new(),
106 data: BTreeMap::new(),
107 name: None,
108 style: Style::default(),
109 locked: false,
110 hidden: false,
111 fallback: None,
112 extra: BTreeMap::new(),
113 }
114 }
115
116 #[must_use]
118 pub fn with_geometry(mut self, s: Shape) -> Self {
119 self.geometry = Some(s);
120 self
121 }
122
123 #[must_use]
125 pub fn with_prop(mut self, k: impl Into<String>, v: PropValue) -> Self {
126 self.props.insert(k.into(), v);
127 self
128 }
129
130 pub fn referenced_entities(&self) -> impl Iterator<Item = EntityId> + '_ {
132 self.props.values().filter_map(PropValue::referenced_entity)
133 }
134}
135
136#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
138#[serde(rename_all = "camelCase")]
139pub struct Layer {
140 pub id: LayerId,
142 pub name: String,
144 #[serde(default = "yes", skip_serializing_if = "is_true")]
146 pub visible: bool,
147 #[serde(default, skip_serializing_if = "is_false")]
149 pub locked: bool,
150 #[serde(default, skip_serializing_if = "Option::is_none")]
152 pub color: Option<Color>,
153 #[serde(flatten)]
155 pub extra: BTreeMap<String, Value>,
156}
157
158impl Layer {
159 #[must_use]
161 pub fn new(id: LayerId, name: impl Into<String>) -> Self {
162 Self { id, name: name.into(), visible: true, locked: false, color: None, extra: BTreeMap::new() }
163 }
164}
165
166#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
169#[serde(rename_all = "camelCase")]
170pub struct Group {
171 pub id: GroupId,
173 #[serde(default, skip_serializing_if = "Option::is_none")]
175 pub name: Option<String>,
176 #[serde(default)]
178 pub members: Vec<EntityId>,
179 #[serde(default, skip_serializing_if = "Vec::is_empty")]
181 pub children: Vec<GroupId>,
182 #[serde(flatten)]
184 pub extra: BTreeMap<String, Value>,
185}
186
187#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
189#[serde(rename_all = "camelCase")]
190pub struct Meta {
191 #[serde(default, skip_serializing_if = "String::is_empty")]
193 pub title: String,
194 #[serde(flatten)]
196 pub extra: BTreeMap<String, Value>,
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
201#[serde(rename_all = "camelCase")]
202pub struct GridSettings {
203 pub spacing: f64,
205 pub major_every: u32,
207}
208
209impl Default for GridSettings {
210 fn default() -> Self {
211 Self { spacing: 10.0, major_every: 10 }
212 }
213}
214
215#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
217#[serde(rename_all = "camelCase")]
218pub struct Settings {
219 #[serde(default)]
221 pub display_unit: LengthUnit,
222 #[serde(default)]
224 pub grid: GridSettings,
225 #[serde(default, skip_serializing_if = "Option::is_none")]
227 pub time_axis: Option<TimeAxis>,
228 #[serde(flatten)]
230 pub extra: BTreeMap<String, Value>,
231}
232
233impl Default for Settings {
234 fn default() -> Self {
235 Self {
236 display_unit: LengthUnit::Millimetre,
237 grid: GridSettings::default(),
238 time_axis: None,
239 extra: BTreeMap::new(),
240 }
241 }
242}