Skip to main content

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}