Skip to main content

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}