Skip to main content

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}