dotloom_scene/lib.rs
1//! # dotloom-scene
2//!
3//! The public scene contract between the Dotloom engine and renderers.
4//!
5//! The engine turns documents into [`SceneItem`]s (world-space shapes with resolved
6//! styles, keyed by entity ID) and ships incremental [`SceneDelta`]s. Renderers
7//! consume deltas and never read documents, so any renderer that understands this
8//! crate can replace the default wgpu renderer.
9//!
10//! Shapes are exact (`f64`, curves kept as curves); renderers flatten them with a
11//! zoom-dependent tolerance. Deltas have a compact, versioned binary encoding
12//! ([`SceneDelta::encode`]) suitable for transfer between a Worker and the main
13//! thread without JSON parsing.
14
15mod codec;
16
17pub use codec::{DecodeError, MAGIC, SCENE_FORMAT_VERSION};
18pub use dotloom_geometry as geometry;
19use dotloom_geometry::{Aabb, Point, Segment, Shape, Text, Vector};
20use serde::{Deserialize, Serialize};
21
22/// Packed RGBA color (`0xRRGGBBAA`).
23pub type Rgba = u32;
24
25/// Stroke style (screen-constant width).
26#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
27pub struct Stroke {
28 /// Color.
29 pub color: Rgba,
30 /// Width in CSS pixels.
31 pub width: f32,
32 /// Dash pattern in CSS pixels (empty = solid).
33 #[serde(default, skip_serializing_if = "Vec::is_empty")]
34 pub dash: Vec<f32>,
35}
36
37/// One drawable primitive in world coordinates.
38#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
39#[serde(tag = "prim", rename_all = "camelCase")]
40pub enum Primitive {
41 /// A shape with optional stroke and fill (fill applies to regions only).
42 Shape {
43 /// Geometry (world coordinates).
44 shape: Shape,
45 /// Stroke.
46 stroke: Option<Stroke>,
47 /// Fill color.
48 fill: Option<Rgba>,
49 },
50 /// Text in world units.
51 Text {
52 /// Text geometry.
53 text: Text,
54 /// Color.
55 color: Rgba,
56 },
57 /// Filled arrow head (dimension lines); `size` in model units.
58 Arrow {
59 /// Tip.
60 tip: Point,
61 /// Unit direction pointing at the tip.
62 direction: Vector,
63 /// Length in model units.
64 size: f64,
65 /// Color.
66 color: Rgba,
67 },
68}
69
70/// Item state flags.
71pub mod flags {
72 /// Selected.
73 pub const SELECTED: u8 = 1;
74 /// Shown from a transient preview (drag), not committed.
75 pub const PREVIEW: u8 = 2;
76 /// Locked (entity or layer).
77 pub const LOCKED: u8 = 4;
78 /// Hovered.
79 pub const HOVER: u8 = 8;
80 /// Read-only (plugin missing or disabled; drawn from its fallback).
81 pub const READONLY: u8 = 16;
82 /// Has a constraint problem.
83 pub const PROBLEM: u8 = 32;
84}
85
86/// Everything drawn for one entity.
87#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
88pub struct SceneItem {
89 /// Entity ID.
90 pub id: u64,
91 /// Index of the entity's layer (bottom = 0).
92 pub layer: u32,
93 /// Bounding box of all primitives (world).
94 pub bbox: Aabb,
95 /// [`flags`] bit set.
96 pub flags: u8,
97 /// Primitives in drawing order.
98 pub prims: Vec<Primitive>,
99}
100
101/// Incremental scene update.
102#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
103pub struct SceneDelta {
104 /// Document revision the delta brings the scene to.
105 pub revision: u64,
106 /// The delta shows transient preview state on top of `revision`.
107 pub preview: bool,
108 /// Drop every item before applying.
109 pub reset: bool,
110 /// New or changed items.
111 pub upserts: Vec<SceneItem>,
112 /// Removed item IDs.
113 pub removals: Vec<u64>,
114 /// Complete draw order (entity IDs bottom to top) when it changed.
115 pub order: Option<Vec<u64>>,
116}
117
118impl SceneDelta {
119 /// Whether the delta changes nothing.
120 #[must_use]
121 pub fn is_empty(&self) -> bool {
122 !self.reset && self.upserts.is_empty() && self.removals.is_empty() && self.order.is_none()
123 }
124}
125
126/// Snap/handle marker kinds drawn by overlays.
127#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
128#[serde(rename_all = "camelCase")]
129pub enum MarkerKind {
130 /// Selection grip.
131 Handle,
132 /// Endpoint snap.
133 Endpoint,
134 /// Midpoint snap.
135 Midpoint,
136 /// Center snap.
137 Center,
138 /// Intersection snap.
139 Intersection,
140 /// Point on curve.
141 Nearest,
142 /// Grid point.
143 Grid,
144 /// Quadrant / vertex / other anchors.
145 Anchor,
146}
147
148/// A marker in world coordinates, drawn at constant screen size.
149#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
150pub struct Marker {
151 /// Position.
152 pub at: Point,
153 /// Kind.
154 pub kind: MarkerKind,
155}
156
157/// Interaction overlay (selection grips, snap marker, marquee, guides), set by the
158/// host UI directly on the renderer; it is not part of the document.
159#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
160pub struct Overlay {
161 /// Markers.
162 #[serde(default)]
163 pub markers: Vec<Marker>,
164 /// Selection rectangle (world).
165 #[serde(default)]
166 pub marquee: Option<Aabb>,
167 /// Whether the marquee is a crossing (dashed) selection.
168 #[serde(default)]
169 pub crossing: bool,
170 /// Construction guides (world).
171 #[serde(default)]
172 pub guides: Vec<Segment>,
173 /// In-progress drawing preview shapes (world), drawn with the preview style.
174 #[serde(default)]
175 pub sketch: Vec<Shape>,
176}