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