aetna_core/ir.rs
1//! Backend-neutral draw-op IR.
2//!
3//! Every visual fact in the laid-out tree resolves to a [`DrawOp`] bound
4//! to a [`ShaderHandle`] and a uniform block. The wgpu renderer dispatches
5//! by shader handle; the SVG fallback (`crate::bundle::svg`) interprets stock
6//! shaders best-effort and emits placeholder rects for custom ones.
7//!
8//! `BackdropSnapshot` is emitted by [`crate::runtime::RunnerCore`] when
9//! the resolved paint stream first needs a backdrop-sampling shader. See
10//! `docs/SHADER_VISION.md` for the backend contract.
11//!
12//! # Why DrawOp over RenderCmd
13//!
14//! Aetna keeps visual material decisions in shader handles and uniform blocks
15//! instead of baking CSS-shaped fields into the IR. Rect colors, gradients,
16//! shadows, focus rings, and glass effects resolve to stock shader uniforms or
17//! custom shader bindings before a backend records GPU commands.
18
19use crate::image::{Image, ImageFit};
20use crate::shader::{ShaderHandle, UniformBlock};
21use crate::svg_icon::IconSource;
22use crate::text::atlas::RunStyle;
23use crate::text::metrics::TextLayout;
24use crate::tree::{Color, Corners, FontFamily, FontWeight, Rect, TextWrap};
25use crate::vector::VectorRenderMode;
26
27/// One paint operation in the laid-out frame.
28#[derive(Clone, Debug)]
29pub enum DrawOp {
30 /// A rectangular region painted by a shader (typically
31 /// `stock::rounded_rect`, but custom shaders also emit `Quad`).
32 Quad {
33 id: String,
34 rect: Rect,
35 scissor: Option<Rect>,
36 shader: ShaderHandle,
37 uniforms: UniformBlock,
38 },
39 /// A run of text. The draw op carries the author text and measured layout;
40 /// backends shape/rasterize through the shared glyph atlas path.
41 GlyphRun {
42 id: String,
43 rect: Rect,
44 scissor: Option<Rect>,
45 shader: ShaderHandle,
46 /// Carried explicitly on the op for SVG fallback and backend text
47 /// shaping.
48 color: Color,
49 text: String,
50 size: f32,
51 line_height: f32,
52 family: FontFamily,
53 /// Monospace face used when `mono` is set. Stamped from the
54 /// source El's `mono_font_family` (themed via
55 /// `Theme::mono_font_family`).
56 mono_family: FontFamily,
57 weight: FontWeight,
58 mono: bool,
59 wrap: TextWrap,
60 anchor: TextAnchor,
61 layout: TextLayout,
62 /// Underline / strikethrough state lifted from the source El's
63 /// `text_underline` / `text_strikethrough`. Backends fold them
64 /// into the synthesized [`RunStyle`] before shaping so the
65 /// decoration pass in [`crate::text::atlas`] runs uniformly
66 /// for standalone leaves and attributed paragraphs.
67 underline: bool,
68 strikethrough: bool,
69 /// Optional link URL from the El's `text_link`. Carried for
70 /// future hit-test work; today it just pins color + underline
71 /// via [`RunStyle::with_link`].
72 link: Option<String>,
73 },
74 /// An attributed paragraph: a sequence of styled runs that flow
75 /// together inside one `rect`. The runtime hands `runs` straight to
76 /// [`crate::text::atlas::GlyphAtlas::shape_and_rasterize_runs`] so
77 /// wrapping decisions cross run boundaries (real prose, not glued
78 /// segments). `layout` is an approximate pre-shaping measurement
79 /// from `text::metrics` — backends shape for accurate placement;
80 /// SVG uses it to lay tspan baselines.
81 AttributedText {
82 id: String,
83 rect: Rect,
84 scissor: Option<Rect>,
85 shader: ShaderHandle,
86 /// Source-order styled spans. Each `String` may contain
87 /// embedded `\n` to express in-paragraph hard breaks.
88 runs: Vec<(String, RunStyle)>,
89 size: f32,
90 line_height: f32,
91 wrap: TextWrap,
92 anchor: TextAnchor,
93 layout: TextLayout,
94 },
95 /// A vector icon scaled into `rect`. The `source` is either a
96 /// built-in [`crate::tree::IconName`] (24x24 lucide-style) or an
97 /// app-supplied [`crate::SvgIcon`]. SVG bundle output renders the
98 /// vector paths directly; wgpu/vulkano backends bake an MTSDF (or
99 /// tessellate for non-flat materials); backends without a native
100 /// vector painter fall back to a glyph for built-ins.
101 Icon {
102 id: String,
103 rect: Rect,
104 scissor: Option<Rect>,
105 source: IconSource,
106 color: Color,
107 size: f32,
108 stroke_width: f32,
109 },
110 /// A raster image painted into `rect`. The `image` carries the
111 /// pixel data (Arc-shared with the source El) and is keyed by
112 /// `image.content_hash()` in backend texture caches. `rect` is the
113 /// post-`fit` destination; for `Cover` it can extend past the El's
114 /// content area and is clipped via `scissor`. SVG bundle output
115 /// emits a placeholder rect labelled with the image's hash.
116 Image {
117 id: String,
118 rect: Rect,
119 scissor: Option<Rect>,
120 image: Image,
121 tint: Option<Color>,
122 radius: Corners,
123 fit: ImageFit,
124 },
125 /// An app-owned GPU texture composited into the paint stream.
126 /// Unlike `DrawOp::Image`, the backend does not upload pixels —
127 /// it samples the existing texture identified by `texture` during
128 /// paint, keying its bind-group cache on
129 /// [`crate::surface::AppTextureId`]. `rect` is the post-`fit`
130 /// destination rect; for `Cover` it can extend past the El's
131 /// content area and is clipped via `scissor`. `transform` is an
132 /// affine applied to the textured quad in destination space,
133 /// around the centre of `rect`. `alpha` selects the blend path.
134 /// SVG bundle output emits a placeholder rect labelled with the
135 /// texture's id.
136 AppTexture {
137 id: String,
138 rect: Rect,
139 scissor: Option<Rect>,
140 texture: crate::surface::AppTexture,
141 alpha: crate::surface::SurfaceAlpha,
142 fit: ImageFit,
143 transform: crate::affine::Affine2,
144 },
145 /// An app-supplied vector asset. `render_mode` decides whether the
146 /// backend preserves authored paint or treats the asset as a
147 /// one-colour mask. SVG bundle output emits a placeholder rect
148 /// labelled with the asset's hash.
149 Vector {
150 id: String,
151 rect: Rect,
152 scissor: Option<Rect>,
153 asset: std::sync::Arc<crate::vector::VectorAsset>,
154 render_mode: VectorRenderMode,
155 },
156 /// Mid-frame snapshot of the current target into a sampled texture,
157 /// scheduled before any backdrop-sampling pass.
158 BackdropSnapshot,
159}
160
161#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
162pub enum TextAnchor {
163 Start,
164 Middle,
165 End,
166}
167
168impl DrawOp {
169 pub fn id(&self) -> &str {
170 match self {
171 DrawOp::Quad { id, .. }
172 | DrawOp::GlyphRun { id, .. }
173 | DrawOp::AttributedText { id, .. }
174 | DrawOp::Icon { id, .. }
175 | DrawOp::Image { id, .. }
176 | DrawOp::AppTexture { id, .. }
177 | DrawOp::Vector { id, .. } => id,
178 DrawOp::BackdropSnapshot => "<backdrop-snapshot>",
179 }
180 }
181 pub fn shader(&self) -> Option<&ShaderHandle> {
182 match self {
183 DrawOp::Quad { shader, .. }
184 | DrawOp::GlyphRun { shader, .. }
185 | DrawOp::AttributedText { shader, .. } => Some(shader),
186 DrawOp::Icon { .. }
187 | DrawOp::Image { .. }
188 | DrawOp::AppTexture { .. }
189 | DrawOp::Vector { .. } => None,
190 DrawOp::BackdropSnapshot => None,
191 }
192 }
193 pub fn scissor(&self) -> Option<Rect> {
194 match self {
195 DrawOp::Quad { scissor, .. }
196 | DrawOp::GlyphRun { scissor, .. }
197 | DrawOp::AttributedText { scissor, .. }
198 | DrawOp::Icon { scissor, .. }
199 | DrawOp::Image { scissor, .. }
200 | DrawOp::AppTexture { scissor, .. }
201 | DrawOp::Vector { scissor, .. } => *scissor,
202 DrawOp::BackdropSnapshot => None,
203 }
204 }
205}