1#[cfg(target_arch = "wasm32")]
9use wasm_bindgen::prelude::*;
10
11use draw_core::history::{Action, History};
12use draw_core::point::{Bounds, ViewState};
13use draw_core::render::{RenderConfig, Renderer};
14use draw_core::{Document, Element};
15
16#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
21pub struct DrawEngine {
22 document: Document,
23 renderer: Renderer,
24 viewport: ViewState,
25 selected_ids: Vec<String>,
26 selection_box: Option<Bounds>,
27 history: History,
28 pixel_ratio: f32,
29}
30
31#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
36impl DrawEngine {
37 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(constructor))]
41 pub fn new(width: u32, height: u32, pixel_ratio: f32) -> Self {
42 let config = RenderConfig {
43 width,
44 height,
45 pixel_ratio,
46 ..RenderConfig::default()
47 };
48 Self {
49 document: Document::new("untitled".to_string()),
50 renderer: Renderer::new(config),
51 viewport: ViewState::default(),
52 selected_ids: Vec::new(),
53 selection_box: None,
54 history: History::new(),
55 pixel_ratio,
56 }
57 }
58
59 pub fn set_size(&mut self, width: u32, height: u32) {
63 let config = RenderConfig {
64 width,
65 height,
66 pixel_ratio: self.pixel_ratio,
67 ..RenderConfig::default()
68 };
69 self.renderer = Renderer::new(config);
70 }
71
72 pub fn load_document(&mut self, json: &str) -> bool {
76 match serde_json::from_str::<Document>(json) {
77 Ok(doc) => {
78 self.document = doc;
79 self.history = History::new();
80 self.selected_ids.clear();
81 self.selection_box = None;
82 true
83 }
84 Err(_) => false,
85 }
86 }
87
88 pub fn save_document(&self) -> String {
90 serde_json::to_string(&self.document).unwrap_or_default()
91 }
92
93 pub fn render(&self) -> Vec<u8> {
97 let sel_refs: Vec<&str> = self.selected_ids.iter().map(|s| s.as_str()).collect();
98 let pixmap = self.renderer.render(
99 &self.document,
100 &self.viewport,
101 &sel_refs,
102 self.selection_box,
103 );
104 pixmap.data().to_vec()
105 }
106
107 pub fn render_width(&self) -> u32 {
109 (self.renderer.config().width as f32 * self.pixel_ratio) as u32
110 }
111
112 pub fn render_height(&self) -> u32 {
114 (self.renderer.config().height as f32 * self.pixel_ratio) as u32
115 }
116
117 pub fn get_text_overlays(&self) -> String {
121 let zoom = self.viewport.zoom;
122 let sx = self.viewport.scroll_x;
123 let sy = self.viewport.scroll_y;
124 let pr = self.pixel_ratio as f64;
125
126 let mut overlays = Vec::new();
127 for el in &self.document.elements {
128 if let Element::Text(t) = el {
129 let screen_x = (t.x * zoom + sx) * pr;
130 let screen_y = (t.y * zoom + sy) * pr;
131 let font_size = t.font.size * zoom * pr;
132 let align = match t.font.align {
133 draw_core::style::TextAlign::Left => "left",
134 draw_core::style::TextAlign::Center => "center",
135 draw_core::style::TextAlign::Right => "right",
136 };
137 let lines: Vec<&str> = t.text.split('\n').collect();
139 let max_chars = lines.iter().map(|l| l.chars().count()).max().unwrap_or(0);
140 let width = (max_chars as f64 * t.font.size * 0.6) * zoom * pr;
141 let height = (lines.len() as f64 * t.font.size * 1.2) * zoom * pr;
142
143 overlays.push(format!(
144 r#"{{"x":{},"y":{},"text":"{}","fontSize":{},"fontFamily":"{}","align":"{}","color":"{}","opacity":{},"width":{},"height":{}}}"#,
145 screen_x, screen_y,
146 t.text.replace('\\', "\\\\").replace('"', "\\\"").replace('\n', "\\n"),
147 font_size, t.font.family, align, t.stroke.color, t.opacity,
148 width, height
149 ));
150 }
151 }
152 format!("[{}]", overlays.join(","))
153 }
154
155 pub fn hit_test(&self, screen_x: f32, screen_y: f32) -> String {
159 self.renderer
160 .hit_test(&self.document, &self.viewport, screen_x, screen_y)
161 .unwrap_or_default()
162 }
163
164 pub fn hit_test_handle(&self, screen_x: f32, screen_y: f32) -> String {
167 match self
168 .renderer
169 .hit_test_handle(&self.document, &self.viewport, screen_x, screen_y)
170 {
171 Some((id, handle)) => {
172 let handle_str = match handle {
173 draw_core::HandlePosition::NorthWest => "NorthWest",
174 draw_core::HandlePosition::NorthEast => "NorthEast",
175 draw_core::HandlePosition::SouthWest => "SouthWest",
176 draw_core::HandlePosition::SouthEast => "SouthEast",
177 };
178 format!(r#"{{"id":"{}","handle":"{}"}}"#, id, handle_str)
179 }
180 None => String::new(),
181 }
182 }
183
184 pub fn elements_in_rect(&self, x: f64, y: f64, w: f64, h: f64) -> String {
186 let rect = Bounds::new(x, y, w, h);
187 let ids = self
188 .renderer
189 .elements_in_rect(&self.document, &self.viewport, rect);
190 serde_json::to_string(&ids).unwrap_or_else(|_| "[]".to_string())
191 }
192
193 pub fn set_viewport(&mut self, scroll_x: f64, scroll_y: f64, zoom: f64) {
197 self.viewport = ViewState {
198 scroll_x,
199 scroll_y,
200 zoom,
201 };
202 }
203
204 pub fn screen_to_world(&self, sx: f64, sy: f64) -> String {
206 let wx = (sx - self.viewport.scroll_x) / self.viewport.zoom;
207 let wy = (sy - self.viewport.scroll_y) / self.viewport.zoom;
208 format!(r#"{{"x":{},"y":{}}}"#, wx, wy)
209 }
210
211 pub fn scroll_x(&self) -> f64 {
212 self.viewport.scroll_x
213 }
214
215 pub fn scroll_y(&self) -> f64 {
216 self.viewport.scroll_y
217 }
218
219 pub fn zoom(&self) -> f64 {
220 self.viewport.zoom
221 }
222
223 pub fn set_selection(&mut self, ids_json: &str) {
227 if let Ok(ids) = serde_json::from_str::<Vec<String>>(ids_json) {
228 self.selected_ids = ids;
229 }
230 }
231
232 pub fn get_selection(&self) -> String {
234 serde_json::to_string(&self.selected_ids).unwrap_or_else(|_| "[]".to_string())
235 }
236
237 pub fn set_selection_box(&mut self, x: f64, y: f64, w: f64, h: f64) {
239 self.selection_box = Some(Bounds::new(x, y, w, h));
240 }
241
242 pub fn clear_selection_box(&mut self) {
244 self.selection_box = None;
245 }
246
247 pub fn add_element(&mut self, json: &str) -> String {
251 match serde_json::from_str::<Element>(json) {
252 Ok(el) => {
253 let id = el.id().to_string();
254 self.history.push(Action::AddElement(Box::new(el.clone())));
255 self.document.add_element(el);
256 id
257 }
258 Err(_) => String::new(),
259 }
260 }
261
262 pub fn remove_element(&mut self, id: &str) -> bool {
264 if let Some(el) = self.document.remove_element(id) {
265 self.history
266 .push(Action::RemoveElement(id.to_string(), Box::new(el)));
267 self.selected_ids.retain(|s| s != id);
268 true
269 } else {
270 false
271 }
272 }
273
274 pub fn move_element(&mut self, id: &str, x: f64, y: f64) {
276 if let Some(el) = self.document.get_element(id) {
277 let bounds = el.bounds();
278 let dx = x - bounds.x;
279 let dy = y - bounds.y;
280 self.history.push(Action::MoveElement {
281 id: id.to_string(),
282 dx,
283 dy,
284 });
285 }
286 if let Some(el) = self.document.get_element_mut(id) {
288 match el {
289 Element::Rectangle(e) | Element::Ellipse(e) | Element::Diamond(e) => {
290 e.x = x;
291 e.y = y;
292 }
293 Element::Line(e) | Element::Arrow(e) => {
294 e.x = x;
295 e.y = y;
296 }
297 Element::FreeDraw(e) => {
298 e.x = x;
299 e.y = y;
300 }
301 Element::Text(e) => {
302 e.x = x;
303 e.y = y;
304 }
305 }
306 }
307 }
308
309 pub fn resize_element(&mut self, id: &str, x: f64, y: f64, w: f64, h: f64) {
311 if let Some(el) = self.document.get_element(id) {
312 let b = el.bounds();
313 self.history.push(Action::ResizeElement {
314 id: id.to_string(),
315 old_x: b.x,
316 old_y: b.y,
317 old_width: b.width,
318 old_height: b.height,
319 new_x: x,
320 new_y: y,
321 new_width: w,
322 new_height: h,
323 });
324 }
325 if let Some(el) = self.document.get_element_mut(id) {
326 match el {
327 Element::Rectangle(e) | Element::Ellipse(e) | Element::Diamond(e) => {
328 e.x = x;
329 e.y = y;
330 e.width = w;
331 e.height = h;
332 }
333 _ => {
334 match el {
337 Element::Line(e) | Element::Arrow(e) => {
338 e.x = x;
339 e.y = y;
340 }
341 Element::FreeDraw(e) => {
342 e.x = x;
343 e.y = y;
344 }
345 Element::Text(e) => {
346 e.x = x;
347 e.y = y;
348 }
349 _ => {}
350 }
351 }
352 }
353 }
354 }
355
356 pub fn update_element_style(&mut self, id: &str, style_json: &str) -> bool {
359 let before = self.document.get_element(id).cloned();
361 let before = match before {
362 Some(b) => b,
363 None => return false,
364 };
365
366 let updates: serde_json::Value = match serde_json::from_str(style_json) {
368 Ok(v) => v,
369 Err(_) => return false,
370 };
371
372 let mut elem_val = match serde_json::to_value(&before) {
374 Ok(v) => v,
375 Err(_) => return false,
376 };
377
378 if let (Some(obj), Some(upd)) = (elem_val.as_object_mut(), updates.as_object()) {
379 for (k, v) in upd {
380 obj.insert(k.clone(), v.clone());
381 }
382 } else {
383 return false;
384 }
385
386 let updated: Element = match serde_json::from_value(elem_val) {
387 Ok(e) => e,
388 Err(_) => return false,
389 };
390
391 self.history.push(Action::UpdateElement {
392 id: id.to_string(),
393 before: Box::new(before),
394 after: Box::new(updated.clone()),
395 });
396
397 if let Some(el) = self.document.get_element_mut(id) {
399 *el = updated;
400 }
401
402 true
403 }
404
405 pub fn get_element(&self, id: &str) -> String {
407 match self.document.get_element(id) {
408 Some(el) => serde_json::to_string(el).unwrap_or_default(),
409 None => String::new(),
410 }
411 }
412
413 pub fn undo(&mut self) -> bool {
417 if let Some(action) = self.history.pop_undo() {
418 self.apply_undo(action);
419 true
420 } else {
421 false
422 }
423 }
424
425 pub fn redo(&mut self) -> bool {
427 if let Some(action) = self.history.pop_redo() {
428 self.apply_redo(action);
429 true
430 } else {
431 false
432 }
433 }
434
435 pub fn push_action(&mut self, action_json: &str) -> bool {
438 let _ = action_json;
443 false
444 }
445
446 pub fn can_undo(&self) -> bool {
447 self.history.can_undo()
448 }
449
450 pub fn can_redo(&self) -> bool {
451 self.history.can_redo()
452 }
453
454 pub fn select_all(&mut self) {
458 self.selected_ids = self
459 .document
460 .elements
461 .iter()
462 .filter(|el| el.group_id().is_none())
463 .map(|el| el.id().to_string())
464 .collect();
465 }
466
467 pub fn clear_selection(&mut self) {
469 self.selected_ids.clear();
470 }
471
472 pub fn add_to_selection(&mut self, id: &str) {
474 if !self.selected_ids.iter().any(|s| s == id) {
475 self.selected_ids.push(id.to_string());
476 }
477 }
478
479 pub fn remove_from_selection(&mut self, id: &str) {
481 self.selected_ids.retain(|s| s != id);
482 }
483
484 pub fn is_selected(&self, id: &str) -> bool {
486 self.selected_ids.iter().any(|s| s == id)
487 }
488
489 pub fn remove_elements(&mut self, ids_json: &str) {
493 if let Ok(ids) = serde_json::from_str::<Vec<String>>(ids_json) {
494 for id in &ids {
495 if let Some(el) = self.document.remove_element(id) {
496 self.history
497 .push(Action::RemoveElement(id.to_string(), Box::new(el)));
498 self.selected_ids.retain(|s| s != id);
499 }
500 }
501 }
502 }
503
504 pub fn get_all_element_ids(&self) -> String {
506 let ids: Vec<&str> = self.document.elements.iter().map(|el| el.id()).collect();
507 serde_json::to_string(&ids).unwrap_or_else(|_| "[]".to_string())
508 }
509
510 pub fn get_elements_by_group(&self, group_id: &str) -> String {
512 let ids: Vec<&str> = self
513 .document
514 .elements
515 .iter()
516 .filter(|el| el.group_id() == Some(group_id))
517 .map(|el| el.id())
518 .collect();
519 serde_json::to_string(&ids).unwrap_or_else(|_| "[]".to_string())
520 }
521
522 pub fn reorder_to_front(&mut self, id: &str) {
526 if let Some(idx) = self.document.elements.iter().position(|e| e.id() == id) {
527 let el = self.document.elements.remove(idx);
528 self.document.elements.push(el);
529 }
530 }
531
532 pub fn reorder_to_back(&mut self, id: &str) {
534 if let Some(idx) = self.document.elements.iter().position(|e| e.id() == id) {
535 let el = self.document.elements.remove(idx);
536 self.document.elements.insert(0, el);
537 }
538 }
539
540 pub fn reorder_forward(&mut self, id: &str) {
542 if let Some(idx) = self.document.elements.iter().position(|e| e.id() == id)
543 && idx < self.document.elements.len() - 1
544 {
545 self.document.elements.swap(idx, idx + 1);
546 }
547 }
548
549 pub fn reorder_backward(&mut self, id: &str) {
551 if let Some(idx) = self.document.elements.iter().position(|e| e.id() == id)
552 && idx > 0
553 {
554 self.document.elements.swap(idx, idx - 1);
555 }
556 }
557
558 pub fn set_show_grid(&mut self, show: bool) {
562 let mut config = self.renderer.config().clone();
563 config.show_grid = show;
564 self.renderer = Renderer::new(config);
565 }
566
567 pub fn get_document_json_for_save(&self) -> String {
571 let mut doc = self.document.clone();
573 doc.modified_at = chrono::Utc::now().to_rfc3339();
574 serde_json::to_string(&doc).unwrap_or_default()
575 }
576
577 pub fn set_document_id(&mut self, id: &str) {
580 self.document.id = id.to_string();
581 }
582
583 pub fn document_id(&self) -> String {
584 self.document.id.clone()
585 }
586
587 pub fn set_created_at(&mut self, ts: &str) {
588 self.document.created_at = ts.to_string();
589 }
590
591 pub fn document_name(&self) -> String {
594 self.document.name.clone()
595 }
596
597 pub fn set_document_name(&mut self, name: &str) {
598 self.document.name = name.to_string();
599 }
600
601 pub fn element_count(&self) -> usize {
602 self.document.elements.len()
603 }
604}
605
606impl DrawEngine {
609 fn apply_undo(&mut self, action: Action) {
610 match action {
611 Action::AddElement(el) => {
612 self.document.remove_element(el.id());
613 }
614 Action::RemoveElement(_, el) => {
615 self.document.add_element(*el);
616 }
617 Action::MoveElement { id, dx, dy } => {
618 if let Some(el) = self.document.get_element_mut(&id) {
619 match el {
620 Element::Rectangle(e) | Element::Ellipse(e) | Element::Diamond(e) => {
621 e.x -= dx;
622 e.y -= dy;
623 }
624 Element::Line(e) | Element::Arrow(e) => {
625 e.x -= dx;
626 e.y -= dy;
627 }
628 Element::FreeDraw(e) => {
629 e.x -= dx;
630 e.y -= dy;
631 }
632 Element::Text(e) => {
633 e.x -= dx;
634 e.y -= dy;
635 }
636 }
637 }
638 }
639 Action::ResizeElement {
640 id,
641 old_x,
642 old_y,
643 old_width,
644 old_height,
645 ..
646 } => {
647 if let Some(Element::Rectangle(e) | Element::Ellipse(e) | Element::Diamond(e)) =
648 self.document.get_element_mut(&id)
649 {
650 e.x = old_x;
651 e.y = old_y;
652 e.width = old_width;
653 e.height = old_height;
654 }
655 }
656 Action::UpdateElement { id, before, .. } => {
657 if let Some(el) = self.document.get_element_mut(&id) {
658 *el = *before;
659 }
660 }
661 Action::Batch(actions) => {
662 for a in actions.into_iter().rev() {
663 self.apply_undo(a);
664 }
665 }
666 }
667 }
668
669 fn apply_redo(&mut self, action: Action) {
670 match action {
671 Action::AddElement(el) => {
672 self.document.add_element(*el);
673 }
674 Action::RemoveElement(id, _) => {
675 self.document.remove_element(&id);
676 }
677 Action::MoveElement { id, dx, dy } => {
678 if let Some(el) = self.document.get_element_mut(&id) {
679 match el {
680 Element::Rectangle(e) | Element::Ellipse(e) | Element::Diamond(e) => {
681 e.x += dx;
682 e.y += dy;
683 }
684 Element::Line(e) | Element::Arrow(e) => {
685 e.x += dx;
686 e.y += dy;
687 }
688 Element::FreeDraw(e) => {
689 e.x += dx;
690 e.y += dy;
691 }
692 Element::Text(e) => {
693 e.x += dx;
694 e.y += dy;
695 }
696 }
697 }
698 }
699 Action::ResizeElement {
700 id,
701 new_x,
702 new_y,
703 new_width,
704 new_height,
705 ..
706 } => {
707 if let Some(Element::Rectangle(e) | Element::Ellipse(e) | Element::Diamond(e)) =
708 self.document.get_element_mut(&id)
709 {
710 e.x = new_x;
711 e.y = new_y;
712 e.width = new_width;
713 e.height = new_height;
714 }
715 }
716 Action::UpdateElement { id, after, .. } => {
717 if let Some(el) = self.document.get_element_mut(&id) {
718 *el = *after;
719 }
720 }
721 Action::Batch(actions) => {
722 for a in actions {
723 self.apply_redo(a);
724 }
725 }
726 }
727 }
728}
729
730#[cfg(test)]
733mod tests {
734 use super::*;
735
736 #[test]
737 fn test_new_engine() {
738 let engine = DrawEngine::new(800, 600, 2.0);
739 assert_eq!(engine.render_width(), 1600);
740 assert_eq!(engine.render_height(), 1200);
741 assert_eq!(engine.element_count(), 0);
742 assert_eq!(engine.document_name(), "untitled");
743 }
744
745 #[test]
746 fn test_set_size() {
747 let mut engine = DrawEngine::new(800, 600, 1.0);
748 engine.set_size(1920, 1080);
749 assert_eq!(engine.render_width(), 1920);
750 assert_eq!(engine.render_height(), 1080);
751 }
752
753 #[test]
754 fn test_load_save_document() {
755 let mut engine = DrawEngine::new(800, 600, 1.0);
756
757 let doc = Document::new("test doc".to_string());
758 let json = serde_json::to_string(&doc).unwrap();
759
760 assert!(engine.load_document(&json));
761 assert_eq!(engine.document_name(), "test doc");
762
763 let saved = engine.save_document();
764 assert!(saved.contains("test doc"));
765
766 assert!(!engine.load_document("not json"));
768 }
769
770 #[test]
771 fn test_render_returns_pixel_data() {
772 let engine = DrawEngine::new(100, 100, 1.0);
773 let data = engine.render();
774 assert_eq!(data.len(), 100 * 100 * 4);
776 }
777
778 #[test]
779 fn test_viewport() {
780 let mut engine = DrawEngine::new(800, 600, 1.0);
781 engine.set_viewport(100.0, 200.0, 2.0);
782 assert_eq!(engine.scroll_x(), 100.0);
783 assert_eq!(engine.scroll_y(), 200.0);
784 assert_eq!(engine.zoom(), 2.0);
785 }
786
787 #[test]
788 fn test_screen_to_world() {
789 let mut engine = DrawEngine::new(800, 600, 1.0);
790 engine.set_viewport(100.0, 50.0, 2.0);
791
792 let result = engine.screen_to_world(300.0, 250.0);
793 assert!(result.contains("100"));
796 }
797
798 #[test]
799 fn test_add_and_remove_element() {
800 let mut engine = DrawEngine::new(800, 600, 1.0);
801
802 let json = r#"{"type":"Rectangle","id":"r1","x":10,"y":20,"width":100,"height":50}"#;
803 let id = engine.add_element(json);
804 assert_eq!(id, "r1");
805 assert_eq!(engine.element_count(), 1);
806
807 let el_json = engine.get_element("r1");
809 assert!(el_json.contains("r1"));
810
811 assert!(engine.remove_element("r1"));
813 assert_eq!(engine.element_count(), 0);
814 assert!(!engine.remove_element("r1")); }
816
817 #[test]
818 fn test_move_element() {
819 let mut engine = DrawEngine::new(800, 600, 1.0);
820 engine
821 .add_element(r#"{"type":"Rectangle","id":"r1","x":10,"y":20,"width":100,"height":50}"#);
822
823 engine.move_element("r1", 50.0, 60.0);
824 let el_json = engine.get_element("r1");
825 assert!(el_json.contains("50"));
826 }
827
828 #[test]
829 fn test_resize_element() {
830 let mut engine = DrawEngine::new(800, 600, 1.0);
831 engine
832 .add_element(r#"{"type":"Rectangle","id":"r1","x":0,"y":0,"width":100,"height":100}"#);
833
834 engine.resize_element("r1", 10.0, 10.0, 200.0, 150.0);
835 let el_json = engine.get_element("r1");
836 assert!(el_json.contains("200"));
837 assert!(el_json.contains("150"));
838 }
839
840 #[test]
841 fn test_selection() {
842 let mut engine = DrawEngine::new(800, 600, 1.0);
843 engine.set_selection(r#"["r1","r2"]"#);
844 let sel = engine.get_selection();
845 assert!(sel.contains("r1"));
846 assert!(sel.contains("r2"));
847 }
848
849 #[test]
850 fn test_selection_box() {
851 let mut engine = DrawEngine::new(800, 600, 1.0);
852 engine.set_selection_box(10.0, 20.0, 100.0, 50.0);
853 let data = engine.render();
855 assert!(!data.is_empty());
856
857 engine.clear_selection_box();
858 let data = engine.render();
859 assert!(!data.is_empty());
860 }
861
862 #[test]
863 fn test_undo_redo_add() {
864 let mut engine = DrawEngine::new(800, 600, 1.0);
865 engine
866 .add_element(r#"{"type":"Rectangle","id":"r1","x":0,"y":0,"width":100,"height":100}"#);
867 assert_eq!(engine.element_count(), 1);
868 assert!(engine.can_undo());
869
870 assert!(engine.undo());
871 assert_eq!(engine.element_count(), 0);
872 assert!(engine.can_redo());
873
874 assert!(engine.redo());
875 assert_eq!(engine.element_count(), 1);
876 }
877
878 #[test]
879 fn test_undo_redo_remove() {
880 let mut engine = DrawEngine::new(800, 600, 1.0);
881 engine
882 .add_element(r#"{"type":"Rectangle","id":"r1","x":0,"y":0,"width":100,"height":100}"#);
883 engine.remove_element("r1");
884 assert_eq!(engine.element_count(), 0);
885
886 assert!(engine.undo()); assert_eq!(engine.element_count(), 1);
888
889 assert!(engine.redo()); assert_eq!(engine.element_count(), 0);
891 }
892
893 #[test]
894 fn test_undo_move() {
895 let mut engine = DrawEngine::new(800, 600, 1.0);
896 engine.add_element(
897 r#"{"type":"Rectangle","id":"r1","x":10,"y":20,"width":100,"height":100}"#,
898 );
899 engine.move_element("r1", 50.0, 60.0);
900
901 engine.undo();
903 let el_json = engine.get_element("r1");
904 assert!(el_json.contains("\"x\":10"));
906 assert!(el_json.contains("\"y\":20"));
907 }
908
909 #[test]
910 fn test_update_element_style() {
911 let mut engine = DrawEngine::new(800, 600, 1.0);
912 engine
913 .add_element(r#"{"type":"Rectangle","id":"r1","x":0,"y":0,"width":100,"height":100}"#);
914
915 let ok = engine.update_element_style("r1", r#"{"opacity": 0.5}"#);
916 assert!(ok);
917
918 let el_json = engine.get_element("r1");
919 assert!(el_json.contains("0.5"));
920
921 engine.undo();
923 let el_json = engine.get_element("r1");
924 assert!(el_json.contains("1.0") || el_json.contains("1"));
925 }
926
927 #[test]
928 fn test_hit_test_empty() {
929 let engine = DrawEngine::new(800, 600, 1.0);
930 let result = engine.hit_test(400.0, 300.0);
931 assert!(result.is_empty());
932 }
933
934 #[test]
935 fn test_elements_in_rect_empty() {
936 let engine = DrawEngine::new(800, 600, 1.0);
937 let result = engine.elements_in_rect(0.0, 0.0, 100.0, 100.0);
938 assert_eq!(result, "[]");
939 }
940
941 #[test]
942 fn test_document_name() {
943 let mut engine = DrawEngine::new(800, 600, 1.0);
944 engine.set_document_name("my drawing");
945 assert_eq!(engine.document_name(), "my drawing");
946 }
947
948 #[test]
949 fn test_push_action_placeholder() {
950 let mut engine = DrawEngine::new(800, 600, 1.0);
951 assert!(!engine.push_action("{}"));
952 }
953}