Skip to main content

dotloom_engine/
view.rs

1//! Read access to a document or a transaction's working state.
2
3use std::collections::BTreeMap;
4
5use dotloom_document::{
6    Constraint, ConstraintId, Document, Entity, EntityId, Group, GroupId, Layer, LayerId, Settings,
7};
8
9/// Read-only view of document content.
10pub trait DocView {
11    /// Entity by ID.
12    fn entity(&self, id: EntityId) -> Option<&Entity>;
13    /// Constraint by ID.
14    fn constraint(&self, id: ConstraintId) -> Option<&Constraint>;
15    /// Layer by ID.
16    fn layer(&self, id: LayerId) -> Option<&Layer>;
17    /// Group by ID.
18    fn group(&self, id: GroupId) -> Option<&Group>;
19    /// Settings.
20    fn settings(&self) -> &Settings;
21}
22
23impl DocView for Document {
24    fn entity(&self, id: EntityId) -> Option<&Entity> {
25        Document::entity(self, id)
26    }
27    fn constraint(&self, id: ConstraintId) -> Option<&Constraint> {
28        Document::constraint(self, id)
29    }
30    fn layer(&self, id: LayerId) -> Option<&Layer> {
31        Document::layer(self, id)
32    }
33    fn group(&self, id: GroupId) -> Option<&Group> {
34        Document::group(self, id)
35    }
36    fn settings(&self) -> &Settings {
37        &self.settings
38    }
39}
40
41/// Copy-on-write overlay over a document used while a transaction runs.
42#[derive(Debug, Clone)]
43pub struct Overlay<'a> {
44    /// Base document.
45    pub base: &'a Document,
46    /// Changed entities (`None` = deleted).
47    pub entities: BTreeMap<EntityId, Option<Entity>>,
48    /// Changed constraints.
49    pub constraints: BTreeMap<ConstraintId, Option<Constraint>>,
50    /// Changed groups.
51    pub groups: BTreeMap<GroupId, Option<Group>>,
52    /// Replaced layer list.
53    pub layers: Option<Vec<Layer>>,
54    /// Replaced settings.
55    pub settings: Option<Settings>,
56    /// Draw-order operations.
57    pub order_ops: Vec<OrderOp>,
58    /// ID allocator.
59    pub next_id: u64,
60}
61
62/// Owned overlay content (without the base borrow), kept by pending commits.
63#[derive(Debug, Clone, Default)]
64pub struct OverlayData {
65    entities: BTreeMap<EntityId, Option<Entity>>,
66    constraints: BTreeMap<ConstraintId, Option<Constraint>>,
67    groups: BTreeMap<GroupId, Option<Group>>,
68    layers: Option<Vec<Layer>>,
69    settings: Option<Settings>,
70    order_ops: Vec<OrderOp>,
71    next_id: u64,
72}
73
74impl<'a> Overlay<'a> {
75    /// Detach the changes from the base document.
76    #[must_use]
77    pub fn into_data(self) -> OverlayData {
78        OverlayData {
79            entities: self.entities,
80            constraints: self.constraints,
81            groups: self.groups,
82            layers: self.layers,
83            settings: self.settings,
84            order_ops: self.order_ops,
85            next_id: self.next_id,
86        }
87    }
88
89    /// Re-attach detached changes to the same base document.
90    #[must_use]
91    pub fn from_data(base: &'a Document, d: OverlayData) -> Self {
92        Self {
93            base,
94            entities: d.entities,
95            constraints: d.constraints,
96            groups: d.groups,
97            layers: d.layers,
98            settings: d.settings,
99            order_ops: d.order_ops,
100            next_id: d.next_id,
101        }
102    }
103}
104
105/// Draw-order change.
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub enum OrderOp {
108    /// Move to index.
109    Move(EntityId, usize),
110}
111
112impl<'a> Overlay<'a> {
113    /// Fresh overlay.
114    #[must_use]
115    pub fn new(base: &'a Document) -> Self {
116        Self {
117            base,
118            entities: BTreeMap::new(),
119            constraints: BTreeMap::new(),
120            groups: BTreeMap::new(),
121            layers: None,
122            settings: None,
123            order_ops: Vec::new(),
124            next_id: base.next_id(),
125        }
126    }
127
128    /// Allocate an ID.
129    pub fn alloc_id(&mut self) -> u64 {
130        let id = self.next_id;
131        self.next_id = self.next_id.saturating_add(1);
132        id
133    }
134
135    /// Entity for modification (copied into the overlay on first write).
136    pub fn entity_mut(&mut self, id: EntityId) -> Option<&mut Entity> {
137        if !self.entities.contains_key(&id) {
138            let e = self.base.entity(id)?.clone();
139            self.entities.insert(id, Some(e));
140        }
141        self.entities.get_mut(&id).and_then(Option::as_mut)
142    }
143
144    /// Insert or replace an entity.
145    pub fn put_entity(&mut self, e: Entity) {
146        self.entities.insert(e.id, Some(e));
147    }
148
149    /// Delete an entity.
150    pub fn delete_entity(&mut self, id: EntityId) {
151        self.entities.insert(id, None);
152    }
153
154    /// Insert or replace a constraint.
155    pub fn put_constraint(&mut self, c: Constraint) {
156        self.constraints.insert(c.id, Some(c));
157    }
158
159    /// Delete a constraint.
160    pub fn delete_constraint(&mut self, id: ConstraintId) {
161        self.constraints.insert(id, None);
162    }
163
164    /// Layers (copied on first write).
165    pub fn layers_mut(&mut self) -> &mut Vec<Layer> {
166        self.layers.get_or_insert_with(|| self.base.layers().to_vec())
167    }
168
169    /// Current layers.
170    #[must_use]
171    pub fn layers(&self) -> &[Layer] {
172        self.layers.as_deref().unwrap_or(self.base.layers())
173    }
174
175    /// Settings for modification.
176    pub fn settings_mut(&mut self) -> &mut Settings {
177        self.settings.get_or_insert_with(|| self.base.settings.clone())
178    }
179
180    /// All constraint IDs visible in the overlay (base ∪ new − deleted).
181    #[must_use]
182    pub fn constraint_ids(&self) -> Vec<ConstraintId> {
183        let mut ids: Vec<ConstraintId> = self
184            .base
185            .constraints()
186            .map(|c| c.id)
187            .filter(|id| !matches!(self.constraints.get(id), Some(None)))
188            .collect();
189        ids.extend(
190            self.constraints
191                .iter()
192                .filter(|(id, c)| c.is_some() && self.base.constraint(**id).is_none())
193                .map(|(id, _)| *id),
194        );
195        ids.sort_unstable();
196        ids
197    }
198
199    /// All entity IDs visible in the overlay.
200    #[must_use]
201    pub fn entity_ids(&self) -> Vec<EntityId> {
202        let mut ids: Vec<EntityId> =
203            self.base.order().iter().copied().filter(|id| !matches!(self.entities.get(id), Some(None))).collect();
204        ids.extend(
205            self.entities.iter().filter(|(id, e)| e.is_some() && self.base.entity(**id).is_none()).map(|(id, _)| *id),
206        );
207        ids
208    }
209
210    /// All groups visible in the overlay.
211    #[must_use]
212    pub fn group_ids(&self) -> Vec<GroupId> {
213        let mut ids: Vec<GroupId> =
214            self.base.groups().map(|g| g.id).filter(|id| !matches!(self.groups.get(id), Some(None))).collect();
215        ids.extend(
216            self.groups.iter().filter(|(id, g)| g.is_some() && self.base.group(**id).is_none()).map(|(id, _)| *id),
217        );
218        ids.sort_unstable();
219        ids
220    }
221}
222
223impl DocView for Overlay<'_> {
224    fn entity(&self, id: EntityId) -> Option<&Entity> {
225        match self.entities.get(&id) {
226            Some(e) => e.as_ref(),
227            None => self.base.entity(id),
228        }
229    }
230    fn constraint(&self, id: ConstraintId) -> Option<&Constraint> {
231        match self.constraints.get(&id) {
232            Some(c) => c.as_ref(),
233            None => self.base.constraint(id),
234        }
235    }
236    fn layer(&self, id: LayerId) -> Option<&Layer> {
237        self.layers().iter().find(|l| l.id == id)
238    }
239    fn group(&self, id: GroupId) -> Option<&Group> {
240        match self.groups.get(&id) {
241            Some(g) => g.as_ref(),
242            None => self.base.group(id),
243        }
244    }
245    fn settings(&self) -> &Settings {
246        self.settings.as_ref().unwrap_or(&self.base.settings)
247    }
248}