blockworx/document_ng/mod.rs
1pub mod auto_route;
2pub mod coord;
3pub mod decorations;
4pub mod document;
5pub mod image;
6pub mod label;
7pub mod linear_distance;
8pub mod path;
9pub mod pin_side;
10pub mod pin_type;
11pub mod route_direction;
12pub mod route_edge;
13pub mod schema_convert;
14
15pub use auto_route::{AutoRoute, Crossing};
16pub use coord::{GridPos, GridRect, GridSize, GridVec};
17pub use decorations::Decorations;
18pub use document::Document;
19pub use image::{Image, ImageData};
20pub use label::{BlockLabel, LabelSide};
21pub use linear_distance::LinearDistance;
22pub use path::BlockPath;
23pub use pin_side::PinSide;
24pub use pin_type::PinType;
25pub use route_direction::RouteDirection;
26pub use route_edge::RouteEdge;
27
28use crate::store::{CommentId, IdMap, IdSet, ImageId, PinId, RectId, RouteId, TextId};
29
30/// A node in the persistent document tree.
31///
32/// `pins` of a Block, when the Block is the one whose interior is being drawn,
33/// are visualized as Ports on the canvas boundary. When the Block is a child
34/// (in some other Block's `children`), it is visualized as a rect with its
35/// pins on its edges.
36#[derive(Clone, Debug, PartialEq)]
37pub struct Block {
38 pub inner: GridRect,
39 /// The block's label decorations. `decorations.title.name` is the single
40 /// source of truth for the block's name.
41 pub decorations: Decorations,
42 pub pins: IdMap<PinId, PinPort>,
43 /// Ids of this block's children. The child `Block`s themselves live in the
44 /// document-global [`Document::blocks`] map; this set only records which of
45 /// them are this block's children and in what order (render z-order). Stored
46 /// as an [`IdSet`] — insertion-ordered, so child z-order is stable.
47 pub children: IdSet<RectId>,
48 pub routes: IdMap<RouteId, AutoRoute>,
49 /// Free-floating text annotations placed on this block's interior. They
50 /// carry no pins and never participate in routing.
51 pub texts: IdMap<TextId, TextBox>,
52 /// Free-floating boundary [`Comment`]s placed on this block's interior. Like
53 /// [`texts`](Self::texts) they carry no pins, never participate in routing,
54 /// and are not part of the hierarchy (never listed in any block's
55 /// `children`). They render on a layer above the blocks and routes.
56 pub comments: IdMap<CommentId, Comment>,
57 /// Free-floating background image annotations placed on this block's
58 /// interior. Like [`texts`](Self::texts) and [`comments`](Self::comments)
59 /// they carry no pins, never participate in routing, and are not part of the
60 /// hierarchy. They paint behind everything (the background layer).
61 pub images: IdMap<ImageId, Image>,
62 /// This block's optional foreground icon: a [`Image`] centered on the block
63 /// that travels with it. Unlike a background [`images`](Self::images) image
64 /// it is only selectable while the block itself is selected.
65 pub icon: Option<Image>,
66 /// Optional accent index (`0..=7`) selecting the block's outline color via
67 /// [`Accent0`](crate::theme::Role::Accent0)..[`Accent7`](crate::theme::Role::Accent7);
68 /// `None` uses [`AccentDefault`](crate::theme::Role::AccentDefault).
69 /// See [`crate::shape::block::Block::accent_role`].
70 pub role: Option<u8>,
71 /// Whether the block's pin/port interface is frozen. A locked block can still
72 /// be moved, deleted, resized, recolored, and have its title/type edited, but
73 /// its pins and ports are protected from material edits (rename, retype, tag
74 /// text, add, delete).
75 pub locked: bool,
76}
77
78/// Whether an interface is frozen against edits — the parameter form of a
79/// `locked` flag, so a call site reads `Lock::Locked` instead of a bare `true`.
80#[derive(Clone, Copy, Debug, PartialEq, Eq)]
81pub enum Lock {
82 Locked,
83 Unlocked,
84}
85
86impl Lock {
87 pub fn is_locked(self) -> bool {
88 self == Lock::Locked
89 }
90}
91
92impl From<bool> for Lock {
93 fn from(locked: bool) -> Self {
94 if locked { Lock::Locked } else { Lock::Unlocked }
95 }
96}
97
98/// Whether a pin/port draws its tag. The parameter form of `tag_hidden`.
99#[derive(Clone, Copy, Debug, PartialEq, Eq)]
100pub enum TagVisibility {
101 Shown,
102 Hidden,
103}
104
105impl TagVisibility {
106 pub fn is_hidden(self) -> bool {
107 self == TagVisibility::Hidden
108 }
109}
110
111impl From<bool> for TagVisibility {
112 fn from(hidden: bool) -> Self {
113 if hidden {
114 TagVisibility::Hidden
115 } else {
116 TagVisibility::Shown
117 }
118 }
119}
120
121/// A free-floating multi-line text annotation. Unlike a [`Block`] it has no
122/// pins, title, tag, or bounding box — just text pinned at a single grid
123/// `anchor`. It implements [`crate::shape::BaseShape`] so it can be
124/// selected, moved, and deleted through the same machinery as blocks and ports
125/// (but it is never resized — its extent is whatever the text needs).
126#[derive(Clone, Debug, PartialEq)]
127pub struct TextBox {
128 /// The annotation's content. May contain `\n` for multiple lines; the user
129 /// governs the layout, there is no wrapping.
130 pub text: String,
131 /// Top-left anchor (grid coordinate). Text flows right/down from here.
132 pub anchor: GridPos,
133 /// Cached on-canvas extent (grid cells, anchor-relative) measured from a
134 /// laid-out galley the last time the text was edited. `None` for freshly
135 /// created boxes (and on load, since it isn't persisted); callers fall back
136 /// to the `text_bbox` estimate until the box is next edited. Anchor-relative
137 /// so it survives a move without needing an update.
138 pub size: Option<GridSize>,
139 /// Optional accent index (`0..=7`) selecting the outline color; `None`
140 /// strokes with [`crate::theme::Role::TextBoxStroke`].
141 pub role: Option<u8>,
142}
143
144/// A free-floating boundary annotation: a rectangle drawn as an outline only
145/// (no fill) with a single repositionable [`BlockLabel`] title. Unlike a
146/// [`Block`] it has no pins, tag, children, or routes and never participates in
147/// the hierarchy — it exists purely to group components visually. It implements
148/// [`crate::shape::BaseShape`] so it is selected, moved, resized, and renamed
149/// through the same machinery as blocks, and it renders on a layer above the
150/// blocks and routes.
151#[derive(Clone, Debug, PartialEq)]
152pub struct Comment {
153 /// Position and size in grid coordinates (resizable, like a block).
154 pub inner: GridRect,
155 /// The comment's repositionable title. `title.name` is the comment's name.
156 pub title: BlockLabel,
157 /// Optional accent index (`0..=7`) selecting the outline color; `None`
158 /// strokes with [`crate::theme::Role::CommentStroke`].
159 pub role: Option<u8>,
160}
161
162/// Unified Pin/Port data structure. When the entry lives in a child block's
163/// `pins` store it is acting as an inner pin and `rect` is unused (the
164/// position is computed from the parent block's rect + `side`/`offset`).
165/// When the entry lives in the current block's `pins` store it is rendered
166/// as a Port and `rect` carries its on-canvas position.
167#[derive(Clone, Debug, PartialEq)]
168pub struct PinPort {
169 /// The pin's primary label, a single line. (Legacy documents may carry a
170 /// second line after a `\n`; it is ignored — the render/edit paths use only
171 /// the first line.)
172 pub name: String,
173 /// The pin's secondary label — its "type" — drawn a little smaller, below the
174 /// name. Single-line, like `name`. Written on disk as `type`. Distinct from
175 /// [`PinType`] (`kind`), which is the I/O direction, not free text.
176 pub type_label: String,
177 /// A short user-editable label — the pin's "location" designator (e.g. "A1").
178 /// Defaults to the pin's id (as a string) at creation.
179 pub tag: String,
180 /// Whether the `tag` label is hidden. Kept beside `tag` since it governs it.
181 /// The user toggles it via the per-pin eye button shown when the pin's
182 /// owning shape is selected. (There is deliberately no `name`-visibility
183 /// flag: an unlabelled pin breaks the hierarchy, so to drop a label the user
184 /// clears its text instead.)
185 pub tag_hidden: bool,
186 pub side: PinSide,
187 pub offset: u32,
188 pub rect: GridRect,
189 /// Signal direction, stored from the pin's point of view. Flipped when
190 /// this `PinPort` is rendered as a port. See [`PinType`].
191 pub kind: PinType,
192 /// The accents to apply to this pin.
193 pub accents: Accents,
194 /// Which way the stub faces when this `PinPort` is drawn as a boundary
195 /// *port*, independent of `side` (which edge of the parent it occupies as a
196 /// pin). `None` derives it as `side.flip()` — a port faces opposite the edge
197 /// its pin sits on, so its interior stub points inward. The flip-L/R tool
198 /// sets it explicitly (and a parent flip freezes it) so it stops tracking
199 /// `side`. Ignored when this `PinPort` is drawn as a block's edge pin.
200 /// Written on disk as `facing`.
201 pub port_orientation: Option<PinSide>,
202}
203
204#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
205pub struct Accents {
206 /// The accent for a pin as drawn as part of a block
207 pub pin_accent: Option<u8>,
208 /// The accent for a pin when it is a port entry in a diagram
209 pub port_accent: Option<u8>,
210 /// The accent for the pin part of a port entry
211 pub port_pin_accent: Option<u8>,
212}
213
214#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
215pub struct Waypoint {
216 pub pos: GridPos,
217 pub locked: bool,
218}
219
220/// Absolute reference to a pin on either the current block (`Port`) or
221/// a child block (`Pin`).
222#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
223pub enum LineAnchor {
224 Port(PinId),
225 Pin { block: RectId, pin: PinId },
226}
227
228impl Default for Block {
229 fn default() -> Self {
230 Self {
231 inner: GridRect::default(),
232 decorations: Decorations {
233 title: BlockLabel {
234 name: "top".to_string(),
235 ..BlockLabel::default()
236 },
237 ..Decorations::default()
238 },
239 pins: IdMap::default(),
240 children: IdSet::default(),
241 routes: IdMap::default(),
242 texts: IdMap::default(),
243 comments: IdMap::default(),
244 images: IdMap::default(),
245 icon: None,
246 role: None,
247 locked: false,
248 }
249 }
250}