jag_draw/display_list.rs
1use crate::scene::*;
2use std::path::PathBuf;
3
4#[derive(Clone, Copy, Debug, Default)]
5pub struct Viewport {
6 pub width: u32,
7 pub height: u32,
8}
9
10/// Opaque handle for an externally-managed texture (e.g., 3D viewport).
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
12pub struct ExternalTextureId(pub u64);
13
14#[derive(Clone, Debug)]
15pub enum Command {
16 DrawRect {
17 rect: Rect,
18 brush: Brush,
19 z: i32,
20 transform: Transform2D,
21 },
22 DrawRoundedRect {
23 rrect: RoundedRect,
24 brush: Brush,
25 z: i32,
26 transform: Transform2D,
27 },
28 StrokeRect {
29 rect: Rect,
30 stroke: Stroke,
31 brush: Brush,
32 z: i32,
33 transform: Transform2D,
34 },
35 StrokeRoundedRect {
36 rrect: RoundedRect,
37 stroke: Stroke,
38 brush: Brush,
39 z: i32,
40 transform: Transform2D,
41 },
42 DrawText {
43 run: TextRun,
44 z: i32,
45 transform: Transform2D,
46 id: u64,
47 dynamic: bool,
48 },
49 DrawEllipse {
50 center: [f32; 2],
51 radii: [f32; 2],
52 brush: Brush,
53 z: i32,
54 transform: Transform2D,
55 },
56 /// Filled path (solid color only for now)
57 FillPath {
58 path: Path,
59 color: ColorLinPremul,
60 z: i32,
61 transform: Transform2D,
62 },
63 /// Stroked path (width only; round join/cap for now)
64 StrokePath {
65 path: Path,
66 stroke: Stroke,
67 color: ColorLinPremul,
68 z: i32,
69 transform: Transform2D,
70 },
71 /// Box shadow for a rounded rectangle. This is handled by a dedicated pass in PassManager,
72 /// not by the generic solid fill pipeline.
73 BoxShadow {
74 rrect: RoundedRect,
75 spec: BoxShadowSpec,
76 z: i32,
77 transform: Transform2D,
78 },
79 /// Hit-only regions that do not render. Useful for scene-surface hits or custom zones.
80 HitRegionRect {
81 id: u32,
82 rect: Rect,
83 z: i32,
84 transform: Transform2D,
85 },
86 HitRegionRoundedRect {
87 id: u32,
88 rrect: RoundedRect,
89 z: i32,
90 transform: Transform2D,
91 },
92 HitRegionEllipse {
93 id: u32,
94 center: [f32; 2],
95 radii: [f32; 2],
96 z: i32,
97 transform: Transform2D,
98 },
99 /// SVG draw at a pixel origin with a max pixel size.
100 /// The path is interpreted relative to the process working directory.
101 DrawSvg {
102 path: PathBuf,
103 origin: [f32; 2],
104 max_size: [f32; 2],
105 z: i32,
106 transform: Transform2D,
107 },
108 /// Raster image draw (PNG/JPEG/GIF/WebP) at a pixel origin with a given size.
109 /// The path is interpreted relative to the process working directory.
110 DrawImage {
111 path: PathBuf,
112 origin: [f32; 2],
113 size: [f32; 2],
114 z: i32,
115 transform: Transform2D,
116 },
117 /// Hyperlink with text, optional underline, and click target.
118 DrawHyperlink {
119 hyperlink: Hyperlink,
120 z: i32,
121 transform: Transform2D,
122 /// Unique ID for this hyperlink instance (for hit testing)
123 id: u64,
124 },
125 PushClip(ClipRect),
126 PopClip,
127 PushTransform(Transform2D),
128 PopTransform,
129 /// Push a CSS-style group opacity for descendant draw commands.
130 /// Commands between `PushOpacity` and `PopOpacity` are composited to an
131 /// intermediate layer, then blended back once with this opacity.
132 PushOpacity(f32),
133 PopOpacity,
134 /// Blit an externally-rendered texture (e.g., 3D viewport) into the scene.
135 DrawExternalTexture {
136 rect: Rect,
137 texture_id: ExternalTextureId,
138 z: i32,
139 transform: Transform2D,
140 /// Additional alpha multiplier applied at composite time.
141 opacity: f32,
142 /// Whether the source texture is already premultiplied alpha.
143 premultiplied: bool,
144 },
145}
146
147impl Command {
148 /// Get the z-index of this command, or None for non-drawable commands
149 pub fn z_index(&self) -> Option<i32> {
150 match self {
151 Command::DrawRect { z, .. } => Some(*z),
152 Command::DrawRoundedRect { z, .. } => Some(*z),
153 Command::StrokeRect { z, .. } => Some(*z),
154 Command::StrokeRoundedRect { z, .. } => Some(*z),
155 Command::DrawText { z, .. } => Some(*z),
156 Command::DrawEllipse { z, .. } => Some(*z),
157 Command::FillPath { z, .. } => Some(*z),
158 Command::StrokePath { z, .. } => Some(*z),
159 Command::BoxShadow { z, .. } => Some(*z),
160 Command::HitRegionRect { z, .. } => Some(*z),
161 Command::HitRegionRoundedRect { z, .. } => Some(*z),
162 Command::HitRegionEllipse { z, .. } => Some(*z),
163 Command::DrawImage { z, .. } => Some(*z),
164 Command::DrawSvg { z, .. } => Some(*z),
165 Command::DrawHyperlink { z, .. } => Some(*z),
166 Command::DrawExternalTexture { z, .. } => Some(*z),
167 _ => None,
168 }
169 }
170}
171
172#[derive(Clone, Debug, Default)]
173pub struct DisplayList {
174 pub viewport: Viewport,
175 pub commands: Vec<Command>,
176}
177
178impl DisplayList {
179 /// Sort commands by z-index while preserving clip/transform stack structure.
180 /// This is a simplified implementation that sorts drawable commands but keeps
181 /// transform/clip commands in their original order. For proper z-ordering with
182 /// transforms and clips, each drawable should store its full transform/clip state.
183 pub fn sort_by_z(&mut self) {
184 // Sort by z-index. Rust's sort_by is stable, preserving relative order of equal elements.
185 // This means transform/clip commands (which have no z-index) will stay in order,
186 // but drawable commands will be sorted by z-index.
187 self.commands.sort_by(|a, b| {
188 match (a.z_index(), b.z_index()) {
189 (Some(z_a), Some(z_b)) => z_a.cmp(&z_b),
190 (Some(_), None) => std::cmp::Ordering::Greater, // Drawables after non-drawables
191 (None, Some(_)) => std::cmp::Ordering::Less, // Non-drawables before drawables
192 (None, None) => std::cmp::Ordering::Equal, // Preserve order for non-drawables
193 }
194 });
195 }
196}
197
198/// Convert z-index to depth value for GPU depth testing.
199/// Maps z-index range to [0.0, 1.0] where lower z-index = closer to camera = lower depth.
200///
201/// Strategy:
202/// - z = 0 maps to depth 0.5 (middle)
203/// - Negative z (closer) maps to (0.0, 0.5)
204/// - Positive z (farther) maps to (0.5, 1.0)
205/// - Clamps to reasonable range to avoid precision issues
206pub fn z_index_to_depth(z: i32) -> f32 {
207 // Clamp z-index to reasonable range [-1000000, 1000000].
208 // Compositor windows use z_base up to 20000, plus per-window z offsets
209 // up to ~12000 (AppWindow chrome), so ~32000+ per window.
210 // Shell overlays (launcher) use z ~500000 to stay above all windows.
211 let z_clamped = z.clamp(-1_000_000, 1_000_000) as f32;
212
213 // Map to [0.0, 1.0] with 0.5 as center
214 // HIGHER z-index = closer = LOWER depth value (rendered on top)
215 // Negate z to invert the mapping
216 let normalized = (-z_clamped / 1_000_000.0) * 0.5 + 0.5;
217
218 // Clamp to valid depth range
219 normalized.clamp(0.0, 1.0)
220}