dotloom_engine/
scene_build.rs1use std::collections::{BTreeMap, BTreeSet};
7
8use dotloom_document::{Color, Entity, EntityId};
9use dotloom_scene::{Primitive, SceneDelta, SceneItem, Stroke, flags};
10
11use crate::{
12 Engine,
13 eval::{Drawable, Evaluated},
14 registry::PrimStyle,
15};
16
17pub const THEME_DEFAULT: u32 = 0;
19
20#[derive(Debug, Clone, Default)]
22pub struct SceneState {
23 pub dirty: BTreeSet<EntityId>,
25 pub removed: BTreeSet<EntityId>,
27 pub reset: bool,
29 pub order_changed: bool,
31 pub preview: BTreeSet<EntityId>,
33}
34
35fn rgba(c: Option<Color>) -> Option<u32> {
36 c.map(|c| c.0)
37}
38
39pub(crate) fn primitives(e: &Entity, layer_color: Option<Color>, ev: &Evaluated) -> Vec<Primitive> {
41 let entity_stroke = rgba(e.style.stroke).or(rgba(layer_color));
42 let width = e.style.stroke_width.unwrap_or(1.0) as f32;
43 let dash: Vec<f32> = e.style.dash.as_ref().map(|d| d.iter().map(|x| *x as f32).collect()).unwrap_or_default();
44 let stroke_for = |st: &PrimStyle| -> Option<Stroke> {
45 if st.no_stroke {
46 return None;
47 }
48 Some(Stroke {
49 color: rgba(st.stroke).or(entity_stroke).unwrap_or(THEME_DEFAULT),
50 width: st.width.map_or(width, |w| w as f32),
51 dash: st.dash.as_ref().map_or_else(|| dash.clone(), |d| d.iter().map(|x| *x as f32).collect()),
52 })
53 };
54 ev.drawables
55 .iter()
56 .map(|d| match d {
57 Drawable::Shape(s, st) => {
58 let fill = if s.is_region() { rgba(st.fill).or(rgba(e.style.fill)) } else { None };
59 Primitive::Shape { shape: s.clone(), stroke: stroke_for(st), fill }
60 }
61 Drawable::Text(t, st) => {
62 Primitive::Text { text: t.clone(), color: rgba(st.stroke).or(entity_stroke).unwrap_or(THEME_DEFAULT) }
63 }
64 Drawable::Arrow { tip, direction, size } => Primitive::Arrow {
65 tip: *tip,
66 direction: *direction,
67 size: *size,
68 color: entity_stroke.unwrap_or(THEME_DEFAULT),
69 },
70 })
71 .collect()
72}
73
74impl Engine {
75 fn layer_index(&self) -> BTreeMap<dotloom_document::LayerId, (u32, bool, bool, Option<Color>)> {
76 self.doc
77 .layers()
78 .iter()
79 .enumerate()
80 .map(|(i, l)| (l.id, (u32::try_from(i).unwrap_or(u32::MAX), l.visible, l.locked, l.color)))
81 .collect()
82 }
83
84 pub(crate) fn scene_item(
85 &mut self,
86 id: EntityId,
87 preview: Option<&Evaluated>,
88 preview_entity: Option<&Entity>,
89 ) -> Option<SceneItem> {
90 let layers = self.layer_index();
91 let e = preview_entity.cloned().or_else(|| self.doc.entity(id).cloned())?;
92 let (layer, visible, layer_locked, layer_color) = layers.get(&e.layer).copied()?;
93 if e.hidden || !visible {
94 return None;
95 }
96 let ev = match preview {
97 Some(p) => p.clone(),
98 None => {
99 let ctx = crate::eval::Ctx { view: &self.doc, registry: &self.registry };
100 self.cache.get(ctx, id).clone()
101 }
102 };
103 let mut f = 0u8;
104 if self.selection.contains(&id) {
105 f |= flags::SELECTED;
106 }
107 if preview.is_some() {
108 f |= flags::PREVIEW;
109 }
110 if e.locked || layer_locked {
111 f |= flags::LOCKED;
112 }
113 if ev.read_only.is_some() {
114 f |= flags::READONLY;
115 }
116 if ev.error.is_some() {
117 f |= flags::PROBLEM;
118 }
119 Some(SceneItem { id: id.0, layer, bbox: ev.bbox, flags: f, prims: primitives(&e, layer_color, &ev) })
120 }
121
122 pub fn take_scene_delta(&mut self) -> SceneDelta {
124 let reset = core::mem::take(&mut self.scene.reset);
125 let mut delta = SceneDelta { revision: self.revision, reset, ..SceneDelta::default() };
126 let ids: Vec<EntityId> = if reset {
127 self.scene.dirty.clear();
128 self.scene.removed.clear();
129 self.scene.preview.clear();
130 self.doc.order().to_vec()
131 } else {
132 let mut d: Vec<EntityId> = core::mem::take(&mut self.scene.dirty).into_iter().collect();
133 d.extend(core::mem::take(&mut self.scene.preview));
134 d.sort_unstable();
135 d.dedup();
136 d
137 };
138 for id in core::mem::take(&mut self.scene.removed) {
139 if self.doc.entity(id).is_none() {
140 delta.removals.push(id.0);
141 }
142 }
143 for id in ids {
144 match self.scene_item(id, None, None) {
145 Some(item) => delta.upserts.push(item),
146 None => delta.removals.push(id.0),
147 }
148 }
149 let order_changed = core::mem::take(&mut self.scene.order_changed);
150 if reset || order_changed {
151 delta.order = Some(self.doc.order().iter().map(|id| id.0).collect());
152 }
153 delta.removals.sort_unstable();
154 delta.removals.dedup();
155 delta
156 }
157
158 pub fn full_scene(&mut self) -> SceneDelta {
160 self.scene.reset = true;
161 self.take_scene_delta()
162 }
163}