1use serde::{Deserialize, Serialize};
2
3use crate::point::{Bounds, Point};
4use crate::style::{Arrowhead, FillStyle, FontStyle, StrokeStyle};
5
6macro_rules! with_element {
8 ($self:expr, $e:ident => $body:expr) => {
9 match $self {
10 Element::Rectangle($e) | Element::Ellipse($e) | Element::Diamond($e) => $body,
11 Element::Line($e) | Element::Arrow($e) => $body,
12 Element::FreeDraw($e) => $body,
13 Element::Text($e) => $body,
14 }
15 };
16}
17
18#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
19#[serde(tag = "type")]
20pub enum Element {
21 Rectangle(ShapeElement),
22 Ellipse(ShapeElement),
23 Diamond(ShapeElement),
24 Line(LineElement),
25 Arrow(LineElement),
26 FreeDraw(FreeDrawElement),
27 Text(TextElement),
28}
29
30impl Element {
31 pub fn id(&self) -> &str {
32 with_element!(self, e => &e.id)
33 }
34
35 pub fn bounds(&self) -> Bounds {
36 match self {
37 Self::Rectangle(e) | Self::Ellipse(e) | Self::Diamond(e) => {
38 Bounds::new(e.x, e.y, e.width, e.height)
39 }
40 Self::Line(e) | Self::Arrow(e) => {
41 let abs: Vec<Point> = e
42 .points
43 .iter()
44 .map(|p| Point::new(p.x + e.x, p.y + e.y))
45 .collect();
46 Bounds::from_points(&abs).unwrap_or(Bounds::new(e.x, e.y, 0.0, 0.0))
47 }
48 Self::FreeDraw(e) => {
49 let abs: Vec<Point> = e
50 .points
51 .iter()
52 .map(|p| Point::new(p.x + e.x, p.y + e.y))
53 .collect();
54 Bounds::from_points(&abs).unwrap_or(Bounds::new(e.x, e.y, 0.0, 0.0))
55 }
56 Self::Text(e) => {
57 let lines: Vec<&str> = e.text.split('\n').collect();
58 let max_len = lines.iter().map(|l| l.chars().count()).max().unwrap_or(0);
59 let width = max_len as f64 * e.font.size * 0.6;
60 let height = lines.len() as f64 * e.font.size * 1.2;
61 Bounds::new(e.x, e.y, width, height)
62 }
63 }
64 }
65
66 pub fn position(&self) -> (f64, f64) {
67 with_element!(self, e => (e.x, e.y))
68 }
69
70 pub fn set_position(&mut self, x: f64, y: f64) {
71 with_element!(self, e => { e.x = x; e.y = y; });
72 }
73
74 pub fn opacity(&self) -> f64 {
75 with_element!(self, e => e.opacity)
76 }
77
78 pub fn is_locked(&self) -> bool {
79 with_element!(self, e => e.locked)
80 }
81
82 pub fn group_id(&self) -> Option<&str> {
83 with_element!(self, e => e.group_id.as_deref())
84 }
85}
86
87#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
88pub struct ShapeElement {
89 pub id: String,
90 pub x: f64,
91 pub y: f64,
92 pub width: f64,
93 pub height: f64,
94 #[serde(default)]
95 pub angle: f64,
96 #[serde(default)]
97 pub stroke: StrokeStyle,
98 #[serde(default)]
99 pub fill: FillStyle,
100 #[serde(default = "default_opacity")]
101 pub opacity: f64,
102 #[serde(default)]
103 pub locked: bool,
104 #[serde(default)]
105 pub group_id: Option<String>,
106}
107
108impl ShapeElement {
109 pub fn new(id: String, x: f64, y: f64, width: f64, height: f64) -> Self {
110 Self {
111 id,
112 x,
113 y,
114 width,
115 height,
116 angle: 0.0,
117 stroke: StrokeStyle::default(),
118 fill: FillStyle::default(),
119 opacity: 1.0,
120 locked: false,
121 group_id: None,
122 }
123 }
124}
125
126#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
127pub struct Binding {
128 pub element_id: String,
129 pub focus: f64,
130 pub gap: f64,
131}
132
133#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
134pub struct LineElement {
135 pub id: String,
136 pub x: f64,
137 pub y: f64,
138 pub points: Vec<Point>,
139 #[serde(default)]
140 pub stroke: StrokeStyle,
141 #[serde(default)]
142 pub start_arrowhead: Option<Arrowhead>,
143 #[serde(default)]
144 pub end_arrowhead: Option<Arrowhead>,
145 #[serde(default = "default_opacity")]
146 pub opacity: f64,
147 #[serde(default)]
148 pub locked: bool,
149 #[serde(default)]
150 pub group_id: Option<String>,
151 #[serde(default)]
152 pub start_binding: Option<Binding>,
153 #[serde(default)]
154 pub end_binding: Option<Binding>,
155}
156
157impl LineElement {
158 pub fn new(id: String, x: f64, y: f64, points: Vec<Point>) -> Self {
159 Self {
160 id,
161 x,
162 y,
163 points,
164 stroke: StrokeStyle::default(),
165 start_arrowhead: None,
166 end_arrowhead: None,
167 opacity: 1.0,
168 locked: false,
169 group_id: None,
170 start_binding: None,
171 end_binding: None,
172 }
173 }
174}
175
176#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
177pub struct FreeDrawElement {
178 pub id: String,
179 pub x: f64,
180 pub y: f64,
181 pub points: Vec<Point>,
182 #[serde(default)]
183 pub stroke: StrokeStyle,
184 #[serde(default = "default_opacity")]
185 pub opacity: f64,
186 #[serde(default)]
187 pub locked: bool,
188 #[serde(default)]
189 pub group_id: Option<String>,
190}
191
192impl FreeDrawElement {
193 pub fn new(id: String, x: f64, y: f64, points: Vec<Point>) -> Self {
194 Self {
195 id,
196 x,
197 y,
198 points,
199 stroke: StrokeStyle::default(),
200 opacity: 1.0,
201 locked: false,
202 group_id: None,
203 }
204 }
205}
206
207#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
208pub struct TextElement {
209 pub id: String,
210 pub x: f64,
211 pub y: f64,
212 pub text: String,
213 #[serde(default)]
214 pub font: FontStyle,
215 #[serde(default)]
216 pub stroke: StrokeStyle,
217 #[serde(default = "default_opacity")]
218 pub opacity: f64,
219 #[serde(default)]
220 pub angle: f64,
221 #[serde(default)]
222 pub locked: bool,
223 #[serde(default)]
224 pub group_id: Option<String>,
225}
226
227impl TextElement {
228 pub fn new(id: String, x: f64, y: f64, text: String) -> Self {
229 Self {
230 id,
231 x,
232 y,
233 text,
234 font: FontStyle::default(),
235 stroke: StrokeStyle::default(),
236 opacity: 1.0,
237 angle: 0.0,
238 locked: false,
239 group_id: None,
240 }
241 }
242}
243
244fn default_opacity() -> f64 {
245 1.0
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn test_element_id() {
254 let rect = Element::Rectangle(ShapeElement::new("r1".to_string(), 0.0, 0.0, 10.0, 10.0));
255 assert_eq!(rect.id(), "r1");
256
257 let line = Element::Line(LineElement::new(
258 "l1".to_string(),
259 0.0,
260 0.0,
261 vec![Point::new(0.0, 0.0), Point::new(10.0, 10.0)],
262 ));
263 assert_eq!(line.id(), "l1");
264
265 let text = Element::Text(TextElement::new(
266 "t1".to_string(),
267 0.0,
268 0.0,
269 "hello".to_string(),
270 ));
271 assert_eq!(text.id(), "t1");
272
273 let fd = Element::FreeDraw(FreeDrawElement::new(
274 "fd1".to_string(),
275 0.0,
276 0.0,
277 vec![Point::new(0.0, 0.0)],
278 ));
279 assert_eq!(fd.id(), "fd1");
280 }
281
282 #[test]
283 fn test_element_bounds_rectangle() {
284 let rect = Element::Rectangle(ShapeElement::new("r1".to_string(), 10.0, 20.0, 100.0, 50.0));
285 let b = rect.bounds();
286 assert_eq!(b.x, 10.0);
287 assert_eq!(b.y, 20.0);
288 assert_eq!(b.width, 100.0);
289 assert_eq!(b.height, 50.0);
290 }
291
292 #[test]
293 fn test_element_bounds_ellipse() {
294 let ellipse = Element::Ellipse(ShapeElement::new("e1".to_string(), 5.0, 10.0, 80.0, 60.0));
295 let b = ellipse.bounds();
296 assert_eq!(b.x, 5.0);
297 assert_eq!(b.y, 10.0);
298 assert_eq!(b.width, 80.0);
299 assert_eq!(b.height, 60.0);
300 }
301
302 #[test]
303 fn test_element_bounds_line() {
304 let line = Element::Line(LineElement::new(
305 "l1".to_string(),
306 10.0,
307 20.0,
308 vec![Point::new(0.0, 0.0), Point::new(50.0, 30.0)],
309 ));
310 let b = line.bounds();
311 assert_eq!(b.x, 10.0);
313 assert_eq!(b.y, 20.0);
314 assert_eq!(b.width, 50.0);
315 assert_eq!(b.height, 30.0);
316 }
317
318 #[test]
319 fn test_element_bounds_freedraw() {
320 let fd = Element::FreeDraw(FreeDrawElement::new(
321 "fd1".to_string(),
322 5.0,
323 5.0,
324 vec![
325 Point::new(0.0, 0.0),
326 Point::new(10.0, 20.0),
327 Point::new(-5.0, 10.0),
328 ],
329 ));
330 let b = fd.bounds();
331 assert_eq!(b.x, 0.0);
333 assert_eq!(b.y, 5.0);
334 assert_eq!(b.width, 15.0);
335 assert_eq!(b.height, 20.0);
336 }
337
338 #[test]
339 fn test_element_bounds_text_multiline() {
340 let text = Element::Text(TextElement::new(
341 "t1".to_string(),
342 0.0,
343 0.0,
344 "hello\nworld!!".to_string(),
345 ));
346 let b = text.bounds();
347 assert!(b.width > 0.0);
351 assert!(b.height > b.width * 0.3); let single = Element::Text(TextElement::new(
354 "t2".to_string(),
355 0.0,
356 0.0,
357 "hello".to_string(),
358 ));
359 let sb = single.bounds();
360 assert!(b.height > sb.height);
361 }
362}