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
//! 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, FontFamily, FontWeight, Rect, TextWrap};
/// 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,
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: f32,
fit: ImageFit,
},
/// Mid-frame snapshot of the current target into a sampled texture,
/// scheduled before any backdrop-sampling pass.
BackdropSnapshot,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
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, .. } => 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 { .. } => 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, .. } => *scissor,
DrawOp::BackdropSnapshot => None,
}
}
}