aetna_core/tree/semantics.rs
1//! Semantic node and paint roles carried by [`El`](crate::El).
2
3use std::panic::Location;
4
5/// Semantic identity of an element. Roughly an HTML tag.
6#[derive(Clone, Debug, PartialEq, Eq)]
7pub enum Kind {
8 /// A bare layout container with no inherent visuals.
9 Group,
10 Card,
11 Button,
12 Badge,
13 Text,
14 Heading,
15 Spacer,
16 Divider,
17 Overlay,
18 Scrim,
19 Modal,
20 /// A vertically scrollable region.
21 Scroll,
22 /// Vertically scrollable region whose children are produced lazily.
23 VirtualList,
24 /// Block whose direct children flow inline.
25 Inlines,
26 /// Forced line break inside a `Kind::Inlines` block.
27 HardBreak,
28 /// Native mathematical notation. Carries a [`crate::math::MathExpr`]
29 /// and renders through Aetna's math box layout.
30 Math,
31 /// Raster image element.
32 Image,
33 /// App-owned GPU texture composited into the paint stream. Backed
34 /// by [`crate::surface::AppTexture`] and the [`crate::tree::surface`]
35 /// builder; the backend samples the texture during paint instead
36 /// of uploading pixels.
37 ///
38 /// The texture stretches across the resolved rect with bilinear
39 /// filtering — source pixel dimensions and rendered size are
40 /// independent. See [`crate::tree::surface`] for the full sizing /
41 /// aspect-ratio contract.
42 Surface,
43 /// App-supplied vector asset. Backed by
44 /// [`crate::vector::VectorAsset`] and the [`crate::tree::vector`]
45 /// builder; callers explicitly choose painted vector rendering or
46 /// one-colour mask rendering. Unlike [`Kind::Image`] (icon-styled,
47 /// square-shaped), this is the general-purpose path for arbitrary-
48 /// aspect vector content — commit-graph curves, Gantt connectors,
49 /// custom chart marks.
50 Vector,
51 /// Escape hatch for app-defined components.
52 Custom(&'static str),
53}
54
55/// Semantic paint role for rect-shaped surfaces.
56///
57/// Each variant maps to a theme-applied recipe at paint time. Roles are
58/// either *decorative* (set stroke + shadow on top of whatever fill the
59/// node already carries) or *fill-providing* (default a fill from the
60/// palette when the node has none). The split matters: setting a
61/// decorative role on a node with no fill produces an "invisible
62/// surface" — only a thin border over the parent's background. For
63/// panel-shaped containers, prefer the dedicated widget (`card()`,
64/// `sidebar()`, `dialog()`, `popover()`) which bundles role + fill +
65/// stroke + radius + shadow correctly.
66#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
67pub enum SurfaceRole {
68 /// No special semantic role. Theme fallback applies.
69 #[default]
70 None,
71 /// **Decorative.** Border + small drop shadow. *Does not paint a
72 /// fill* — the node must supply one (e.g. `tokens::CARD`) or sit
73 /// inside a widget like `card()` / `sidebar()` that does.
74 Panel,
75 /// **Decorative.** Border + half-strength shadow, suggesting one
76 /// elevation step above its parent. Like `Panel`, no fill.
77 Raised,
78 /// **Fill-providing.** Slightly darker variant of `MUTED` (palette
79 /// `darken(0.08)`) with input-toned border. Use for inset bands —
80 /// search wells, segmented-control tracks, recessed list headers.
81 Sunken,
82 /// **Decorative.** Input-toned border + large drop shadow for
83 /// floating panels. Used by `popover()` and friends; bring your
84 /// own fill (typically `tokens::POPOVER`).
85 Popover,
86 /// **Fill-providing.** PRIMARY-tinted alpha 28 fill +
87 /// PRIMARY-tinted alpha 110 border. The selected item inside a
88 /// collection. Prefer the `.selected()` chainable, which sets this
89 /// role plus content color in one call.
90 Selected,
91 /// **Fill-providing.** Solid `ACCENT` fill + neutral border for
92 /// the current page / nav item. Prefer the `.current()` chainable,
93 /// which also bumps font weight and content color.
94 Current,
95 /// **Fill-providing.** Same recipe as `Sunken` — used by text
96 /// inputs and other editable surfaces.
97 Input,
98 /// **Decorative.** Destructive-toned border, no shadow. Pair with
99 /// a tint fill (e.g. `tokens::DESTRUCTIVE.with_alpha(40)`) for the
100 /// classic "danger" band in a form or section header.
101 Danger,
102}
103
104impl SurfaceRole {
105 pub fn name(self) -> &'static str {
106 match self {
107 SurfaceRole::None => "none",
108 SurfaceRole::Panel => "panel",
109 SurfaceRole::Raised => "raised",
110 SurfaceRole::Sunken => "sunken",
111 SurfaceRole::Popover => "popover",
112 SurfaceRole::Selected => "selected",
113 SurfaceRole::Current => "current",
114 SurfaceRole::Input => "input",
115 SurfaceRole::Danger => "danger",
116 }
117 }
118
119 pub fn uniform_id(self) -> f32 {
120 match self {
121 SurfaceRole::None => 0.0,
122 SurfaceRole::Panel => 1.0,
123 SurfaceRole::Raised => 2.0,
124 SurfaceRole::Sunken => 3.0,
125 SurfaceRole::Popover => 4.0,
126 SurfaceRole::Selected => 5.0,
127 SurfaceRole::Current => 6.0,
128 SurfaceRole::Input => 7.0,
129 SurfaceRole::Danger => 8.0,
130 }
131 }
132}
133
134/// Interaction state, applied as a render-time visual delta.
135#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
136#[non_exhaustive]
137pub enum InteractionState {
138 #[default]
139 Default,
140 Hover,
141 Press,
142 Focus,
143 Disabled,
144 Loading,
145}
146
147/// Recorded source location for an element. Set automatically via
148/// `#[track_caller]` on every constructor.
149///
150/// `from_library` distinguishes Els constructed inside aetna's own
151/// widget closures (where the closure boundary defeats
152/// `#[track_caller]` and the recorded location lands inside aetna-core
153/// instead of at the user's call site) from Els constructed in user
154/// code. The lint pass uses this to gate user-facing findings and to
155/// walk blame attribution upward to the nearest user-source ancestor.
156/// Set explicitly via [`crate::tree::El::from_library`] at the few
157/// closure-builder sites that need it.
158#[derive(Clone, Copy, Debug, Default)]
159#[non_exhaustive]
160pub struct Source {
161 pub file: &'static str,
162 pub line: u32,
163 pub from_library: bool,
164}
165
166impl Source {
167 pub fn from_caller(loc: &'static Location<'static>) -> Self {
168 Self {
169 file: loc.file(),
170 line: loc.line(),
171 from_library: false,
172 }
173 }
174}