Skip to main content

canvas_core/
scene.rs

1//! Scene graph for managing canvas elements.
2
3use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6
7use crate::{CanvasError, CanvasResult, Element, ElementId};
8
9/// A scene containing all canvas elements.
10#[derive(Debug, Clone, Default, Serialize, Deserialize)]
11pub struct Scene {
12    /// All elements in the scene, indexed by ID.
13    elements: HashMap<ElementId, Element>,
14    /// Root-level element IDs (not children of any group).
15    root_elements: Vec<ElementId>,
16    /// Currently selected element IDs.
17    selected: Vec<ElementId>,
18    /// Viewport width in pixels.
19    pub viewport_width: f32,
20    /// Viewport height in pixels.
21    pub viewport_height: f32,
22    /// Current zoom level (1.0 = 100%).
23    pub zoom: f32,
24    /// Pan offset X.
25    pub pan_x: f32,
26    /// Pan offset Y.
27    pub pan_y: f32,
28}
29
30impl Scene {
31    /// Create a new empty scene with the given viewport size.
32    #[must_use]
33    pub fn new(width: f32, height: f32) -> Self {
34        Self {
35            elements: HashMap::new(),
36            root_elements: Vec::new(),
37            selected: Vec::new(),
38            viewport_width: width,
39            viewport_height: height,
40            zoom: 1.0,
41            pan_x: 0.0,
42            pan_y: 0.0,
43        }
44    }
45
46    /// Add an element to the scene.
47    pub fn add_element(&mut self, element: Element) -> ElementId {
48        let id = element.id;
49        if element.parent.is_none() {
50            self.root_elements.push(id);
51        }
52        self.elements.insert(id, element);
53        id
54    }
55
56    /// Remove an element from the scene.
57    ///
58    /// # Errors
59    ///
60    /// Returns an error if the element is not found.
61    pub fn remove_element(&mut self, id: &ElementId) -> CanvasResult<Element> {
62        self.root_elements.retain(|&eid| eid != *id);
63        self.selected.retain(|&eid| eid != *id);
64        self.elements
65            .remove(id)
66            .ok_or_else(|| CanvasError::ElementNotFound(id.to_string()))
67    }
68
69    /// Get an element by ID.
70    #[must_use]
71    pub fn get_element(&self, id: ElementId) -> Option<&Element> {
72        self.elements.get(&id)
73    }
74
75    /// Get a mutable reference to an element by ID.
76    pub fn get_element_mut(&mut self, id: ElementId) -> Option<&mut Element> {
77        self.elements.get_mut(&id)
78    }
79
80    /// Get all elements in the scene.
81    pub fn elements(&self) -> impl Iterator<Item = &Element> {
82        self.elements.values()
83    }
84
85    /// Get mutable references to all elements in the scene.
86    pub fn elements_mut(&mut self) -> impl Iterator<Item = &mut Element> {
87        self.elements.values_mut()
88    }
89
90    /// Get root-level elements (not children of groups).
91    pub fn root_elements(&self) -> impl Iterator<Item = &Element> {
92        self.root_elements
93            .iter()
94            .filter_map(|id| self.elements.get(id))
95    }
96
97    /// Set the viewport dimensions.
98    pub fn set_viewport(&mut self, width: f32, height: f32) {
99        self.viewport_width = width;
100        self.viewport_height = height;
101    }
102
103    /// Find the element at the given canvas coordinates.
104    /// Returns the ID of the topmost (highest z-index) interactive element.
105    #[must_use]
106    pub fn element_at(&self, x: f32, y: f32) -> Option<ElementId> {
107        let canvas_x = (x - self.pan_x) / self.zoom;
108        let canvas_y = (y - self.pan_y) / self.zoom;
109
110        self.elements
111            .values()
112            .filter(|e| e.interactive && e.contains_point(canvas_x, canvas_y))
113            .max_by_key(|e| e.transform.z_index)
114            .map(|e| e.id)
115    }
116
117    /// Select an element.
118    ///
119    /// # Errors
120    ///
121    /// Returns an error if the element is not found.
122    pub fn select(&mut self, id: ElementId) -> CanvasResult<()> {
123        if let Some(element) = self.elements.get_mut(&id) {
124            element.selected = true;
125            if !self.selected.contains(&id) {
126                self.selected.push(id);
127            }
128            Ok(())
129        } else {
130            Err(CanvasError::ElementNotFound(id.to_string()))
131        }
132    }
133
134    /// Deselect all elements.
135    pub fn deselect_all(&mut self) {
136        for id in &self.selected {
137            if let Some(element) = self.elements.get_mut(id) {
138                element.selected = false;
139            }
140        }
141        self.selected.clear();
142    }
143
144    /// Get currently selected elements.
145    pub fn selected_elements(&self) -> impl Iterator<Item = &Element> {
146        self.selected.iter().filter_map(|id| self.elements.get(id))
147    }
148
149    /// Get the number of elements in the scene.
150    #[must_use]
151    pub fn element_count(&self) -> usize {
152        self.elements.len()
153    }
154
155    /// Check if the scene is empty.
156    #[must_use]
157    pub fn is_empty(&self) -> bool {
158        self.elements.is_empty()
159    }
160
161    /// Clear all elements from the scene.
162    pub fn clear(&mut self) {
163        self.elements.clear();
164        self.root_elements.clear();
165        self.selected.clear();
166    }
167
168    /// Serialize the scene to JSON.
169    ///
170    /// # Errors
171    ///
172    /// Returns an error if serialization fails.
173    pub fn to_json(&self) -> CanvasResult<String> {
174        serde_json::to_string(self).map_err(CanvasError::Serialization)
175    }
176
177    /// Deserialize a scene from JSON.
178    ///
179    /// # Errors
180    ///
181    /// Returns an error if deserialization fails.
182    pub fn from_json(json: &str) -> CanvasResult<Self> {
183        serde_json::from_str(json).map_err(CanvasError::Serialization)
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190    use crate::{ElementKind, Transform};
191
192    #[test]
193    fn test_scene_add_remove() {
194        let mut scene = Scene::new(800.0, 600.0);
195        assert!(scene.is_empty());
196
197        let element = Element::new(ElementKind::Text {
198            content: "Hello".to_string(),
199            font_size: 16.0,
200            color: "#000000".to_string(),
201        });
202        let id = scene.add_element(element);
203
204        assert_eq!(scene.element_count(), 1);
205        assert!(scene.get_element(id).is_some());
206
207        scene.remove_element(&id).expect("should remove");
208        assert!(scene.is_empty());
209    }
210
211    #[test]
212    fn test_element_at() {
213        let mut scene = Scene::new(800.0, 600.0);
214
215        let element = Element::new(ElementKind::Text {
216            content: "Test".to_string(),
217            font_size: 16.0,
218            color: "#000000".to_string(),
219        })
220        .with_transform(Transform {
221            x: 100.0,
222            y: 100.0,
223            width: 200.0,
224            height: 50.0,
225            rotation: 0.0,
226            z_index: 0,
227        });
228
229        scene.add_element(element);
230
231        // Point inside element
232        assert!(scene.element_at(150.0, 125.0).is_some());
233
234        // Point outside element
235        assert!(scene.element_at(50.0, 50.0).is_none());
236    }
237}