damascene_core/tree/semantics.rs
1//! Semantic node and paint roles carried by [`El`](crate::El).
2
3// Lock in full per-item documentation for this module (issue #73).
4#![warn(missing_docs)]
5
6use std::panic::Location;
7
8/// Semantic identity of an element. Roughly an HTML tag.
9#[derive(Clone, Debug, PartialEq, Eq)]
10pub enum Kind {
11 /// A bare layout container with no inherent visuals.
12 Group,
13 /// A bordered panel surface; the body of the `card()` widget.
14 Card,
15 /// A clickable button (HTML `<button>`).
16 Button,
17 /// A small status/label pill.
18 Badge,
19 /// A run of body text (HTML `<span>`/`<p>`).
20 Text,
21 /// Heading text (HTML `<h1>`–`<h3>`; built by `h1()`/`h2()`/`h3()`).
22 Heading,
23 /// Empty space used to push siblings apart.
24 Spacer,
25 /// A thin separating rule (HTML `<hr>`).
26 Divider,
27 /// A full-size layer that stacks floating content over the main view.
28 Overlay,
29 /// A full-size dimming layer behind a modal; its key routes dismiss clicks.
30 Scrim,
31 /// A floating modal/dialog panel (HTML `<dialog>`).
32 Modal,
33 /// A vertically scrollable region.
34 Scroll,
35 /// Vertically scrollable region whose children are produced lazily.
36 VirtualList,
37 /// Block whose direct children flow inline.
38 Inlines,
39 /// Forced line break inside a `Kind::Inlines` block.
40 HardBreak,
41 /// Native mathematical notation. Carries a [`crate::math::MathExpr`]
42 /// and renders through Damascene's math box layout.
43 Math,
44 /// Raster image element.
45 Image,
46 /// App-owned GPU texture composited into the paint stream. Backed
47 /// by [`crate::surface::AppTexture`] and the [`crate::tree::surface`]
48 /// builder; the backend samples the texture during paint instead
49 /// of uploading pixels.
50 ///
51 /// The texture stretches across the resolved rect with bilinear
52 /// filtering — source pixel dimensions and rendered size are
53 /// independent. See [`crate::tree::surface`] for the full sizing /
54 /// aspect-ratio contract.
55 Surface,
56 /// App-supplied vector asset. Backed by
57 /// [`crate::vector::VectorAsset`] and the [`crate::tree::vector`]
58 /// builder; callers explicitly choose painted vector rendering or
59 /// one-colour mask rendering. Unlike [`Kind::Image`] (icon-styled,
60 /// square-shaped), this is the general-purpose path for arbitrary-
61 /// aspect vector content — commit-graph curves, Gantt connectors,
62 /// custom chart marks.
63 Vector,
64 /// A backend-neutral 3D scene — closed-scope graph/model (point
65 /// scatter, small lit meshes, lines). Backed by
66 /// [`crate::scene::SceneSpec`] and the [`crate::tree::chart3d`]
67 /// builder; [`crate::draw_ops`](mod@crate::draw_ops) resolves it into a `DrawOp::Scene3D`
68 /// that the backend renders with its own scene pipelines (it does not
69 /// sample a texture from core). See `docs/SCENE3D_PLAN.md`.
70 Scene3D,
71 /// A backend-neutral 2D plot — line/scatter data over auto-scaled,
72 /// pannable/zoomable axes. Backed by [`crate::plot::PlotSpec`] and the
73 /// [`crate::tree::plot`] builder; [`crate::draw_ops`](mod@crate::draw_ops)
74 /// resolves it into a `DrawOp::Scene3D` (an orthographic, degenerate
75 /// scene — see `docs/PLOT2D_PLAN.md`) plus themed axis/grid chrome.
76 Plot,
77 /// A pan/zoom viewport — a clipped window onto a content layer the
78 /// user can drag and zoom. Carries a [`crate::viewport::ViewportConfig`]
79 /// (via [`crate::tree::viewport`]); the layout pass bakes the
80 /// pan/zoom transform into descendant rects and the input pass owns
81 /// the drag / wheel gestures (unless the config's
82 /// [`FitPolicy::Lock`](crate::viewport::FitPolicy::Lock) disables
83 /// them and pins the content fit).
84 Viewport,
85 /// Escape hatch for app-defined components.
86 Custom(&'static str),
87}
88
89/// Semantic paint role for rect-shaped surfaces.
90///
91/// Each variant maps to a theme-applied recipe at paint time. Roles are
92/// either *decorative* (set stroke + shadow on top of whatever fill the
93/// node already carries) or *fill-providing* (default a fill from the
94/// palette when the node has none). The split matters: setting a
95/// decorative role on a node with no fill produces an "invisible
96/// surface" — only a thin border over the parent's background. For
97/// panel-shaped containers, prefer the dedicated widget (`card()`,
98/// `sidebar()`, `dialog()`, `popover()`) which bundles role + fill +
99/// stroke + radius + shadow correctly.
100#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
101pub enum SurfaceRole {
102 /// No special semantic role. Theme fallback applies.
103 #[default]
104 None,
105 /// **Decorative.** Border + small drop shadow. *Does not paint a
106 /// fill* — the node must supply one (e.g. `tokens::CARD`) or sit
107 /// inside a widget like `card()` / `sidebar()` that does.
108 Panel,
109 /// **Decorative.** Border + half-strength shadow, suggesting one
110 /// elevation step above its parent. Like `Panel`, no fill.
111 Raised,
112 /// **Fill-providing.** Slightly darker variant of `MUTED` (palette
113 /// `darken(0.08)`) with input-toned border. Use for inset bands —
114 /// search wells, segmented-control tracks, recessed list headers.
115 Sunken,
116 /// **Decorative.** Input-toned border + large drop shadow for
117 /// floating panels. Used by `popover()` and friends; bring your
118 /// own fill (typically `tokens::POPOVER`).
119 Popover,
120 /// **Fill-providing.** PRIMARY-tinted alpha 28 fill +
121 /// PRIMARY-tinted alpha 110 border. The selected item inside a
122 /// collection. Prefer the `.selected()` chainable, which sets this
123 /// role plus content color in one call.
124 Selected,
125 /// **Fill-providing.** Solid `ACCENT` fill + neutral border for
126 /// the current page / nav item. Prefer the `.current()` chainable,
127 /// which also bumps font weight and content color.
128 Current,
129 /// **Fill-providing.** Same recipe as `Sunken` — used by text
130 /// inputs and other editable surfaces.
131 Input,
132 /// **Decorative.** Destructive-toned border, no shadow. Pair with
133 /// a tint fill (e.g. `tokens::DESTRUCTIVE.with_alpha_u8(40)`) for the
134 /// classic "danger" band in a form or section header.
135 Danger,
136}
137
138impl SurfaceRole {
139 /// The lowercase role name, as printed in inspection artifacts.
140 pub fn name(self) -> &'static str {
141 match self {
142 SurfaceRole::None => "none",
143 SurfaceRole::Panel => "panel",
144 SurfaceRole::Raised => "raised",
145 SurfaceRole::Sunken => "sunken",
146 SurfaceRole::Popover => "popover",
147 SurfaceRole::Selected => "selected",
148 SurfaceRole::Current => "current",
149 SurfaceRole::Input => "input",
150 SurfaceRole::Danger => "danger",
151 }
152 }
153
154 /// Stable numeric id for the role, passed to shaders as an `f32` uniform.
155 pub fn uniform_id(self) -> f32 {
156 match self {
157 SurfaceRole::None => 0.0,
158 SurfaceRole::Panel => 1.0,
159 SurfaceRole::Raised => 2.0,
160 SurfaceRole::Sunken => 3.0,
161 SurfaceRole::Popover => 4.0,
162 SurfaceRole::Selected => 5.0,
163 SurfaceRole::Current => 6.0,
164 SurfaceRole::Input => 7.0,
165 SurfaceRole::Danger => 8.0,
166 }
167 }
168}
169
170/// Interaction state, applied as a render-time visual delta.
171#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
172#[non_exhaustive]
173pub enum InteractionState {
174 /// The resting state; no visual delta.
175 #[default]
176 Default,
177 /// The pointer is over the element.
178 Hover,
179 /// The element is being pressed.
180 Press,
181 /// The element has keyboard focus.
182 Focus,
183 /// The element does not respond to interaction.
184 Disabled,
185 /// The element is waiting on an in-flight operation.
186 Loading,
187}
188
189/// Recorded source location for an element. Set automatically via
190/// `#[track_caller]` on every constructor.
191///
192/// `from_library` distinguishes Els constructed inside damascene's own
193/// widget closures (where the closure boundary defeats
194/// `#[track_caller]` and the recorded location lands inside damascene-core
195/// instead of at the user's call site) from Els constructed in user
196/// code. The lint pass uses this to gate user-facing findings and to
197/// walk blame attribution upward to the nearest user-source ancestor.
198/// Set explicitly via [`crate::tree::El::from_library`] at the few
199/// closure-builder sites that need it.
200#[derive(Clone, Copy, Debug, Default)]
201#[non_exhaustive]
202pub struct Source {
203 /// Source file path, as reported by `Location::file()`.
204 pub file: &'static str,
205 /// 1-based line number within `file`.
206 pub line: u32,
207 /// True when the El was constructed inside damascene's own widget
208 /// closures rather than user code (see the struct-level doc).
209 pub from_library: bool,
210}
211
212impl Source {
213 /// Build a `Source` from a `#[track_caller]` location, with
214 /// `from_library` set to `false`.
215 pub fn from_caller(loc: &'static Location<'static>) -> Self {
216 Self {
217 file: loc.file(),
218 line: loc.line(),
219 from_library: false,
220 }
221 }
222}