git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

src/scene/paint.rs (108.5K)

   1 //! Display list + paint context — Phase 3 of the core rebuild (single paint path).
   2 //!
   3 //! Today the toolkit paints through **three** uncoordinated routes — the app's top-level
   4 //! `view*` methods, each container's recursive `all_quads`/`all_rounded_quads` (with clipping
   5 //! hand-copied into every container), and the immediate-mode `render_widget`/`SectionContext`.
   6 //! Nothing arbitrates z-order (hence the `overlay_quads` escape hatch) and every clip is CPU
   7 //! rect-intersection math duplicated per container (there is no GPU scissor).
   8 //!
   9 //! This module is the foundation for collapsing those into **one** ordered pass: a paint walk
  10 //! emits primitives into a single [`DisplayList`] through a [`PaintCtx`] that carries a **clip
  11 //! stack** (each pushed clip is intersected with the current one, so a primitive records the exact
  12 //! scissor rect it should be drawn under) and a **translate stack** (local coordinates compose to
  13 //! absolute — the seam Phase 4 animation slides/scales through). The backend then tessellates the
  14 //! one ordered list, using the recorded clip as a GPU `set_scissor_rect`.
  15 //!
  16 //! This first cut is pure data + bookkeeping, fully unit-tested without a GPU. Wiring the widget
  17 //! tree's paint into it, and routing the backend through the result, are the runtime-gated
  18 //! follow-ups.
  19 
  20 use crate::scene::layout::Rect;
  21 use crate::scene::material::{Finish, Material, PlateRole};
  22 
  23 /// End-cap style for a [`Prim::Vector`], mirroring the toolkit's line caps.
  24 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
  25 pub enum Cap {
  26     Flat,
  27     Round,
  28     Arrow,
  29 }
  30 
  31 /// A single paint primitive in logical pixels (absolute coordinates once emitted). These mirror
  32 /// the toolkit's existing tessellators so a `DisplayList` maps directly onto them at draw time.
  33 /// Per-corner radii `(top_left, top_right, bottom_right, bottom_left)`, matching the toolkit's
  34 /// `CornerRadii` order.
  35 pub type Radii = (f32, f32, f32, f32);
  36 
  37 /// RFC Phase 7b: the ONE description of a lit base surface — a window's root
  38 /// plate or a nested pane plate — distinguished only by ROLE data, never by
  39 /// type. A window root is a plate whose four corners are all window corners;
  40 /// detaching a pane into its own window is a role flip, nothing more.
  41 ///
  42 /// The material's tint always carries POSITIVE alpha; the frost encoding is
  43 /// applied by [`Self::fill`] per the role (see the Phase 7b blur-regime note
  44 /// in `docs/rfc-core-rebuild.md`): a root plate stays positive-alpha (the
  45 /// COMPOSITOR frosts behind the window), a nested plate whose material is
  46 /// [`Frost::Frosted`] encodes the in-app frost pass's negative-alpha sentinel.
  47 #[derive(Debug, Clone, Copy, PartialEq)]
  48 pub struct PlateSpec {
  49     pub rect: Rect,
  50     /// What the plate is made of: tint, frost and finish
  51     /// (`docs/rfc-material.md`). `Material::root()` / `Material::pane()` are
  52     /// the rung defaults; `Material::opaque(c)` an app's own colour.
  53     pub material: Material,
  54     /// Which corners lie ON the window silhouette (TL, TR, BR, BL).
  55     pub window_corners: (bool, bool, bool, bool),
  56     /// Transition-band width of the rolled perimeter. Negative = the fill-less
  57     /// roll-overlay sentinel (see [`PaintCtx::plate`]).
  58     pub depth: f32,
  59 }
  60 
  61 impl PlateSpec {
  62     /// THE standard root plate of a `width` x `height` window — the base
  63     /// surface every cce app stands its panes and controls on: the whole
  64     /// window, the root rung's material ([`Material::root`], which is the
  65     /// DE's `style.surface.plate.root.color` at its configured opacity
  66     /// unless a `material=` is bound), all four corners on the silhouette,
  67     /// and the perimeter rolled over [`crate::layout::bevel_width`].
  68     ///
  69     /// This is the spec every app used to hand-copy as an eight-line block
  70     /// (page-low colour, opacity override, four window corners, the DE roll)
  71     /// — the copies are gone, and a window whose base is anything else is
  72     /// off the standard on purpose, which its code should say. Emit it with
  73     /// [`PaintCtx::root_plate`]; deviate with [`Self::with_material`] /
  74     /// [`Self::with_depth`] (cce-system-interface's own tint, an overlay's
  75     /// shallower roll).
  76     pub fn window(width: f32, height: f32) -> Self {
  77         Self::root_at(Rect { x: 0.0, y: 0.0, width, height })
  78     }
  79 
  80     /// [`Self::window`] for a root plate that is not the whole surface — a
  81     /// layer-shell overlay drawing the window silhouette itself inside a
  82     /// larger transparent surface (cce-cloud). Same material, corners and
  83     /// roll; `rect` is where the "window" is.
  84     pub fn root_at(rect: Rect) -> Self {
  85         Self {
  86             rect,
  87             material: Material::root(),
  88             window_corners: (true, true, true, true),
  89             depth: crate::layout::bevel_width(),
  90         }
  91     }
  92 
  93     /// This plate made of `material` instead of its rung's default.
  94     pub fn with_material(mut self, material: Material) -> Self {
  95         self.material = material;
  96         self
  97     }
  98 
  99     /// This plate with a `depth` roll instead of the DE's `bevel_width`.
 100     pub fn with_depth(mut self, depth: f32) -> Self {
 101         self.depth = depth;
 102         self
 103     }
 104 
 105     /// All four corners on the silhouette: this plate IS the window's base
 106     /// surface.
 107     pub fn is_root(&self) -> bool {
 108         let (tl, tr, br, bl) = self.window_corners;
 109         tl && tr && br && bl
 110     }
 111 
 112     /// Which of `rect`'s corners lie on a `win_w` x `win_h` window's
 113     /// silhouette (edge tolerance 1.5px) — the designer's `pane_plate_radii`
 114     /// derivation, toolkit-side.
 115     pub fn window_corner_flags(rect: Rect, win_w: f32, win_h: f32) -> (bool, bool, bool, bool) {
 116         let e = 1.5;
 117         let left = rect.x <= e;
 118         let top = rect.y <= e;
 119         let right = rect.x + rect.width >= win_w - e;
 120         let bottom = rect.y + rect.height >= win_h - e;
 121         (top && left, top && right, bottom && right, bottom && left)
 122     }
 123 
 124     /// Per-corner radii for `flags`: a window corner wears the SHARED
 125     /// silhouette curve (`window_corner_radius * corner_span_factor` — the
 126     /// compositor clips the window and the desktop grid draws its cells from
 127     /// the same value, so window-corner arcs must follow it, never a per-app
 128     /// plate override); an interior corner wears the nominal
 129     /// `plate_corner_radius`.
 130     pub fn radii_for(flags: (bool, bool, bool, bool)) -> Radii {
 131         let nominal = crate::layout::plate_corner_radius();
 132         let window_r = crate::layout::window_silhouette_radius();
 133         let (tl, tr, br, bl) = flags;
 134         let pick = |on: bool| if on { window_r } else { nominal };
 135         (pick(tl), pick(tr), pick(br), pick(bl))
 136     }
 137 
 138     /// [`Self::radii_for`] over this spec's flags.
 139     pub fn radii(&self) -> Radii {
 140         Self::radii_for(self.window_corners)
 141     }
 142 
 143     /// This plate detached into its own window (RFC Phase 7c): every corner
 144     /// becomes a window corner, and with the role the radii snap to the
 145     /// silhouette curve and [`Self::fill`] flips frost regimes (the
 146     /// compositor's blur-behind takes over from the in-app sentinel). The
 147     /// reverse — reattaching — is the host assigning its computed
 148     /// `window_corner_flags` back.
 149     pub fn detached(mut self) -> Self {
 150         self.window_corners = (true, true, true, true);
 151         self
 152     }
 153 
 154     /// The frost regime this plate is under: [`PlateRole::Root`] when it IS
 155     /// the window's base surface, [`PlateRole::Nested`] otherwise.
 156     pub fn role(&self) -> PlateRole {
 157         if self.is_root() { PlateRole::Root } else { PlateRole::Nested }
 158     }
 159 
 160     /// The fill with the role-correct frost encoding: root → alpha forced
 161     /// non-negative (the compositor's frost, not ours), nested + frosted →
 162     /// the in-app frost pass's negative-alpha sentinel. The rule itself is
 163     /// [`Material::fill_tint`], the one place a negative alpha is written.
 164     pub fn fill(&self) -> [f32; 4] {
 165         self.material.fill(self.role())
 166     }
 167 }
 168 
 169 /// The **relief primitives** are the members of this enum that describe a lit
 170 /// surface rather than a flat fill: [`Prim::Bevel`], [`Prim::Plate`],
 171 /// [`Prim::Recess`], [`Prim::Boss`], [`Prim::Ridge`], [`Prim::ConcaveFillet`],
 172 /// [`Prim::Groove`], [`Prim::Lattice`], [`Prim::CarveUnion`] and [`Prim::Sphere`]. They share one lighting model — the
 173 /// DE's light vector, roll width and profile, per-pixel through shader2d's
 174 /// SDF branch (see `crate::layout::bevel_shader`) — and split in two:
 175 ///
 176 /// - **plates** carry their own fill: `Bevel`, `Plate`. Shader mode 1.
 177 /// - **carves** emit shading ONLY, no fill, over whatever is already painted
 178 ///   beneath: `Recess`, `Boss`, `Ridge`, `ConcaveFillet`, `Groove`, `Lattice`,
 179 ///   `CarveUnion`. Modes 2-4, 6-8, 13 and 14. (`Sphere`, mode 5, is neither —
 180 ///   a lit ball under the same model.)
 181 ///
 182 /// That split is load-bearing for flat-path hosts, which need one list for the
 183 /// faces and another for the edges drawn over them (cce-files' `rects` vs
 184 /// `reliefs`).
 185 ///
 186 /// How a control plate sits on the surface beneath it — see "Plates, wells
 187 /// and seams" in `CLAUDE.md`.
 188 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 189 pub enum PlateStance {
 190     /// Floats above the surface: a [`Prim::Bevel`] when it has a face of its
 191     /// own, an edges-only [`Prim::Boss`] carved inside its footprint when the
 192     /// face is transparent (the surface below shows through as the face).
 193     Raised,
 194     /// Level with the surface inside a groove ring: a [`Prim::Trough`] carved
 195     /// inside its footprint, with the face as a flat fill when it has one
 196     /// ([`PaintCtx::inset_plate`]).
 197     Flush,
 198     /// No relief at all — the face alone, filling the footprint as a flat
 199     /// rounded rect. This is the PANE rung's material brought down to the
 200     /// control rung, and it exists because the other two stances cannot give
 201     /// a control two things a pane has:
 202     ///
 203     /// - **Its silhouette IS its rect.** `Raised` and `Flush` both carve
 204     ///   inside the footprint, so their visible edge sits half the carve depth
 205     ///   in and a control laid out on the same numbers as a pane does not line
 206     ///   up with it. Nothing is inset here, so it does.
 207     /// - **It can be frosted.** The blur-behind sentinel (a negative alpha)
 208     ///   only reaches quads, and the relief stances lay their face through
 209     ///   `Border`/`Trough` strokes. This one fills with a quad, so a control
 210     ///   can be made of the same frosted material as the pane behind it.
 211     ///
 212     /// The cost is that a flat fill carries ONE radius, not four: the
 213     /// per-corner silhouette a nested relief control computes (Dropdown's
 214     /// concentric corner adjustment) has no equivalent here, and `radii.0` is
 215     /// used for all four corners. A focus `tint` is drawn as a ring, since
 216     /// there is no rim to light.
 217     Flat,
 218 }
 219 
 220 /// How far past a [`Prim::Field`]'s end its run's end is put to say the run
 221 /// REACHES that end and there is no well on that side: far enough that the
 222 /// blend across the seam and the seam's own wall land nowhere near the
 223 /// field. An encoding of the prim's; a [`Field`] says it by its form.
 224 pub const FIELD_RUN_ONLY: f32 = 1.0e4;
 225 
 226 /// How near a run's end may come to the field's end and still be taken as
 227 /// reaching it — the shader's own tolerance (half a pixel, `MODE_FIELD`).
 228 const FIELD_REACH: f32 = 0.5;
 229 
 230 /// A FIELD: a well cut into a plate with a flush plate, the RUN, standing
 231 /// in it, one outline round both. One object in every form a control takes
 232 /// — they differ only in where the run is:
 233 ///
 234 /// | form | constructor | run | well |
 235 /// | --- | --- | --- | --- |
 236 /// | text box | [`Field::well`] | none | the whole field |
 237 /// | flush control plate (dropdown, button, …) | [`Field::run`] | the whole field | none |
 238 /// | text row with its picker, spinbox | [`Field::ending_in_run`] | the right end | left of it |
 239 /// | toggle | [`Field::sliding_run`] | part of the field, anywhere | either side of it |
 240 ///
 241 /// Painted by [`PaintCtx::field`], as a [`Prim::Field`] — except a field
 242 /// with no run, which is a [`Prim::Recess`]: that is what a plain well has
 243 /// always been, and a recess groups into the plate under it where a field
 244 /// prim never does. The rect is the field's OUTLINE (the carve's boundary;
 245 /// a widget takes it through [`crate::layout::carve_inside`] from its
 246 /// footprint), and `depth` the wall width.
 247 #[derive(Debug, Clone, Copy, PartialEq)]
 248 pub struct Field {
 249     pub rect: Rect,
 250     pub radii: Radii,
 251     pub depth: f32,
 252     /// The run's span in x, clamped to the outline; `None` for a well.
 253     run: Option<(f32, f32)>,
 254     /// Lights the rim — the focus and hover treatment.
 255     pub tint: Option<[f32; 3]>,
 256 }
 257 
 258 impl Field {
 259     /// All well, no run: a text box.
 260     pub fn well(rect: Rect, radii: Radii, depth: f32) -> Self {
 261         Field { rect, radii, depth, run: None, tint: None }
 262     }
 263 
 264     /// All run, no well: the edge of a flush control plate — a dropdown
 265     /// trigger, a button ([`PaintCtx::inset_plate`]).
 266     pub fn run(rect: Rect, radii: Radii, depth: f32) -> Self {
 267         Self::spanning(rect, radii, depth, rect.x, rect.x + rect.width)
 268     }
 269 
 270     /// A well ending in a run from `split` to the field's right end: a text
 271     /// row with its completion picker, a spinbox with its -/+ run.
 272     pub fn ending_in_run(rect: Rect, radii: Radii, depth: f32, split: f32) -> Self {
 273         Self::spanning(rect, radii, depth, split, rect.x + rect.width)
 274     }
 275 
 276     /// A run `width` wide, at `t` of its travel along the field: 0 is the
 277     /// left end, 1 the right, a well either side between — a toggle, whose
 278     /// run glides from off to on.
 279     pub fn sliding_run(rect: Rect, radii: Radii, depth: f32, width: f32, t: f32) -> Self {
 280         let width = width.clamp(0.0, rect.width);
 281         let x = rect.x + t.clamp(0.0, 1.0) * (rect.width - width);
 282         Self::spanning(rect, radii, depth, x, x + width)
 283     }
 284 
 285     /// A run from `a` to `b`, anywhere in the field — the general form the
 286     /// others are; clamped to the outline.
 287     pub fn spanning(rect: Rect, radii: Radii, depth: f32, a: f32, b: f32) -> Self {
 288         let (l, r) = (rect.x, rect.x + rect.width);
 289         let a = a.clamp(l, r);
 290         let b = b.clamp(a, r);
 291         Field { rect, radii, depth, run: Some((a, b)), tint: None }
 292     }
 293 
 294     /// The rim lit in `tint` (focus, hover), or not.
 295     pub fn with_tint(mut self, tint: Option<[f32; 3]>) -> Self {
 296         self.tint = tint;
 297         self
 298     }
 299 
 300     /// Where the run is, `(left, right)` within the outline; `None` for a
 301     /// field that is all well.
 302     pub fn run_span(&self) -> Option<(f32, f32)> {
 303         self.run
 304     }
 305 
 306     /// Whether any of the field is well — false for a field that is all run.
 307     pub fn has_well(&self) -> bool {
 308         match self.run {
 309             None => true,
 310             Some((a, b)) => a > self.rect.x + FIELD_REACH || b < self.rect.x + self.rect.width - FIELD_REACH,
 311         }
 312     }
 313 
 314     /// The run as [`Prim::Field`] encodes it, `(split, end)`: an end that
 315     /// reaches the field's is put [`FIELD_RUN_ONLY`] past it.
 316     fn prim_span(&self) -> Option<(f32, f32)> {
 317         let (a, b) = self.run?;
 318         let (l, r) = (self.rect.x, self.rect.x + self.rect.width);
 319         let split = if a <= l + FIELD_REACH { l - FIELD_RUN_ONLY } else { a };
 320         let end = if b >= r - FIELD_REACH { r + FIELD_RUN_ONLY } else { b };
 321         Some((split, end))
 322     }
 323 }
 324 
 325 /// A control plate: the thing you press, at the control rung of the plate
 326 /// ladder. One description for every control face — Button, Dropdown,
 327 /// FontSelector, Breadcrumb, a ButtonStrip's selected plateau — so their
 328 /// carve-inside, radius, depth and transparent-face rules cannot drift.
 329 /// Painted by [`PaintCtx::control_plate`]. The root and pane rungs of the
 330 /// ladder are [`PlateSpec`]; this is the same idea one rung down.
 331 ///
 332 /// `rect` is the plate's footprint, the OUTER edge of its silhouette; the
 333 /// carve is taken inside it ([`crate::layout::carve_inside`]), so the gap
 334 /// beside the plate is the gap. `radii` is the silhouette, per corner (a
 335 /// Dropdown nested concentrically in a frame corner adjusts each). `face`
 336 /// is the plate's own material; `None` means the surface below IS the face
 337 /// (edges only), and a frosted material is a real face — the frost carried
 338 /// where the stance can (`Flat`; see [`PlateStance`]). `depth` is the
 339 /// relief's wall width — [`ControlPlate::control`] takes the DE relief width
 340 /// capped at a fifth of the height.
 341 #[derive(Debug, Clone, Copy, PartialEq)]
 342 pub struct ControlPlate {
 343     pub rect: Rect,
 344     pub radii: Radii,
 345     pub stance: PlateStance,
 346     pub face: Option<Material>,
 347     pub depth: f32,
 348     /// The rim's light and shadow tinted this colour: the keyboard-focus
 349     /// ring, drawn on the plate's own relief rather than as extra geometry.
 350     /// `None` untinted.
 351     pub tint: Option<[f32; 3]>,
 352 }
 353 
 354 impl ControlPlate {
 355     /// A control plate at `rect` with a uniform corner `radius`: depth from
 356     /// the DE relief width, capped at a fifth of the plate's height.
 357     pub fn control(rect: Rect, radius: f32, stance: PlateStance, face: Option<Material>) -> Self {
 358         let depth = crate::layout::bevel_width().min(rect.height * 0.2);
 359         Self { rect, radii: (radius, radius, radius, radius), stance, face, depth, tint: None }
 360     }
 361 
 362 
 363     /// Light the rim — the focus ring on the plate's silhouette. Pass the
 364     /// highlight colour while the control holds keyboard focus, `None` otherwise.
 365     pub fn with_tint(mut self, tint: Option<[f32; 3]>) -> Self {
 366         self.tint = tint;
 367         self
 368     }
 369 
 370     /// The DE's focus-ring colour for a plate rim: the highlight accent, the
 371     /// same the wells light their rims with while editing.
 372     pub fn focus_tint() -> [f32; 3] {
 373         let c = crate::color::highlight_primary_color();
 374         [c[0], c[1], c[2]]
 375     }
 376 
 377     /// Per-corner silhouette (a concentric corner-frame adjustment).
 378     pub fn with_radii(mut self, radii: Radii) -> Self {
 379         self.radii = radii;
 380         self
 381     }
 382 
 383     /// An explicit wall width — a plate that shares its depth with the well
 384     /// it stands in, or one capped by its short side rather than its height.
 385     pub fn with_depth(mut self, depth: f32) -> Self {
 386         self.depth = depth;
 387         self
 388     }
 389 
 390     /// The face a stance draws: `Some` only for a material with a visible
 391     /// tint — a transparent one is the surface below showing through, the
 392     /// same as `None`.
 393     pub fn faced(&self) -> Option<&Material> {
 394         self.face.as_ref().filter(|m| m.tint[3] > 0.001)
 395     }
 396 
 397     /// The face as the encoded fill the flat-path bridges consume
 398     /// (`Button::inset_face`, cce-system-interface's `ControlCarve`):
 399     /// transparent for no face, else the material's nested fill.
 400     pub fn face_fill(&self) -> [f32; 4] {
 401         self.face.map_or([0.0; 4], |m| m.fill(PlateRole::Nested))
 402     }
 403 }
 404 
 405 // Call the family **relief primitives** — see `Prim`. (This note sat above `DropletSpec`,
 406 // which moved to `cce_core::droplet`.)
 407 // Call the family **relief primitives**, not "bevel primitives": `Bevel` is one
 408 // specific member — a filled rounded rect plus a lit roll on its lip — and a
 409 // groove, a fillet or a sphere is not a bevel in any sense. "Relief" is also
 410 // what the rest of the stack already says: `layout::control_relief` gates the
 411 // whole family, and the config node is `relief`. The name **bevel** is reserved
 412 // for two things: the `Bevel` prim, and the shared *edge treatment* every
 413 // relief primitive is shaded with (`bevel_width`, `bevel_depth`,
 414 // `bevel_shader`, `bevel_profile` — the lit roll, not the shape).
 415 pub use cce_core::droplet::DropletSpec;
 416 
 417 /// What a droplet's material is shaded with — an extension, since [`DropletSpec`] lives in
 418 /// `cce_core`, which knows no [`Finish`]. `use cce_ui::scene::paint::DropletFinish;` at a
 419 /// call site that writes `spec.finish()`.
 420 pub trait DropletFinish {
 421     /// The drop's finish: its own gleam, shine and rim in the specular,
 422     /// shininess and curvature slots of a [`Finish`] (a drop is wetter than
 423     /// the DE's plates), the shading strength the DE's. The material a
 424     /// droplet is emitted with carries this — `Material::from_fill(c)
 425     /// .with_finish(spec.finish())` — and the tessellator reads it from
 426     /// there like any plate's, instead of packing the slots by hand.
 427     fn finish(&self) -> Finish;
 428 }
 429 
 430 impl DropletFinish for DropletSpec {
 431     fn finish(&self) -> Finish {
 432         Finish { spec: self.gleam, shininess: self.shine, curvature: self.rim, ..Finish::from_style() }
 433     }
 434 }
 435 
 436 #[derive(Clone, Debug, PartialEq)]
 437 pub enum Prim {
 438     Quad { rect: Rect, color: [f32; 4] },
 439     RoundedRect { rect: Rect, radius: f32, corners: (bool, bool, bool, bool), color: [f32; 4] },
 440     /// A rounded fill plus a solid border stroke — a widget's own "plate"
 441     /// (`append_widget_plate`'s non-bevel branch).
 442     Border { rect: Rect, radii: Radii, fill: [f32; 4], border: [f32; 4], thickness: f32 },
 443     /// A beveled plate: a rounded fill at full size plus a light/shadow overlay lip
 444     /// (`append_widget_plate`'s bevel branch). `tint` colours the
 445     /// roll's light and shadow — neutral white normally; a host sets it to a
 446     /// highlight color to mark the plate (the focused-pane treatment) without a
 447     /// separate border ring: the light goes to the tint, the shadow to a dark
 448     /// tint, so the relief still reads. Shader-plates path only; the legacy
 449     /// banded tessellation ignores it.
 450     Bevel { rect: Rect, radii: Radii, material: Material, depth: f32, tint: [f32; 3] },
 451     /// A [`Prim::Bevel`] turned inside out: the plate's face is everything in
 452     /// `rect` OUTSIDE `hole`, and its rolled edge runs round the hole's
 453     /// outline, falling INTO the hole. So a corner of the hole is an inside
 454     /// corner of the plate — a cove, rounded at the hole's radius in the DE's
 455     /// corner family — which a box can only round convex. A band of a
 456     /// window's edge with an opening above it (the designer's playbar shelf):
 457     /// its top edge and both coves are ONE outline, one profile evaluation,
 458     /// with no join to stack two shadings at. `rect` bounds the face (its
 459     /// own edges are not rolled — lay them past the window or under
 460     /// something), and it is a carve host like a Bevel: carves inside `rect`
 461     /// group into it. Shader-plates path; the legacy path fills `rect`.
 462     Frame { rect: Rect, hole: Rect, hole_radii: Radii, material: Material, depth: f32 },
 463     /// A recess carved into whatever is already painted underneath — the inverse of
 464     /// `Bevel`. Emits ONLY the shaded edges, never a fill, so the surface below shows
 465     /// through the middle: a relief cut into the root plate rather than a plate laid on
 466     /// top of it. The light vector is negated relative to `Bevel`, so the edges facing
 467     /// `light_source_position` fall into shadow and the far edges catch the light —
 468     /// which is what reads as "lower" instead of "raised".
 469     ///
 470     /// The shading is a translucent light/shadow overlay, so the carve needs no knowledge
 471     /// of what it carves: fills, gradients, and translucency below all show through
 472     /// modulated rather than repainted.
 473     /// `edges` is (top, right, bottom, left): which walls of the carve actually exist.
 474     /// A region flush with the plate's own edge is a step, not a trough — see
 475     /// `push_bevel_edge_vertices_banded`.
 476     /// `tint` colours the wall's light and shadow — the same focused-pane
 477     /// treatment as [`Prim::Bevel`]'s tint, for carved wells instead of raised
 478     /// plates. A tinted
 479     /// recess never groups into a host plate's CSG features (a feature carries no
 480     /// color), so it always renders as the free-carve overlay. Shader-plates path
 481     /// only; the legacy banded tessellation ignores it.
 482     Recess { rect: Rect, radii: Radii, depth: f32, edges: (bool, bool, bool, bool), tint: Option<[f32; 3]> },
 483     /// The inverse of [`Prim::Recess`]: a plateau RAISED out of the surface below.
 484     /// Like `Recess` it emits only the shaded edges, never a fill — the face is the
 485     /// untouched surface underneath — so a region outlined by raised rolled bumps
 486     /// keeps the root plate's own color and translucency. Same wall semantics as
 487     /// `Recess` (`edges` = top/right/bottom/left); the lighting is the raised sign,
 488     /// so the edges facing `light_source_position` catch the light. `tint` colors
 489     /// the lit rim like [`Prim::Recess`]'s — the focused-pane treatment for a
 490     /// rim-only pane (a fill-less surface can't carry [`Prim::Bevel`]'s tint).
 491     /// Like a tinted recess it never groups into a host plate's CSG features.
 492     Boss { rect: Rect, radii: Radii, depth: f32, edges: (bool, bool, bool, bool), tint: Option<[f32; 3]> },
 493     /// A raised RIM riding the rect's boundary: a bump profile straddling the
 494     /// outline (span ±depth/2), rising from the surrounding surface to a crest on
 495     /// the boundary and falling back to the same level inside — an elevated border
 496     /// around a channel, both faces at the underlying surface's own level. One
 497     /// primitive, ONE lighting evaluation per pixel: building the same shape from
 498     /// a Boss plus an inset Recess stacks two shading passes (double specular /
 499     /// shoulder terms at the crest) and reads far hotter than a plate edge.
 500     Ridge { rect: Rect, radii: Radii, depth: f32, edges: (bool, bool, bool, bool) },
 501     /// The sunken twin of [`Prim::Ridge`]: a VALLEY riding the rect's boundary —
 502     /// a bump profile straddling the outline (span ±depth/2), falling from the
 503     /// surrounding surface to a trough on the boundary and rising back to the
 504     /// same level inside, so both faces sit at the underlying surface's own
 505     /// level. This is the seam a flush inset control leaves ([`PaintCtx::inset_plate`]).
 506     ///
 507     /// Same reason to exist as `Ridge`, measured: building this from a `Recess`
 508     /// on an outset rect plus a `Boss` on the rect (what `inset_plate` used to
 509     /// emit) stacks two independent shading passes. At depth 4.8 that read as a
 510     /// band 15px wide instead of 8 with THREE lobes — bright, dark, brighter —
 511     /// because the recess ring's own lit rim lands ~depth outside the control
 512     /// instead of merging into one wall, and the highlight peaked 22% hotter
 513     /// than a single evaluation of the same depth. It looked like two concentric
 514     /// rings, which is what it was.
 515     ///
 516     /// `edges` and the host-box fade behave exactly as [`Prim::Recess`]'s.
 517     /// SDF path only; the legacy banded tessellation approximates it with the
 518     /// old two-step stack (like `Ridge`, which approximates itself there).
 519     ///
 520     /// `tint` lights the rim like [`Prim::Recess`]'s — the focus treatment of a
 521     /// flush control plate (`PaintCtx::control_plate`).
 522     Trough { rect: Rect, radii: Radii, depth: f32, edges: (bool, bool, bool, bool), tint: Option<[f32; 3]> },
 523     /// A sunken well holding a FLUSH run: one field, one outer contour.
 524     /// Between `split` and `end` (xs in the same space as `rect`) the
 525     /// interior is back at the surface's level, as a flush control plate's
 526     /// face, inside a valley on the outline; on either side of that it is a
 527     /// [`Prim::Recess`] — the interior one step down. A run that reaches an
 528     /// end of the field (`split` left of it, or `end` right of it — by
 529     /// [`FIELD_RUN_ONLY`]) has no well on that side. The forms in use: a
 530     /// parameter pane's text row with its completion picker and a spinbox's
 531     /// value with its -/+ run (the run at the right end), a flush control
 532     /// plate (all run), a toggle (a run half the field wide, at the left end
 533     /// off and the right end on, gliding between).
 534     ///
 535     /// Why one prim and not the two it replaces, side by side: each of those
 536     /// shades its OWN box, so at the seam the outline breaks — each box
 537     /// turns its own square corner there, and the strong line of the edge
 538     /// jumps from the recess's outer rim to the trough's inner lip. Here the
 539     /// outline is evaluated once (the whole field, its own radii), its wall
 540     /// blends from the step to the valley across a wall's width about the
 541     /// seam — the two agree on the outer half, falling from the surface to
 542     /// half the step, and part on the inner half — and the seam is the
 543     /// well's floor rising to the run's face: a step wall along `split`,
 544     /// fading to nothing where it meets the outer wall, the one place the
 545     /// two sides stand at the same height.
 546     ///
 547     /// SDF path only; the legacy banded tessellation and flat hosts draw the
 548     /// two-box form it replaced. `tint` lights it like a recess's.
 549     Field { rect: Rect, radii: Radii, depth: f32, split: f32, end: f32, tint: Option<[f32; 3]> },
 550     /// The window's glass slab: a rounded fill plus a rolled, lit edge around its whole
 551     /// perimeter, drawn at full size. Distinct from `Bevel`, which insets its fill by
 552     /// `depth` — a plate must fill the window exactly, or the compositor's rounded window
 553     /// corners would show a gap. `depth` is the width of the roll-off in px, not a color
 554     /// offset (the shading amplitude is the DE-wide `bevel_depth`).
 555     ///
 556     /// `shape` overrides the DE-wide corner exponent (`layout::corner_shape`)
 557     /// for this one plate — `Some(2.0)` is circular arcs, so a plate whose
 558     /// radii reach its half-extent is a true circle regardless of the
 559     /// squircle the rest of the DE wears. `None` follows the DE.
 560     Plate { rect: Rect, radii: Radii, material: Material, depth: f32, shape: Option<f32> },
 561     Arc { cx: f32, cy: f32, radius: f32, thickness: f32, start: f32, end: f32, color: [f32; 4] },
 562     /// A ring band with radial color interpolation — inner rim → crest
 563     /// (centerline) → outer rim — for rounded rim bevels (the Ramp's key
 564     /// rings). `radius` is the stroke's outer edge, like `Arc`.
 565     ArcShaded { cx: f32, cy: f32, radius: f32, thickness: f32, start: f32, end: f32, inner: [f32; 4], crest: [f32; 4], outer: [f32; 4] },
 566     Vector { x1: f32, y1: f32, x2: f32, y2: f32, thickness: f32, color: [f32; 4], cap: Cap },
 567     Circle { cx: f32, cy: f32, radius: f32, color: [f32; 4] },
 568     /// A feathered aura around (and over) a rounded rect: the interior fills
 569     /// at the color's full alpha, and outside the boundary the alpha falls
 570     /// off smoothly to zero across `reach` px. Tessellated as concentric
 571     /// per-vertex-alpha rings the GPU interpolates, so the gradient is
 572     /// per-pixel smooth — no stacked-layer banding. Highlights and soft
 573     /// focus auras (the designer's drop-target glow) are the intended use;
 574     /// no relief shading, no light involvement.
 575     Glow { rect: Rect, radius: f32, reach: f32, color: [f32; 4] },
 576     /// A `Circle` lit as a ball: the disc is shaded per pixel as a hemisphere
 577     /// under the DE's plate light (same ambient/diffuse/specular model), so it
 578     /// reads as a sphere sitting on the surface — the slider thumb's look. The
 579     /// color is the sphere's face color exactly at the lit center, like a
 580     /// plate's face keeps the app's color. Falls back to a flat circle on the
 581     /// legacy (`bevel_shader 0`) path.
 582     Sphere { cx: f32, cy: f32, radius: f32, material: Material },
 583     /// A hanging water droplet clinging to the TOP edge of `rect`, lit per pixel
 584     /// by shader mode 10: the silhouette is a smooth union of a film "sheet"
 585     /// attached to the top edge (square top corners — the attach line) and a
 586     /// belly capsule resting on the rect's bottom, blended metaball-style so a
 587     /// waist forms where the sides pull up. Shaded as a glass dome under the
 588     /// DE's plate light — same ambient/diffuse and decoupled specular as the
 589     /// plates, plus a fresnel rim crest and a thin-edge clarity falloff (tint
 590     /// opacity drops toward the silhouette, so the frosted backdrop shows
 591     /// through clearer at the rim, which is what reads as water rather than
 592     /// plastic). Shape knobs in [`DropletSpec`]. On the legacy (`bevel_shader
 593     /// 0`) path it degrades to the flat hanging capsule — square top, round
 594     /// bottom — rather than vanishing.
 595     Droplet { rect: Rect, material: Material, spec: DropletSpec },
 596     /// The same silhouette as [`Prim::Droplet`] under the same [`DropletSpec`],
 597     /// filled FLAT and feathered inward: opaque through the interior, fading
 598     /// to nothing over `feather` px as it approaches the drop's edge. A
 599     /// vignette shaped exactly like the drop, for grounding text drawn on top
 600     /// of one — not a second lit body, so it carries no dome, rim, gleam or
 601     /// contact shadow.
 602     ///
 603     /// It shares the droplet's shader path rather than approximating the
 604     /// outline with a rounded rect, so the two can never disagree about where
 605     /// the drop's edge is. On the legacy (`bevel_shader 0`) path it degrades
 606     /// to the same flat rounded-rect outline `Prim::Droplet` falls back to.
 607     DropletScrim { rect: Rect, material: Material, spec: DropletSpec, feather: f32 },
 608     /// A concave inside-corner fillet for composed carves: a quarter-arc wall
 609     /// whose centre `(cx, cy)` sits out in the corner's pocket, shaded with the
 610     /// same step profile as a `Recess`/`Boss` wall (`raised` flips the sign).
 611     /// `start` is the wedge's start angle (quarter span, hard-cut at the
 612     /// tangent lines — the neighboring straight walls continue the profile
 613     /// exactly there). Box radii can only round convex corners; this is the
 614     /// missing concave piece. SDF path only (no legacy fallback).
 615     ConcaveFillet { cx: f32, cy: f32, radius: f32, depth: f32, start: f32, raised: bool },
 616     /// An engraved line: a groove of half-width `width / 2` running along the
 617     /// segment `a`–`b`, cut into whatever is painted beneath. Like [`Prim::Recess`]
 618     /// it emits only shading, never a fill — but its shape is a SLAB (a band about
 619     /// an arbitrary line) rather than a box, which is what lets it run at an angle.
 620     /// A box SDF can only carve axis-aligned walls; this is the diagonal case.
 621     ///
 622     /// Both walls come from ONE profile evaluation on `|distance to the line|`, so
 623     /// the groove carries a single specular/shoulder term — the same reason
 624     /// [`Prim::Ridge`] exists instead of stacking a boss on a recess.
 625     /// `width` 0 makes the two walls meet in a V.
 626     ///
 627     /// `depth` is the transition width in px (the wall's run), matching
 628     /// [`Prim::Recess`]. `host` is the surface the groove is engraved into: the
 629     /// shading fades out across that box's perimeter roll, so a seam cut across a
 630     /// plate dies into the plate's own rolled edge instead of ending on a hard line.
 631     /// SDF path only — the legacy banded tessellation draws nothing (like `Ridge`).
 632     ///
 633     /// `strength` scales the groove's shading, specular and AO — 1.0 is the
 634     /// DE's finish, 0.0 no groove at all — which is how a carve with no colour
 635     /// of its own fades ([`Prim::faded`]).
 636     Groove { a: (f32, f32), b: (f32, f32), width: f32, depth: f32, host: Rect, strength: f32 },
 637     /// A periodic field of identical rounded-box wells — every cell of a grid
 638     /// carved into whatever is painted beneath, as ONE surface. The wells
 639     /// repeat every `period` (x, y) with one cell centred at `origin`, each
 640     /// `cell` (w, h) big with `radius` corners; the wall runs from the cell
 641     /// edge OUTWARD over `depth` px (floor at the edge, plateau one run out),
 642     /// so a rail between two cells carries one wall from each side and the
 643     /// rail face is whatever the runs leave. Shading lands only inside `rect`.
 644     /// The wall's outer edge is MITRED, not offset: it is the cell grown by
 645     /// the run at the same `radius`, so a crossing keeps the cell's corner
 646     /// rounding instead of sweeping at `radius + depth`, and the four walls
 647     /// meet on the diagonals.
 648     ///
 649     /// This exists because a lattice drawn as one [`Prim::Recess`] per cell is
 650     /// N independent overlays: where four rounded rings meet at a crossing
 651     /// their shadings stack in colour space and read as overlapping effects,
 652     /// not a junction. Here the pixel is folded into the period and the
 653     /// distance is to the NEAREST cell — the union of every well — evaluated
 654     /// once, so the rail centre lines and the diagonals at each crossing are
 655     /// true mitres, and the cost is one draw regardless of how many cells the
 656     /// surface holds (a free carve per cell also runs into the per-frame
 657     /// feature budget long before a zoomed-out grid does). SDF path only.
 658     Lattice { rect: Rect, period: (f32, f32), origin: (f32, f32), cell: (f32, f32), radius: f32, depth: f32 },
 659     /// `color`, flat, everywhere inside `rect` that is OUTSIDE a periodic
 660     /// field of rounded cells — the same field [`Prim::Lattice`] carves
 661     /// (`period`, one cell centred at `origin`, each `cell` big with `radius`
 662     /// corners), painted as grout rather than shaded. One draw for the whole
 663     /// grid, with the cells' superellipse corners exact: what a graph's grid
 664     /// lines are when the cells are the surface beneath showing through.
 665     /// SDF path only — the legacy banded tessellation draws nothing.
 666     Grout { rect: Rect, period: (f32, f32), origin: (f32, f32), cell: (f32, f32), radius: f32, color: [f32; 4] },
 667     /// A flat fill of a MATERIAL: `rect` at `radii`, no roll, no rim — the
 668     /// material's tint, frosted at the material's own recipe when it is
 669     /// frosted. What a frosted `RoundedRect` promotes to, except that the
 670     /// recipe is the material's rather than the DE default's, so a fill can
 671     /// compress harder (or softer) than the pane it sits on. Opens no carve
 672     /// host: carves emitted after it overlay it, as they overlay any flat
 673     /// geometry. An opaque material draws as a plain rounded fill.
 674     Fill { rect: Rect, radii: Radii, material: Material },
 675     /// Several rounded boxes carved (`raised` false) or raised (`raised`
 676     /// true) as ONE shape: the union of the boxes is the well, and its wall
 677     /// follows the union's outline — straddling it by ±`depth`/2 like every
 678     /// carve boundary — through a single profile evaluation per pixel. An L,
 679     /// a T, a plus, a slot with a round end: any outline boxes can compose.
 680     ///
 681     /// The alternative, one [`Prim::Recess`] per box, is N overlays that
 682     /// each shade their own full outline: where two boxes overlap, each
 683     /// draws a wall straight through the other's interior, and where their
 684     /// walls cross the shadings stack in colour space — the junction reads
 685     /// as two effects laid over each other, not one shape. Here the pixel's
 686     /// distance is to the NEAREST box (the union SDF), so a box's wall
 687     /// vanishes wherever it runs inside another, and an inside corner is a
 688     /// sharp mitre (round it with [`Prim::ConcaveFillet`] if it must be
 689     /// concave-rounded — the union has no radius there by construction).
 690     /// Outer corners are mitred like [`Prim::Lattice`]'s: the wall band runs
 691     /// between each box shrunk and grown by half the run at the box's own
 692     /// radius, so a corner keeps its radius instead of sweeping wider.
 693     ///
 694     /// The boxes ride the frame's plate-feature buffer (the same slots CSG
 695     /// carves use, 64 per frame), so a union costs one draw plus one slot
 696     /// per box. When the budget cannot hold all of a union's boxes the
 697     /// tessellator keeps as many as fit — a degraded shape rather than none —
 698     /// and says so under `CCE_PLATE_DEBUG`. SDF path only.
 699     CarveUnion { boxes: Vec<(Rect, Radii)>, depth: f32, raised: bool },
 700     /// Text in sRGB u8 (the `TextLabel` convention). `font` is a font string for
 701     /// `get_text_buffer` (family, or "family:size"); `bounds` is a logical `[l, t, r, b]` clip
 702     /// for the glyph pass (Phase 6: the backend renders these through the glyph pass when the app
 703     /// opts in via `Application::display_list_text`; the paint walk's clip additionally
 704     /// applies through the item's `clip`). `attrs` carries the optional shaping attributes
 705     /// beyond family+size (the font picker's italic/weight preview variants). `layout`, when
 706     /// `Some`, requests box layout — word-wrap at a width and horizontal/vertical alignment
 707     /// within a box (the placed-text-box case, e.g. cce-layout-interface's canvas elements);
 708     /// `None` is the ordinary single-run label. `alpha` fades the glyphs (1.0 = opaque) —
 709     /// the color stays sRGB u8, so translucent text doesn't need a color-type change.
 710     Text { text: String, x: f32, y: f32, font_size: f32, color: [u8; 3], alpha: f32, font: Option<String>, bounds: Option<[f32; 4]>, attrs: TextAttrs, layout: Option<TextLayout> },
 711     /// A user image (id from `cce_ui::vk::upload_rgba`) drawn as a quad, in
 712     /// display-list order like any other primitive. The paint walk's clip
 713     /// applies through the item's `clip` as usual.
 714     Image { image: u32, rect: Rect, alpha: f32 },
 715 }
 716 
 717 impl Prim {
 718     /// This prim at `alpha` of its strength (0..1): a colour's alpha scaled,
 719     /// a text's or an image's alpha, a groove's shading. What a host fading a
 720     /// part of its drawing out or in replays it through (the context menu's
 721     /// page turn). The relief prims with no colour or strength of their own —
 722     /// the walls, plates and materials — come back as they are: they are
 723     /// drawn whole or not at all.
 724     pub fn faded(self, alpha: f32) -> Prim {
 725         let a = alpha.clamp(0.0, 1.0);
 726         let f = |c: [f32; 4]| [c[0], c[1], c[2], c[3] * a];
 727         match self {
 728             Prim::Quad { rect, color } => Prim::Quad { rect, color: f(color) },
 729             Prim::RoundedRect { rect, radius, corners, color } => Prim::RoundedRect { rect, radius, corners, color: f(color) },
 730             Prim::Border { rect, radii, fill, border, thickness } => Prim::Border { rect, radii, fill: f(fill), border: f(border), thickness },
 731             Prim::Arc { cx, cy, radius, thickness, start, end, color } => Prim::Arc { cx, cy, radius, thickness, start, end, color: f(color) },
 732             Prim::ArcShaded { cx, cy, radius, thickness, start, end, inner, crest, outer } => {
 733                 Prim::ArcShaded { cx, cy, radius, thickness, start, end, inner: f(inner), crest: f(crest), outer: f(outer) }
 734             }
 735             Prim::Vector { x1, y1, x2, y2, thickness, color, cap } => Prim::Vector { x1, y1, x2, y2, thickness, color: f(color), cap },
 736             Prim::Circle { cx, cy, radius, color } => Prim::Circle { cx, cy, radius, color: f(color) },
 737             Prim::Glow { rect, radius, reach, color } => Prim::Glow { rect, radius, reach, color: f(color) },
 738             Prim::Grout { rect, period, origin, cell, radius, color } => Prim::Grout { rect, period, origin, cell, radius, color: f(color) },
 739             Prim::Groove { a: p, b, width, depth, host, strength } => Prim::Groove { a: p, b, width, depth, host, strength: strength * a },
 740             Prim::Text { text, x, y, font_size, color, alpha, font, bounds, attrs, layout } => {
 741                 Prim::Text { text, x, y, font_size, color, alpha: alpha * a, font, bounds, attrs, layout }
 742             }
 743             Prim::Image { image, rect, alpha } => Prim::Image { image, rect, alpha: alpha * a },
 744             other => other,
 745         }
 746     }
 747 }
 748 
 749 /// Horizontal alignment of laid-out (boxed) text — the toolkit-plain mirror of
 750 /// `cosmic_text::Align`, mapped at shape time.
 751 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
 752 pub enum AlignH {
 753     #[default]
 754     Left,
 755     Center,
 756     Right,
 757 }
 758 
 759 /// Vertical alignment of laid-out text within its box.
 760 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
 761 pub enum AlignV {
 762     #[default]
 763     Top,
 764     Middle,
 765     Bottom,
 766 }
 767 
 768 /// Box layout for a [`Prim::Text`]: word-wrap width (`Some` ⇒ multiline wrap; `None` ⇒ single
 769 /// run) and horizontal/vertical alignment within a box of `box_height`. All lengths are logical.
 770 /// The backend shapes it with `get_text_buffer_laid_out`, cached under the box as well as the
 771 /// text, and applies the vertical offset from the shaped height.
 772 #[derive(Clone, Copy, Debug, PartialEq)]
 773 pub struct TextLayout {
 774     pub wrap_width: Option<f32>,
 775     pub box_height: f32,
 776     pub align_h: AlignH,
 777     pub align_v: AlignV,
 778 }
 779 
 780 /// Optional shaping attributes for a [`Prim::Text`] — the subset a widget can request beyond
 781 /// family + size. `weight` is the OpenType weight (400 regular, 700 bold); `None` leaves the
 782 /// family default. `stretch` is the OpenType width class (`usWidthClass`, 1 ultra-condensed
 783 /// … 5 normal … 9 ultra-expanded); `None` is normal width — what picks a family's Narrow or
 784 /// Condensed cut over its normal-width sibling at the same weight. Kept toolkit-plain (no
 785 /// cosmic-text types) like the rest of the scene layer; the backend maps them onto
 786 /// `cosmic_text::Style`/`Weight`/`Stretch` at shape time.
 787 ///
 788 /// Build one with `..Default::default()` after the fields you set, so a field added here
 789 /// does not break every literal in the sibling crates.
 790 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
 791 pub struct TextAttrs {
 792     pub italic: bool,
 793     pub weight: Option<u16>,
 794     pub stretch: Option<u16>,
 795 }
 796 
 797 /// A primitive plus the scissor rect it must be clipped to (`None` = unclipped), an
 798 /// optional circular clip `[cx, cy, r]` in logical pixels (`None` = unclipped) — the
 799 /// per-vertex circle clip the tessellators already support, for round panes (the designer's
 800 /// circular network pane) — and an optional rounded-rect clip `[cx, cy, bx, by, r]`
 801 /// (center, SDF half-extents = half-size minus radius, corner radius; logical px) so a
 802 /// plate's children cut off at its rounded corners. The clips compose: the scissor is GPU
 803 /// state, the circle rides the vertices, the rounded rect is per-draw-batch state.
 804 #[derive(Clone, Debug, PartialEq)]
 805 pub struct PaintItem {
 806     pub prim: Prim,
 807     pub clip: Option<Rect>,
 808     pub clip_circle: Option<[f32; 3]>,
 809     pub clip_rrect: Option<[f32; 5]>,
 810 }
 811 
 812 /// An ordered list of clipped primitives — the single source of truth for a frame's geometry.
 813 #[derive(Clone, Debug, Default, PartialEq)]
 814 pub struct DisplayList {
 815     pub items: Vec<PaintItem>,
 816 }
 817 
 818 impl DisplayList {
 819     pub fn new() -> Self {
 820         DisplayList { items: Vec::new() }
 821     }
 822     pub fn len(&self) -> usize {
 823         self.items.len()
 824     }
 825     pub fn is_empty(&self) -> bool {
 826         self.items.is_empty()
 827     }
 828 }
 829 
 830 /// Intersection of two rects, clamped so width/height never go negative (an empty clip is a
 831 /// zero-size rect — nothing draws under it).
 832 fn intersect(a: Rect, b: Rect) -> Rect {
 833     let x0 = a.x.max(b.x);
 834     let y0 = a.y.max(b.y);
 835     let x1 = (a.x + a.width).min(b.x + b.width);
 836     let y1 = (a.y + a.height).min(b.y + b.height);
 837     Rect { x: x0, y: y0, width: (x1 - x0).max(0.0), height: (y1 - y0).max(0.0) }
 838 }
 839 
 840 /// Accumulates a [`DisplayList`] while a paint walk pushes/pops clips and translations.
 841 ///
 842 /// Coordinates passed to the emit methods (and to [`push_clip`](PaintCtx::push_clip)) are in the
 843 /// **current** local space; the active translation is applied so everything recorded is absolute.
 844 pub struct PaintCtx {
 845     list: DisplayList,
 846     /// Each entry is the effective (already-intersected, absolute) clip at that depth.
 847     clip_stack: Vec<Rect>,
 848     /// Active circular clips; primitives record the innermost (`last`). Circles don't
 849     /// intersect analytically like rects, so nesting keeps the innermost only.
 850     clip_circle_stack: Vec<[f32; 3]>,
 851     /// Active rounded-rect clips `[cx, cy, bx, by, r]`; innermost wins, like circles.
 852     clip_rrect_stack: Vec<[f32; 5]>,
 853     /// Saved offsets for nesting; `offset` is the current cumulative translation.
 854     offset_stack: Vec<(f32, f32)>,
 855     offset: (f32, f32),
 856 }
 857 
 858 impl Default for PaintCtx {
 859     fn default() -> Self {
 860         Self::new()
 861     }
 862 }
 863 
 864 impl PaintCtx {
 865     pub fn new() -> Self {
 866         PaintCtx {
 867             list: DisplayList::new(),
 868             clip_stack: Vec::new(),
 869             clip_circle_stack: Vec::new(),
 870             clip_rrect_stack: Vec::new(),
 871             offset_stack: Vec::new(),
 872             offset: (0.0, 0.0),
 873         }
 874     }
 875 
 876     /// The scissor rect primitives are currently recorded under.
 877     pub fn current_clip(&self) -> Option<Rect> {
 878         self.clip_stack.last().copied()
 879     }
 880 
 881     /// Push a clip (in current local space); it is translated to absolute and intersected with the
 882     /// enclosing clip. Pair with [`pop_clip`](PaintCtx::pop_clip), or prefer [`clip`](PaintCtx::clip).
 883     pub fn push_clip(&mut self, rect: Rect) {
 884         let r = self.apply_offset(rect);
 885         let effective = match self.clip_stack.last() {
 886             Some(cur) => intersect(*cur, r),
 887             None => r,
 888         };
 889         self.clip_stack.push(effective);
 890     }
 891 
 892     pub fn pop_clip(&mut self) {
 893         self.clip_stack.pop();
 894     }
 895 
 896     /// Run `f` with `rect` pushed as a clip, popping it afterward.
 897     pub fn clip<R>(&mut self, rect: Rect, f: impl FnOnce(&mut Self) -> R) -> R {
 898         self.push_clip(rect);
 899         let out = f(self);
 900         self.pop_clip();
 901         out
 902     }
 903 
 904     /// Push a circular clip `[cx, cy, r]` (current local space, translated to absolute).
 905     /// Primitives emitted while it is active record it and tessellate with the per-vertex
 906     /// circle clip. Pair with [`pop_clip_circle`](PaintCtx::pop_clip_circle), or prefer
 907     /// [`clip_circle`](PaintCtx::clip_circle).
 908     pub fn push_clip_circle(&mut self, c: [f32; 3]) {
 909         self.clip_circle_stack.push([c[0] + self.offset.0, c[1] + self.offset.1, c[2]]);
 910     }
 911 
 912     pub fn pop_clip_circle(&mut self) {
 913         self.clip_circle_stack.pop();
 914     }
 915 
 916     /// Run `f` with `[cx, cy, r]` pushed as a circular clip, popping it afterward.
 917     pub fn clip_circle<R>(&mut self, c: [f32; 3], f: impl FnOnce(&mut Self) -> R) -> R {
 918         self.push_clip_circle(c);
 919         let out = f(self);
 920         self.pop_clip_circle();
 921         out
 922     }
 923 
 924     /// Push a rounded-rect clip: `rect` (current local space) with corner radius `radius`,
 925     /// so children of a rounded plate cut off at its corners. Pushes the rect as a scissor
 926     /// too — the scissor handles the straight edges (and keeps batching), the SDF trims the
 927     /// corners. A radius of zero degenerates to the plain rect clip. Pair with
 928     /// [`pop_clip_rounded`](PaintCtx::pop_clip_rounded), or prefer
 929     /// [`clip_rounded`](PaintCtx::clip_rounded).
 930     pub fn push_clip_rounded(&mut self, rect: Rect, radius: f32) {
 931         self.push_clip(rect);
 932         let r = radius.max(0.0);
 933         if r > 0.0 {
 934             let abs = self.apply_offset(rect);
 935             self.clip_rrect_stack.push([
 936                 abs.x + abs.width / 2.0,
 937                 abs.y + abs.height / 2.0,
 938                 (abs.width / 2.0 - r).max(0.0),
 939                 (abs.height / 2.0 - r).max(0.0),
 940                 r,
 941             ]);
 942         } else {
 943             // Keep push/pop balanced regardless of radius.
 944             self.clip_rrect_stack.push([0.0; 5]);
 945         }
 946     }
 947 
 948     pub fn pop_clip_rounded(&mut self) {
 949         self.clip_rrect_stack.pop();
 950         self.pop_clip();
 951     }
 952 
 953     /// Run `f` with `rect` (radius `radius`) pushed as a rounded clip, popping it afterward.
 954     pub fn clip_rounded<R>(&mut self, rect: Rect, radius: f32, f: impl FnOnce(&mut Self) -> R) -> R {
 955         self.push_clip_rounded(rect, radius);
 956         let out = f(self);
 957         self.pop_clip_rounded();
 958         out
 959     }
 960 
 961     /// Run `f` with an additional translation applied to all emitted coordinates.
 962     /// Imperative translate pair for spans too large to wrap in
 963     /// [`translate`](Self::translate)'s closure (an app bracketing its whole
 964     /// frame in the overflow-margin shift). Must balance before `finish`.
 965     pub fn push_translate(&mut self, dx: f32, dy: f32) {
 966         self.offset_stack.push(self.offset);
 967         self.offset.0 += dx;
 968         self.offset.1 += dy;
 969     }
 970 
 971     /// See [`push_translate`](Self::push_translate).
 972     pub fn pop_translate(&mut self) {
 973         self.offset = self.offset_stack.pop().expect("translate stack underflow");
 974     }
 975 
 976     pub fn translate<R>(&mut self, dx: f32, dy: f32, f: impl FnOnce(&mut Self) -> R) -> R {
 977         self.offset_stack.push(self.offset);
 978         self.offset = (self.offset.0 + dx, self.offset.1 + dy);
 979         let out = f(self);
 980         self.offset = self.offset_stack.pop().expect("translate stack underflow");
 981         out
 982     }
 983 
 984     /// The translation applied to what is emitted now: a widget's own rect
 985     /// plus this is where it lands in the window.
 986     pub fn offset(&self) -> (f32, f32) {
 987         self.offset
 988     }
 989 
 990     fn apply_offset(&self, r: Rect) -> Rect {
 991         Rect { x: r.x + self.offset.0, y: r.y + self.offset.1, width: r.width, height: r.height }
 992     }
 993 
 994     fn push(&mut self, prim: Prim) {
 995         let clip = self.current_clip();
 996         let clip_circle = self.clip_circle_stack.last().copied();
 997         // r == 0 entries are balance placeholders (a zero-radius rounded clip is just its
 998         // scissor rect) — record no rounded clip so batches keep merging.
 999         let clip_rrect = self.clip_rrect_stack.last().copied().filter(|c| c[4] > 0.0);
1000         self.list.items.push(PaintItem { prim, clip, clip_circle, clip_rrect });
1001     }
1002 
1003     /// Append items another context painted — a nested paint walk spliced into this list,
1004     /// as a host does that keeps a walk's text for a pass of its own. Each item keeps its own
1005     /// clips and is clipped by this context's current scissor too; its own circular or
1006     /// rounded clip wins over the current one (the innermost, as when pushing). Items are
1007     /// absolute already, so this context's offset does not apply.
1008     pub fn append_items(&mut self, items: impl IntoIterator<Item = PaintItem>) {
1009         let cur = self.current_clip();
1010         let cur_circle = self.clip_circle_stack.last().copied();
1011         let cur_rrect = self.clip_rrect_stack.last().copied().filter(|c| c[4] > 0.0);
1012         for mut item in items {
1013             item.clip = match (cur, item.clip) {
1014                 (Some(a), Some(b)) => Some(intersect(a, b)),
1015                 (a, b) => a.or(b),
1016             };
1017             item.clip_circle = item.clip_circle.or(cur_circle);
1018             item.clip_rrect = item.clip_rrect.or(cur_rrect);
1019             self.list.items.push(item);
1020         }
1021     }
1022 
1023     pub fn quad(&mut self, rect: Rect, color: [f32; 4]) {
1024         let rect = self.apply_offset(rect);
1025         self.push(Prim::Quad { rect, color });
1026     }
1027 
1028     /// A user image (id from `cce_ui::vk::upload_rgba`) drawn at `rect`.
1029     pub fn image(&mut self, image: u32, rect: Rect, alpha: f32) {
1030         let rect = self.apply_offset(rect);
1031         self.push(Prim::Image { image, rect, alpha });
1032     }
1033 
1034     /// A bundled cce-icons glyph (`cce-icons/svg/<name>.svg`) drawn at
1035     /// `rect`, tinted `color` — given as a text colour is, raw sRGB, so a
1036     /// glyph and the label beside it match — with the colour's alpha as the
1037     /// image's. Rasterized at twice the rect's longer side so it stays crisp
1038     /// on a 2x output, and cached (see [`crate::upload_icon_tinted`]).
1039     /// `false`, and nothing drawn, when the glyph is missing.
1040     ///
1041     /// The ONE way the toolkit draws a symbol: a chevron, a mark, a + or a
1042     /// − is this, never a character in whatever face the font falls back to.
1043     /// A `weather-*` glyph carries its own colours; draw it with
1044     /// [`icon_untinted`](Self::icon_untinted).
1045     pub fn icon(&mut self, name: &str, rect: Rect, color: [f32; 4]) -> bool {
1046         let px = (rect.width.max(rect.height) * 2.0).ceil().max(1.0) as u32;
1047         match crate::upload_icon_tinted(name, px, crate::icon_tint(color)) {
1048             Some((id, _, _)) => {
1049                 self.image(id, rect, color[3]);
1050                 true
1051             }
1052             None => false,
1053         }
1054     }
1055 
1056     /// [`icon`](Self::icon) for a glyph that carries its own colours (the
1057     /// `weather-*` family): drawn as it is, at `alpha`.
1058     pub fn icon_untinted(&mut self, name: &str, rect: Rect, alpha: f32) -> bool {
1059         let px = (rect.width.max(rect.height) * 2.0).ceil().max(1.0) as u32;
1060         match crate::upload_icon(name, px) {
1061             Some((id, _, _)) => {
1062                 self.image(id, rect, alpha);
1063                 true
1064             }
1065             None => false,
1066         }
1067     }
1068 
1069     pub fn rounded_rect(&mut self, rect: Rect, radius: f32, corners: (bool, bool, bool, bool), color: [f32; 4]) {
1070         let rect = self.apply_offset(rect);
1071         self.push(Prim::RoundedRect { rect, radius, corners, color });
1072     }
1073 
1074     /// Feathered aura over a rounded rect (see [`Prim::Glow`]): interior at
1075     /// the color's alpha, smooth per-pixel falloff to zero across `reach` px
1076     /// outside the boundary.
1077     pub fn glow(&mut self, rect: Rect, radius: f32, reach: f32, color: [f32; 4]) {
1078         let rect = self.apply_offset(rect);
1079         self.push(Prim::Glow { rect, radius, reach, color });
1080     }
1081 
1082     pub fn vector(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, thickness: f32, color: [f32; 4], cap: Cap) {
1083         let (ox, oy) = self.offset;
1084         self.push(Prim::Vector { x1: x1 + ox, y1: y1 + oy, x2: x2 + ox, y2: y2 + oy, thickness, color, cap });
1085     }
1086 
1087     pub fn circle(&mut self, cx: f32, cy: f32, radius: f32, color: [f32; 4]) {
1088         let (ox, oy) = self.offset;
1089         self.push(Prim::Circle { cx: cx + ox, cy: cy + oy, radius, color });
1090     }
1091 
1092     /// A sphere-lit circle — see `Prim::Sphere`.
1093     pub fn sphere(&mut self, cx: f32, cy: f32, radius: f32, material: &Material) {
1094         let (ox, oy) = self.offset;
1095         self.push(Prim::Sphere { cx: cx + ox, cy: cy + oy, radius, material: *material });
1096     }
1097 
1098     /// A hanging water droplet clinging to `rect`'s top edge — see
1099     /// [`Prim::Droplet`] and [`DropletSpec`].
1100     /// See [`Prim::DropletScrim`]. `feather` is how far in from the drop's
1101     /// edge the fill reaches full opacity, in logical px.
1102     pub fn droplet_scrim(&mut self, rect: Rect, material: &Material, spec: DropletSpec, feather: f32) {
1103         let rect = self.apply_offset(rect);
1104         self.push(Prim::DropletScrim { rect, material: *material, spec, feather });
1105     }
1106 
1107     /// The drop's finish is the material's (see [`DropletSpec::finish`]).
1108     pub fn droplet(&mut self, rect: Rect, material: &Material, spec: DropletSpec) {
1109         let rect = self.apply_offset(rect);
1110         self.push(Prim::Droplet { rect, material: *material, spec });
1111     }
1112 
1113     /// A concave inside-corner fillet — see `Prim::ConcaveFillet`. `start` is
1114     /// the quarter wedge's start angle; the arc's centre sits in the corner's
1115     /// pocket and the wall descends (or rises, `raised`) away from it.
1116     pub fn concave_fillet(&mut self, cx: f32, cy: f32, radius: f32, depth: f32, start: f32, raised: bool) {
1117         let (ox, oy) = self.offset;
1118         self.push(Prim::ConcaveFillet { cx: cx + ox, cy: cy + oy, radius, depth, start, raised });
1119     }
1120 
1121     /// Re-emit an already-built [`Prim`] through this context, so it re-records the
1122     /// current clip and translate state. This is the **single** place that has to
1123     /// learn a new `Prim` variant: a nested paint walk builds a scratch list and
1124     /// replays it into the real one, and that forwarding match used to exist
1125     /// verbatim in two crates ([`crate::widget::model`] and cce-cloud's
1126     /// `json_layout`) — adding `Prim::Groove` compiled against one and broke the
1127     /// other, caught only by a full workspace build.
1128     ///
1129     /// [`Prim::Text`] is NOT emitted: it is returned untouched, because the two
1130     /// callers disagree about it (a subtree painter authors its own text and wants
1131     /// it forwarded; everyone else drops it in favour of the widget's own label
1132     /// bridge). Every other variant is emitted and `None` comes back.
1133     #[must_use = "a returned Text prim was not emitted — drop or forward it explicitly"]
1134     pub fn replay(&mut self, prim: Prim) -> Option<Prim> {
1135         match prim {
1136             Prim::Text { .. } => return Some(prim),
1137             Prim::Quad { rect, color } => self.quad(rect, color),
1138             Prim::RoundedRect { rect, radius, corners, color } => {
1139                 self.rounded_rect(rect, radius, corners, color)
1140             }
1141             Prim::Border { rect, radii, fill, border, thickness } => {
1142                 self.border(rect, radii, fill, border, thickness)
1143             }
1144             Prim::Bevel { rect, radii, material, depth, tint } => {
1145                 self.bevel_tinted(rect, radii, &material, depth, tint)
1146             }
1147             Prim::Frame { rect, hole, hole_radii, material, depth } => {
1148                 self.frame(rect, hole, hole_radii, &material, depth)
1149             }
1150             Prim::Recess { rect, radii, depth, edges, tint } => match tint {
1151                 Some(t) => self.recess_tinted(rect, radii, depth, t),
1152                 None => self.recess_edges(rect, radii, depth, edges),
1153             },
1154             Prim::Boss { rect, radii, depth, edges, tint } => match tint {
1155                 Some(t) => self.boss_edges_tinted(rect, radii, depth, edges, t),
1156                 None => self.boss_edges(rect, radii, depth, edges),
1157             },
1158             Prim::Ridge { rect, radii, depth, edges } => self.ridge_edges(rect, radii, depth, edges),
1159             Prim::Trough { rect, radii, depth, edges, tint } => match tint {
1160                 Some(t) => self.trough_tinted(rect, radii, depth, t),
1161                 None => self.trough_edges(rect, radii, depth, edges),
1162             },
1163             Prim::Field { rect, radii, depth, split, end, tint } => {
1164                 self.field(&Field::spanning(rect, radii, depth, split, end).with_tint(tint))
1165             }
1166             Prim::Plate { rect, radii, material, depth, shape } => {
1167                 self.plate_shaped(rect, radii, &material, depth, shape)
1168             }
1169             Prim::Arc { cx, cy, radius, thickness, start, end, color } => {
1170                 self.arc(cx, cy, radius, thickness, start, end, color)
1171             }
1172             Prim::ArcShaded { cx, cy, radius, thickness, start, end, inner, crest, outer } => {
1173                 self.arc_shaded(cx, cy, radius, thickness, start, end, inner, crest, outer)
1174             }
1175             Prim::Vector { x1, y1, x2, y2, thickness, color, cap } => {
1176                 self.vector(x1, y1, x2, y2, thickness, color, cap)
1177             }
1178             Prim::Circle { cx, cy, radius, color } => self.circle(cx, cy, radius, color),
1179             Prim::Sphere { cx, cy, radius, material } => self.sphere(cx, cy, radius, &material),
1180             Prim::Glow { rect, radius, reach, color } => self.glow(rect, radius, reach, color),
1181             Prim::Droplet { rect, material, spec } => self.droplet(rect, &material, spec),
1182             Prim::DropletScrim { rect, material, spec, feather } => {
1183                 self.droplet_scrim(rect, &material, spec, feather)
1184             }
1185             Prim::ConcaveFillet { cx, cy, radius, depth, start, raised } => {
1186                 self.concave_fillet(cx, cy, radius, depth, start, raised)
1187             }
1188             Prim::Groove { a, b, width, depth, host, strength } => self.groove_strength(a, b, width, depth, host, strength),
1189             Prim::Lattice { rect, period, origin, cell, radius, depth } => {
1190                 self.lattice(rect, period, origin, cell, radius, depth)
1191             }
1192             Prim::Grout { rect, period, origin, cell, radius, color } => {
1193                 self.grout(rect, period, origin, cell, radius, color)
1194             }
1195             Prim::Fill { rect, radii, material } => self.fill_material(rect, radii, &material),
1196             Prim::CarveUnion { boxes, depth, raised } => self.carve_union(boxes, depth, raised),
1197             Prim::Image { image, rect, alpha } => self.image(image, rect, alpha),
1198         }
1199         None
1200     }
1201 
1202     /// An engraved line from `a` to `b` cut into `host` — see [`Prim::Groove`].
1203     pub fn groove(&mut self, a: (f32, f32), b: (f32, f32), width: f32, depth: f32, host: Rect) {
1204         self.groove_strength(a, b, width, depth, host, 1.0);
1205     }
1206 
1207     /// [`Self::groove`] at a fraction of its strength — see [`Prim::Groove`].
1208     pub fn groove_strength(&mut self, a: (f32, f32), b: (f32, f32), width: f32, depth: f32, host: Rect, strength: f32) {
1209         let (ox, oy) = self.offset;
1210         let host = self.apply_offset(host);
1211         self.push(Prim::Groove {
1212             a: (a.0 + ox, a.1 + oy),
1213             b: (b.0 + ox, b.1 + oy),
1214             width,
1215             depth,
1216             host,
1217             strength,
1218         });
1219     }
1220 
1221     /// A periodic field of rounded wells carved as one surface — see
1222     /// [`Prim::Lattice`]. `origin` is any one cell's centre; `rect` bounds the
1223     /// shading. All logical px, like every other carve.
1224     pub fn lattice(
1225         &mut self,
1226         rect: Rect,
1227         period: (f32, f32),
1228         origin: (f32, f32),
1229         cell: (f32, f32),
1230         radius: f32,
1231         depth: f32,
1232     ) {
1233         let (ox, oy) = self.offset;
1234         let rect = self.apply_offset(rect);
1235         self.push(Prim::Lattice { rect, period, origin: (origin.0 + ox, origin.1 + oy), cell, radius, depth });
1236     }
1237 
1238     /// A flat fill of `material` — see [`Prim::Fill`].
1239     pub fn fill_material(&mut self, rect: Rect, radii: Radii, material: &Material) {
1240         let rect = self.apply_offset(rect);
1241         self.push(Prim::Fill { rect, radii, material: *material });
1242     }
1243 
1244     /// Grout between a periodic field of rounded cells — see [`Prim::Grout`].
1245     /// `origin` is any one cell's centre; `rect` bounds the paint.
1246     pub fn grout(&mut self, rect: Rect, period: (f32, f32), origin: (f32, f32), cell: (f32, f32), radius: f32, color: [f32; 4]) {
1247         let (ox, oy) = self.offset;
1248         let rect = self.apply_offset(rect);
1249         self.push(Prim::Grout { rect, period, origin: (origin.0 + ox, origin.1 + oy), cell, radius, color });
1250     }
1251 
1252     /// Carve (or raise, with `raised`) the union of `boxes` as one shape with
1253     /// one wall — see [`Prim::CarveUnion`]. `depth` is the wall's run in px,
1254     /// as for [`PaintCtx::recess`].
1255     pub fn carve_union(&mut self, boxes: Vec<(Rect, Radii)>, depth: f32, raised: bool) {
1256         let boxes: Vec<(Rect, Radii)> = boxes.into_iter().map(|(r, radii)| (self.apply_offset(r), radii)).collect();
1257         if boxes.is_empty() {
1258             return;
1259         }
1260         self.push(Prim::CarveUnion { boxes, depth, raised });
1261     }
1262 
1263     pub fn border(&mut self, rect: Rect, radii: Radii, fill: [f32; 4], border: [f32; 4], thickness: f32) {
1264         let rect = self.apply_offset(rect);
1265         self.push(Prim::Border { rect, radii, fill, border, thickness });
1266     }
1267 
1268     pub fn bevel(&mut self, rect: Rect, radii: Radii, material: &Material, depth: f32) {
1269         self.bevel_tinted(rect, radii, material, depth, [1.0, 1.0, 1.0]);
1270     }
1271 
1272     /// A plate whose face is `rect` outside `hole` — see [`Prim::Frame`].
1273     pub fn frame(&mut self, rect: Rect, hole: Rect, hole_radii: Radii, material: &Material, depth: f32) {
1274         let rect = self.apply_offset(rect);
1275         let hole = self.apply_offset(hole);
1276         self.push(Prim::Frame { rect, hole, hole_radii, material: *material, depth });
1277     }
1278 
1279     /// `bevel` with a specular tint — see `Prim::Bevel::tint`.
1280     pub fn bevel_tinted(&mut self, rect: Rect, radii: Radii, material: &Material, depth: f32, tint: [f32; 3]) {
1281         let rect = self.apply_offset(rect);
1282         self.push(Prim::Bevel { rect, radii, material: *material, depth, tint });
1283     }
1284 
1285     /// Carve a recess into the already-painted surface below. Unlike `bevel`, this fills
1286     /// nothing — the shading is an overlay, so it composes over whatever was painted.
1287     pub fn recess(&mut self, rect: Rect, radii: Radii, depth: f32) {
1288         self.recess_edges(rect, radii, depth, (true, true, true, true));
1289     }
1290 
1291     /// [`PaintCtx::recess`] with the lit rim tinted — see `Prim::Recess::tint`
1292     /// (the focused-well treatment).
1293     pub fn recess_tinted(&mut self, rect: Rect, radii: Radii, depth: f32, tint: [f32; 3]) {
1294         let rect = self.apply_offset(rect);
1295         self.push(Prim::Recess { rect, radii, depth, edges: (true, true, true, true), tint: Some(tint) });
1296     }
1297 
1298     /// Raise a plateau out of the already-painted surface below — the inverse of
1299     /// [`PaintCtx::recess`]. Only the edges are shaded; the face stays the surface
1300     /// beneath, so the raised region inherits the root plate's color. `depth` is the
1301     /// roll width in px (pass [`crate::layout::bevel_width`] unless the widget
1302     /// needs a tighter lip).
1303     pub fn boss(&mut self, rect: Rect, radii: Radii, depth: f32) {
1304         self.boss_edges(rect, radii, depth, (true, true, true, true));
1305     }
1306 
1307     /// [`PaintCtx::boss`] with only some of the walls — see `Prim::Boss`.
1308     pub fn boss_edges(
1309         &mut self,
1310         rect: Rect,
1311         radii: Radii,
1312         depth: f32,
1313         edges: (bool, bool, bool, bool),
1314     ) {
1315         let rect = self.apply_offset(rect);
1316         self.push(Prim::Boss { rect, radii, depth, edges, tint: None });
1317     }
1318 
1319     /// [`PaintCtx::boss_edges`] with a specular tint on the lit rim — see
1320     /// `Prim::Boss::tint`.
1321     pub fn boss_edges_tinted(
1322         &mut self,
1323         rect: Rect,
1324         radii: Radii,
1325         depth: f32,
1326         edges: (bool, bool, bool, bool),
1327         tint: [f32; 3],
1328     ) {
1329         let rect = self.apply_offset(rect);
1330         self.push(Prim::Boss { rect, radii, depth, edges, tint: Some(tint) });
1331     }
1332 
1333     /// Paint a control plate — see [`ControlPlate`]. The ONE place a control face's
1334     /// relief is composed: raised with a face is a `bevel` on the footprint;
1335     /// raised without one carves inside and raises a `boss`; flush carves
1336     /// inside and lays an `inset_plate` (trough plus face).
1337     pub fn control_plate(&mut self, plate: &ControlPlate) {
1338         match plate.stance {
1339             PlateStance::Raised => {
1340                 if let Some(face) = plate.faced() {
1341                     match plate.tint {
1342                         Some(t) => self.bevel_tinted(plate.rect, plate.radii, face, plate.depth, t),
1343                         None => self.bevel(plate.rect, plate.radii, face, plate.depth),
1344                     }
1345                 } else {
1346                     let (plateau, radii) = crate::layout::carve_inside(plate.rect, plate.radii, plate.depth);
1347                     match plate.tint {
1348                         Some(t) => self.boss_edges_tinted(plateau, radii, plate.depth, (true, true, true, true), t),
1349                         None => self.boss(plateau, radii, plate.depth),
1350                     }
1351                 }
1352             }
1353             PlateStance::Flush => {
1354                 let (trough, radii) = crate::layout::carve_inside(plate.rect, plate.radii, plate.depth);
1355                 match plate.tint {
1356                     Some(t) => self.inset_plate_tinted(trough, radii, plate.faced(), plate.depth, t),
1357                     None => self.inset_plate(trough, radii, plate.faced(), plate.depth),
1358                 }
1359             }
1360             PlateStance::Flat => {
1361                 if let Some(face) = plate.faced() {
1362                     // A QUAD deliberately, not the `Border` the relief stances
1363                     // fill through: carrying the blur-behind sentinel is half
1364                     // the point of this stance, and only quads reach it.
1365                     self.rounded_rect(
1366                         plate.rect,
1367                         plate.radii.0,
1368                         (true, true, true, true),
1369                         face.fill(PlateRole::Nested),
1370                     );
1371                 }
1372                 if let Some(t) = plate.tint {
1373                     // No relief, so no rim to light: the focus ring is drawn as
1374                     // one, over the face and keeping the per-corner silhouette.
1375                     self.border(plate.rect, plate.radii, [0.0; 4], [t[0], t[1], t[2], 1.0], 1.0);
1376                 }
1377             }
1378         }
1379     }
1380 
1381     /// A section's well — the settings app's union carve, the ONE shape a
1382     /// section or a [`crate::widget::Group`] is cut into the plate with: the
1383     /// `body` carved as a recess with `radii` (TL, TR, BR, BL), and when there
1384     /// is a title `tab` (flush on the body's top edge, at its left), the tab
1385     /// carved WITH it as one shape — the tab bottom-open, one piece owning the
1386     /// body's whole right run so its corners are real turns, a left piece
1387     /// carrying the left wall, the pieces extending past their interior seam by
1388     /// `depth` so the walls crossfade there instead of notching — and the
1389     /// throat's inside corner rounded by a concave fillet.
1390     pub fn section_well(&mut self, body: Rect, tab: Option<Rect>, radii: Radii, depth: f32) {
1391         let (cx, cy, cw, ch) = (body.x, body.y, body.width, body.height);
1392         let (tl, tr, br, bl) = radii;
1393         let Some(t) = tab else {
1394             self.recess_edges(body, radii, depth, (true, true, true, true));
1395             return;
1396         };
1397         let (tx, ty, tw, th) = (t.x, t.y, t.width, t.height);
1398         let rt = tl.max(tr).min(th * 0.45);
1399         let throat_r = tx + tw;
1400         // The designer's SECTION_FILLET_R.
1401         let rho = 10.0f32;
1402         let body_lr = |x_run: f32, pc: &mut Self| {
1403             pc.recess_edges(
1404                 Rect { x: x_run, y: cy, width: cx + cw - x_run, height: ch },
1405                 (0.0, tr, br, 0.0),
1406                 depth,
1407                 (true, true, true, false),
1408             );
1409             pc.recess_edges(
1410                 Rect { x: cx, y: cy, width: x_run + depth - cx, height: ch },
1411                 (0.0, 0.0, 0.0, bl),
1412                 depth,
1413                 (false, false, true, true),
1414             );
1415         };
1416         if cx + cw > throat_r + 2.0 * rho {
1417             // Filleted throat: the tab's right wall ends at the fillet's vertical
1418             // tangent, a left-only bridge carries the left wall across the span.
1419             self.recess_edges(
1420                 Rect { x: tx, y: ty, width: tw, height: (cy - rho) - ty + depth },
1421                 (rt, rt, 0.0, 0.0),
1422                 depth,
1423                 (true, true, false, true),
1424             );
1425             self.recess_edges(
1426                 Rect { x: tx, y: cy - rho, width: tw, height: rho + depth },
1427                 (0.0, 0.0, 0.0, 0.0),
1428                 depth,
1429                 (false, false, false, true),
1430             );
1431             body_lr(throat_r + rho - depth, self);
1432             self.concave_fillet(throat_r + rho, cy - rho, rho, depth, std::f32::consts::FRAC_PI_2, false);
1433         } else if cx + cw > throat_r + 0.5 {
1434             // Too narrow for the fillet: the plain square throat.
1435             self.recess_edges(
1436                 Rect { x: tx, y: ty, width: tw, height: (cy - ty) + depth },
1437                 (rt, rt, 0.0, 0.0),
1438                 depth,
1439                 (true, true, false, true),
1440             );
1441             body_lr(throat_r - depth, self);
1442         } else {
1443             // The tab spans the body: no top wall at all.
1444             self.recess_edges(
1445                 Rect { x: tx, y: ty, width: tw, height: (cy - ty) + depth },
1446                 (rt, rt, 0.0, 0.0),
1447                 depth,
1448                 (true, true, false, true),
1449             );
1450             self.recess_edges(
1451                 Rect { x: cx, y: cy, width: cw, height: ch },
1452                 (0.0, 0.0, br, bl),
1453                 depth,
1454                 (false, true, true, true),
1455             );
1456         }
1457     }
1458 
1459     /// A flush inset control: `rect`'s plate sits SUNKEN into the surface with
1460     /// its face level with it — a valley seam runs the boundary, the surface
1461     /// falling into it on the way out and the control's own face rising back
1462     /// out of it inside. The face never leaves the surface plane; the seam is
1463     /// the only thing saying it is a separate part. `depth` is the full width
1464     /// of that valley, which straddles the boundary by ±depth/2.
1465     ///
1466     /// One [`Prim::Trough`] — ONE lighting evaluation. This used to emit a
1467     /// `Recess` on a rect outset by depth/2 plus a `Boss` on the rect, whose
1468     /// walls overlapped over half their width and shaded twice; see
1469     /// `Prim::Trough` for what that measured as. Do not re-expand this into its
1470     /// parts.
1471     ///
1472     /// An opaque `color` fills the face; transparent leaves the surface below
1473     /// showing through as the face.
1474     pub fn inset_plate(&mut self, rect: Rect, radii: Radii, face: Option<&Material>, depth: f32) {
1475         // A transparent material is no face either — only a visible tint
1476         // fills; a frosted one fills with the sentinel.
1477         if let Some(face) = face.filter(|m| m.tint[3] > 0.001) {
1478             // Flat fill only — the relief is the trough's, so the face must not
1479             // carry a lip of its own (that lip WAS the second wall).
1480             //
1481             // Deliberately a zero-stroke `Border` and NOT `rounded_rect`: this
1482             // fill used to be a `Bevel`, and the legacy reverse bridges
1483             // (`all_rounded_quads` and friends in `widget/model.rs`) extract
1484             // `Prim::RoundedRect` but neither `Bevel` nor `Border`. Emitting a
1485             // RoundedRect here would newly leak every raised control's face into
1486             // those getters — a change to the legacy surface that has nothing to
1487             // do with the relief. Border also keeps all four radii, which
1488             // `Prim::RoundedRect`'s single radius cannot.
1489             self.border(rect, radii, face.fill(PlateRole::Nested), [0.0; 4], 0.0);
1490         }
1491         // The edge is a field's RUN (a field with no well, its seam put
1492         // [`FIELD_RUN_ONLY`] px to the left): the outer half a well's own
1493         // fall, the inner half that fall mirrored back up to the face — so
1494         // every flush control has the edge of the run at the end of a text
1495         // row's field, and the well beside it. Until 2026-10-02 this was a
1496         // [`Prim::Trough`], whose outer half is a compressed copy of a step;
1497         // the dropdown, button, breadcrumb, font selector and menubar
1498         // triggers had been switched to the run's edge one by one the day
1499         // before, and the apps' own flush plates (the calendar's, cce-cloud's,
1500         // cce-files', the system interface's) kept the trough until here.
1501         self.field(&Field::run(rect, radii, depth));
1502     }
1503 
1504     /// [`inset_plate`](Self::inset_plate) with the rim lit — the focused flush
1505     /// control plate's ring (`ControlPlate::with_tint`); the face fill as
1506     /// there, the trough tinted.
1507     pub fn inset_plate_tinted(&mut self, rect: Rect, radii: Radii, face: Option<&Material>, depth: f32, tint: [f32; 3]) {
1508         if let Some(face) = face.filter(|m| m.tint[3] > 0.001) {
1509             self.border(rect, radii, face.fill(PlateRole::Nested), [0.0; 4], 0.0);
1510         }
1511         self.field(&Field::run(rect, radii, depth).with_tint(Some(tint)));
1512     }
1513 
1514     /// A canvas well's floor — the opening you look into or draw in (a
1515     /// Trackpad, a Slider2D pad, a bevel or ramp preview) — cut into `host`,
1516     /// the material of the plate it sits on (`Material::pane()` for a pane).
1517     /// `lifted` is a clickable canvas's hover cue: the floor rises toward
1518     /// the plate.
1519     ///
1520     /// An opaque host's floor is that plate darkened, drawn as the darkening
1521     /// itself ([`crate::colors::WELL_FLOOR`] over whatever the plate resolved
1522     /// to — exact at any plate alpha, and what every floor drew before
1523     /// materials). A FROSTED host's floor is deeper glass
1524     /// ([`Material::floor`]: the host's material with the tint darkened,
1525     /// frost and finish carried), so a well in glass blurs and compresses
1526     /// what is under it again instead of being the one opaque patch in a
1527     /// frosted pane (RFC material § 11 (3)).
1528     pub fn well_floor(&mut self, rect: Rect, radius: f32, host: &Material, lifted: bool) {
1529         let fill = if host.frost.is_frosted() {
1530             host.floor(lifted).fill(PlateRole::Nested)
1531         } else if lifted {
1532             crate::colors::WELL_FLOOR_LIFTED
1533         } else {
1534             crate::colors::WELL_FLOOR
1535         };
1536         self.rounded_rect(rect, radius, (true, true, true, true), fill);
1537     }
1538 
1539     /// A canvas well's rim, drawn AFTER the content so the wall's shading falls
1540     /// over whatever runs to the edge. Under `relief` it is the recess carved
1541     /// inside `rect` ([`crate::layout::carve_inside`], the wall the DE width
1542     /// capped at a fifth of the height — every well's rule); flat, the
1543     /// hairline frame every well shares ([`crate::colors::well_frame_color`]).
1544     pub fn well_rim(&mut self, rect: Rect, radius: f32, relief: bool) {
1545         let radii = (radius, radius, radius, radius);
1546         if relief {
1547             let depth = crate::layout::bevel_width().min(rect.height * 0.2);
1548             let (well, radii) = crate::layout::carve_inside(rect, radii, depth);
1549             self.recess(well, radii, depth);
1550         } else {
1551             self.border(rect, radii, [0.0; 4], crate::colors::well_frame_color(false, false), 1.0);
1552         }
1553     }
1554 
1555     /// [`well_floor`](Self::well_floor) then [`well_rim`](Self::well_rim) in
1556     /// one call — a canvas whose content is drawn over the rim (a Trackpad's
1557     /// fingers). Content that should slide under the wall draws between the two.
1558     pub fn canvas_well(&mut self, rect: Rect, radius: f32, host: &Material, relief: bool, lifted: bool) {
1559         self.well_floor(rect, radius, host, lifted);
1560         self.well_rim(rect, radius, relief);
1561     }
1562 
1563     /// Emit one [`crate::layout::ReliefCarve`]. The shared application point:
1564     /// a widget's `paint` carves through here, and a flat host re-emits the
1565     /// carves it collected through here too, so the two can only ever draw the
1566     /// same prim.
1567     ///
1568     /// A tinted recess takes `recess_tinted`, which lights the whole rim — it
1569     /// is the focus treatment, and every tinted carve the toolkit emits is a
1570     /// full ring. A partial ring falls back to the untinted walls rather than
1571     /// silently tinting walls the caller suppressed.
1572     pub fn carve(&mut self, c: &crate::layout::ReliefCarve) {
1573         let rect = Rect { x: c.x, y: c.y, width: c.w, height: c.h };
1574         match c.kind {
1575             crate::layout::CarveKind::Boss { tint: Some(t) } if c.edges == (true, true, true, true) => {
1576                 self.boss_edges_tinted(rect, c.radii, c.depth, c.edges, t)
1577             }
1578             crate::layout::CarveKind::Boss { .. } => self.boss_edges(rect, c.radii, c.depth, c.edges),
1579             crate::layout::CarveKind::Recess { tint: Some(t) }
1580                 if c.edges == (true, true, true, true) =>
1581             {
1582                 self.recess_tinted(rect, c.radii, c.depth, t)
1583             }
1584             crate::layout::CarveKind::Recess { .. } => {
1585                 self.recess_edges(rect, c.radii, c.depth, c.edges)
1586             }
1587             crate::layout::CarveKind::Trough => self.trough_edges(rect, c.radii, c.depth, c.edges),
1588         }
1589     }
1590 
1591     /// Sink a valley along `rect`'s boundary — see [`Prim::Trough`]. `depth` is
1592     /// the full width of the seam (it straddles the outline by ±depth/2).
1593     pub fn trough(&mut self, rect: Rect, radii: Radii, depth: f32) {
1594         self.trough_edges(rect, radii, depth, (true, true, true, true));
1595     }
1596 
1597     /// [`PaintCtx::trough`] with only some of the walls — see [`Prim::Trough`].
1598     pub fn trough_edges(
1599         &mut self,
1600         rect: Rect,
1601         radii: Radii,
1602         depth: f32,
1603         edges: (bool, bool, bool, bool),
1604     ) {
1605         let rect = self.apply_offset(rect);
1606         self.push(Prim::Trough { rect, radii, depth, edges, tint: None });
1607     }
1608 
1609     /// [`PaintCtx::trough`] with the rim lit — see `Prim::Trough::tint` (the
1610     /// focused flush control plate).
1611     pub fn trough_tinted(&mut self, rect: Rect, radii: Radii, depth: f32, tint: [f32; 3]) {
1612         let rect = self.apply_offset(rect);
1613         self.push(Prim::Trough { rect, radii, depth, edges: (true, true, true, true), tint: Some(tint) });
1614     }
1615 
1616     /// [`PaintCtx::trough_edges`] with a specular tint on the lit rim — see
1617     /// `Prim::Trough::tint`.
1618     pub fn trough_edges_tinted(
1619         &mut self,
1620         rect: Rect,
1621         radii: Radii,
1622         depth: f32,
1623         edges: (bool, bool, bool, bool),
1624         tint: [f32; 3],
1625     ) {
1626         let rect = self.apply_offset(rect);
1627         self.push(Prim::Trough { rect, radii, depth, edges, tint: Some(tint) });
1628     }
1629 
1630     /// Paint a [`Field`] — see it for the forms. One with a run is a
1631     /// [`Prim::Field`]; one that is all well a [`Prim::Recess`], which groups
1632     /// into the plate under it.
1633     pub fn field(&mut self, field: &Field) {
1634         let Field { rect, radii, depth, tint, .. } = *field;
1635         match field.prim_span() {
1636             None => match tint {
1637                 Some(t) => self.recess_tinted(rect, radii, depth, t),
1638                 None => self.recess(rect, radii, depth),
1639             },
1640             Some((split, end)) => {
1641                 let (split, end) = (split + self.offset.0, end + self.offset.0);
1642                 let rect = self.apply_offset(rect);
1643                 self.push(Prim::Field { rect, radii, depth, split, end, tint });
1644             }
1645         }
1646     }
1647 
1648     /// Raise a rim along `rect`'s boundary — see `Prim::Ridge`. `depth` is the
1649     /// full width of the bump (it straddles the outline by ±depth/2).
1650     pub fn ridge(&mut self, rect: Rect, radii: Radii, depth: f32) {
1651         self.ridge_edges(rect, radii, depth, (true, true, true, true));
1652     }
1653 
1654     /// [`PaintCtx::ridge`] with only some of the walls — see `Prim::Ridge`.
1655     pub fn ridge_edges(
1656         &mut self,
1657         rect: Rect,
1658         radii: Radii,
1659         depth: f32,
1660         edges: (bool, bool, bool, bool),
1661     ) {
1662         let rect = self.apply_offset(rect);
1663         self.push(Prim::Ridge { rect, radii, depth, edges });
1664     }
1665 
1666     /// [`PaintCtx::recess`] with only some of the walls — see `Prim::Recess`.
1667     pub fn recess_edges(
1668         &mut self, rect: Rect, radii: Radii, depth: f32,
1669         edges: (bool, bool, bool, bool),
1670     ) {
1671         let rect = self.apply_offset(rect);
1672         self.push(Prim::Recess { rect, radii, depth, edges, tint: None });
1673     }
1674 
1675     /// [`PaintCtx::recess_edges`] with a specular tint on the lit rim — see
1676     /// `Prim::Recess::tint`. A tinted carve never groups into its host plate
1677     /// (the tint could only land on the whole plate's specular), so it
1678     /// shades through the overlay path with its own lit rim.
1679     pub fn recess_edges_tinted(
1680         &mut self, rect: Rect, radii: Radii, depth: f32,
1681         edges: (bool, bool, bool, bool),
1682         tint: [f32; 3],
1683     ) {
1684         let rect = self.apply_offset(rect);
1685         self.push(Prim::Recess { rect, radii, depth, edges, tint: Some(tint) });
1686     }
1687 
1688     /// The window's glass slab: rounded fill at full size plus a rolled, lit perimeter.
1689     /// `depth` is the roll-off width in px — pass [`crate::layout::bevel_width`] unless the
1690     /// window wants a shallower edge than the DE default.
1691     ///
1692     /// A NEGATIVE `depth` is the fill-less sentinel: no fill is drawn, and the
1693     /// rolled perimeter (width `-depth`) renders as an overlay — translucent
1694     /// white screen / black multiply — over whatever is beneath, for a root
1695     /// plate whose face is not a fill (the designer's full-bleed 3D canvas).
1696     /// `material` is ignored; the roll profile, crest and specular are exactly the
1697     /// positive-depth plate's.
1698     pub fn plate(&mut self, rect: Rect, radii: Radii, material: &Material, depth: f32) {
1699         self.plate_shaped(rect, radii, material, depth, None);
1700     }
1701 
1702     /// [`plate`](Self::plate) with an explicit corner exponent — see
1703     /// [`Prim::Plate`]'s `shape`. `Some(2.0)` on a plate whose radii are its
1704     /// half-extent draws a circle; `None` is exactly `plate`.
1705     pub fn plate_shaped(&mut self, rect: Rect, radii: Radii, material: &Material, depth: f32, shape: Option<f32>) {
1706         let rect = self.apply_offset(rect);
1707         self.push(Prim::Plate { rect, radii, material: *material, depth, shape });
1708     }
1709 
1710     /// Emit the plate a [`PlateSpec`] describes: role-resolved per-corner
1711     /// radii and role-encoded frost (RFC Phase 7b).
1712     ///
1713     /// The spec's radii are FINAL on-screen values (a window corner already
1714     /// wears the full silhouette span), but `Prim::Plate` speaks the older
1715     /// convention — NOMINAL radii, span applied downstream by
1716     /// `plate_push_raised(scale_corners = true)`, which the unmigrated
1717     /// hand-rolled plates (cce-cloud, the test-interface gallery shim) still
1718     /// rely on. So divide the span back out here and let the push multiply
1719     /// reconstruct the spec's exact values.
1720     ///
1721     /// Feeding the final radii straight through double-spanned every window
1722     /// corner (12 → ~100 logical at corner_shape 4.5): the plate arc pulled
1723     /// away from the compositor's clip, the black window background showed
1724     /// through as a corner crescent, and the corners stopped matching the
1725     /// desktop grid — the original 7b-2 report of this looking like "the arc
1726     /// correction" was the regression itself.
1727     pub fn plate_spec(&mut self, spec: &PlateSpec) {
1728         let f = crate::layout::corner_span_factor();
1729         let (tl, tr, br, bl) = spec.radii();
1730         self.plate(spec.rect, (tl / f, tr / f, br / f, bl / f), &spec.material.for_role(spec.role()), spec.depth);
1731     }
1732 
1733     /// The standard root plate of a `width` x `height` window —
1734     /// [`PlateSpec::window`] emitted. The first prim of a standard cce app's
1735     /// frame: everything else is laid on this surface (pane plates atop it,
1736     /// bands and wells carved into it), starting
1737     /// [`crate::layout::root_plate_inset`] in from each window edge.
1738     pub fn root_plate(&mut self, width: f32, height: f32) {
1739         self.plate_spec(&PlateSpec::window(width, height));
1740     }
1741 
1742     pub fn arc(&mut self, cx: f32, cy: f32, radius: f32, thickness: f32, start: f32, end: f32, color: [f32; 4]) {
1743         let (ox, oy) = self.offset;
1744         self.push(Prim::Arc { cx: cx + ox, cy: cy + oy, radius, thickness, start, end, color });
1745     }
1746 
1747     /// A radially-shaded ring band — see [`Prim::ArcShaded`].
1748     #[allow(clippy::too_many_arguments)]
1749     pub fn arc_shaded(
1750         &mut self,
1751         cx: f32,
1752         cy: f32,
1753         radius: f32,
1754         thickness: f32,
1755         start: f32,
1756         end: f32,
1757         inner: [f32; 4],
1758         crest: [f32; 4],
1759         outer: [f32; 4],
1760     ) {
1761         let (ox, oy) = self.offset;
1762         self.push(Prim::ArcShaded {
1763             cx: cx + ox,
1764             cy: cy + oy,
1765             radius,
1766             thickness,
1767             start,
1768             end,
1769             inner,
1770             crest,
1771             outer,
1772         });
1773     }
1774 
1775     pub fn text(&mut self, text: impl Into<String>, x: f32, y: f32, font_size: f32, color: [u8; 3]) {
1776         self.text_with(text, x, y, font_size, color, None, None);
1777     }
1778 
1779     /// Text with a per-label font and clip rect (`[l, t, r, b]`, local space) — what the
1780     /// legacy `text_labels_with_font_and_bounds` tuples carry, expressible in the display
1781     /// list since Phase 6.
1782     pub fn text_with(
1783         &mut self,
1784         text: impl Into<String>,
1785         x: f32,
1786         y: f32,
1787         font_size: f32,
1788         color: [u8; 3],
1789         font: Option<String>,
1790         bounds: Option<[f32; 4]>,
1791     ) {
1792         self.text_attrs(text, x, y, font_size, color, font, bounds, TextAttrs::default());
1793     }
1794 
1795     /// [`text_with`](PaintCtx::text_with) plus shaping attributes (italic / weight) — what the
1796     /// font picker's style-variant previews need beyond family + size.
1797     #[allow(clippy::too_many_arguments)]
1798     pub fn text_attrs(
1799         &mut self,
1800         text: impl Into<String>,
1801         x: f32,
1802         y: f32,
1803         font_size: f32,
1804         color: [u8; 3],
1805         font: Option<String>,
1806         bounds: Option<[f32; 4]>,
1807         attrs: TextAttrs,
1808     ) {
1809         let (ox, oy) = self.offset;
1810         let bounds = bounds.map(|[l, t, r, b]| [l + ox, t + oy, r + ox, b + oy]);
1811         self.push(Prim::Text { text: text.into(), x: x + ox, y: y + oy, font_size, color, alpha: 1.0, font, bounds, attrs, layout: None });
1812     }
1813 
1814     /// [`text_with`](PaintCtx::text_with) plus a glyph alpha (1.0 = opaque) — translucent
1815     /// labels (a pane fading out) without changing the sRGB u8 color convention.
1816     #[allow(clippy::too_many_arguments)]
1817     pub fn text_faded(
1818         &mut self,
1819         text: impl Into<String>,
1820         x: f32,
1821         y: f32,
1822         font_size: f32,
1823         color: [u8; 3],
1824         alpha: f32,
1825         font: Option<String>,
1826         bounds: Option<[f32; 4]>,
1827     ) {
1828         let (ox, oy) = self.offset;
1829         let bounds = bounds.map(|[l, t, r, b]| [l + ox, t + oy, r + ox, b + oy]);
1830         self.push(Prim::Text {
1831             text: text.into(),
1832             x: x + ox,
1833             y: y + oy,
1834             font_size,
1835             color,
1836             alpha,
1837             font,
1838             bounds,
1839             attrs: TextAttrs::default(),
1840             layout: None,
1841         });
1842     }
1843 
1844     /// Boxed text: word-wrap + horizontal/vertical alignment within a box (a placed text box).
1845     /// Unlike [`text_with`](PaintCtx::text_with), the backend shapes this with the box layout
1846     /// applied (cached per box). `x, y` are the box's top-left; the backend applies the vertical offset.
1847     #[allow(clippy::too_many_arguments)]
1848     pub fn text_boxed(
1849         &mut self,
1850         text: impl Into<String>,
1851         x: f32,
1852         y: f32,
1853         font_size: f32,
1854         color: [u8; 3],
1855         font: Option<String>,
1856         bounds: Option<[f32; 4]>,
1857         attrs: TextAttrs,
1858         layout: TextLayout,
1859     ) {
1860         let (ox, oy) = self.offset;
1861         let bounds = bounds.map(|[l, t, r, b]| [l + ox, t + oy, r + ox, b + oy]);
1862         self.push(Prim::Text {
1863             text: text.into(),
1864             x: x + ox,
1865             y: y + oy,
1866             font_size,
1867             color,
1868             alpha: 1.0,
1869             font,
1870             bounds,
1871             attrs,
1872             layout: Some(layout),
1873         });
1874     }
1875 
1876     /// Consume the context and return the accumulated display list.
1877     pub fn finish(self) -> DisplayList {
1878         debug_assert!(self.clip_stack.is_empty(), "unbalanced push_clip/pop_clip");
1879         debug_assert!(self.offset_stack.is_empty(), "unbalanced translate");
1880         self.list
1881     }
1882 }
1883 
1884 /// `PaintCtx` as a popover render target: display-list hosts pass their frame
1885 /// ctx straight into `render_popover`, so popovers draw REAL prims — relief
1886 /// plates, rounded rects, bounded text — instead of the flattened
1887 /// `PopoverCollector` view (which stays for legacy tuple hosts).
1888 impl crate::layout::RenderTarget for PaintCtx {
1889     fn icon(&mut self, name: &str, rect: Rect, color: [f32; 4]) {
1890         PaintCtx::icon(self, name, rect, color);
1891     }
1892     fn line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, thickness: f32, color: [f32; 4], cap: Cap) {
1893         PaintCtx::vector(self, x1, y1, x2, y2, thickness, color, cap);
1894     }
1895     fn arc(&mut self, cx: f32, cy: f32, radius: f32, thickness: f32, start: f32, end: f32, color: [f32; 4]) {
1896         PaintCtx::arc(self, cx, cy, radius, thickness, start, end, color);
1897     }
1898     fn circle(&mut self, cx: f32, cy: f32, radius: f32, color: [f32; 4]) {
1899         PaintCtx::circle(self, cx, cy, radius, color);
1900     }
1901 
1902     fn rect(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32) {
1903         self.quad(Rect { x, y, width: w, height: h }, color);
1904     }
1905     fn rect_with_radius(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, radius: f32) {
1906         self.rounded_rect(Rect { x, y, width: w, height: h }, radius, (true, true, true, true), color);
1907     }
1908     fn rect_with_radius_corners(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, radius: f32, corners: (bool, bool, bool, bool)) {
1909         self.rounded_rect(Rect { x, y, width: w, height: h }, radius, corners, color);
1910     }
1911     fn text(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
1912         let c = [
1913             (color[0] * 255.0).clamp(0.0, 255.0) as u8,
1914             (color[1] * 255.0).clamp(0.0, 255.0) as u8,
1915             (color[2] * 255.0).clamp(0.0, 255.0) as u8,
1916         ];
1917         PaintCtx::text(self, content, x, y, size, c);
1918     }
1919     fn text_with_font(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4], font: &str) {
1920         crate::layout::RenderTarget::text_with_font_and_bounds(self, content, x, y, size, color, font, None);
1921     }
1922     fn text_with_bounds(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4], bounds: Option<[f32; 4]>) {
1923         let c = [
1924             (color[0] * 255.0).clamp(0.0, 255.0) as u8,
1925             (color[1] * 255.0).clamp(0.0, 255.0) as u8,
1926             (color[2] * 255.0).clamp(0.0, 255.0) as u8,
1927         ];
1928         self.text_with(content, x, y, size, c, None, bounds);
1929     }
1930     fn text_with_font_and_bounds(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4], font: &str, bounds: Option<[f32; 4]>) {
1931         let c = [
1932             (color[0] * 255.0).clamp(0.0, 255.0) as u8,
1933             (color[1] * 255.0).clamp(0.0, 255.0) as u8,
1934             (color[2] * 255.0).clamp(0.0, 255.0) as u8,
1935         ];
1936         self.text_with(content, x, y, size, c, Some(font.to_string()), bounds);
1937     }
1938     fn push_clip_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
1939         self.push_clip(Rect { x, y, width: w, height: h });
1940     }
1941     fn pop_clip_rect(&mut self) {
1942         self.pop_clip();
1943     }
1944     fn inset_plate(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, radius: f32, depth: f32) {
1945         PaintCtx::inset_plate(self, Rect { x, y, width: w, height: h }, (radius, radius, radius, radius), Material::face(color).as_ref(), depth);
1946     }
1947     fn inset_plate_tinted(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, radius: f32, depth: f32, tint: [f32; 3]) {
1948         PaintCtx::inset_plate_tinted(self, Rect { x, y, width: w, height: h }, (radius, radius, radius, radius), Material::face(color).as_ref(), depth, tint);
1949     }
1950     fn relief_carve(&mut self, carve: &crate::layout::ReliefCarve) {
1951         PaintCtx::carve(self, carve);
1952     }
1953 }
1954 
1955 #[cfg(test)]
1956 mod tests {
1957     use super::*;
1958 
1959     /// A nested paint spliced in keeps its own clips under the host's: the scissors
1960     /// intersect, and the nested item's rounded clip wins over the host's.
1961     #[test]
1962     fn appended_items_keep_their_clips_under_the_hosts() {
1963         let r = |x: f32, y: f32, w: f32, h: f32| Rect { x, y, width: w, height: h };
1964         let mut nested = PaintCtx::new();
1965         nested.quad(r(0.0, 0.0, 5.0, 5.0), [1.0; 4]);
1966         nested.clip_rounded(r(10.0, 10.0, 40.0, 40.0), 6.0, |pc| pc.quad(r(12.0, 12.0, 5.0, 5.0), [1.0; 4]));
1967         let mut host = PaintCtx::new();
1968         host.clip(r(0.0, 0.0, 30.0, 30.0), |pc| pc.append_items(nested.finish().items));
1969         let items = host.finish().items;
1970         assert_eq!(items.len(), 2);
1971         assert_eq!(items[0].clip, Some(r(0.0, 0.0, 30.0, 30.0)), "an unclipped item takes the host's clip");
1972         assert_eq!(items[1].clip, Some(r(10.0, 10.0, 20.0, 20.0)), "the two scissors intersect");
1973         assert!(items[1].clip_rrect.is_some_and(|c| c[4] == 6.0), "its rounded clip survives");
1974     }
1975 
1976     /// The one flush control plate draws a face and a field that is all
1977     /// run — the edge every flush control wears — and no trough.
1978     #[test]
1979     fn an_inset_plate_is_a_field_that_is_all_run() {
1980         let rect = Rect { x: 10.0, y: 20.0, width: 120.0, height: 24.0 };
1981         let face = Material::face([0.2, 0.2, 0.25, 1.0]);
1982         for tint in [None, Some([1.0, 0.5, 0.0])] {
1983             let mut pc = PaintCtx::new();
1984             match tint {
1985                 Some(t) => pc.inset_plate_tinted(rect, (4.0, 4.0, 4.0, 4.0), face.as_ref(), 4.0, t),
1986                 None => pc.inset_plate(rect, (4.0, 4.0, 4.0, 4.0), face.as_ref(), 4.0),
1987             }
1988             let prims: Vec<Prim> = pc.finish().items.into_iter().map(|i| i.prim).collect();
1989             assert!(!prims.iter().any(|p| matches!(p, Prim::Trough { .. })), "{prims:?}");
1990             assert!(prims.iter().any(|p| matches!(p, Prim::Border { .. })), "the face");
1991             assert!(
1992                 prims.iter().any(|p| matches!(p, Prim::Field { rect: r, split, tint: t, .. } if *r == rect && *split <= rect.x - 100.0 && *t == tint)),
1993                 "{prims:?}"
1994             );
1995         }
1996     }
1997     use crate::scene::material::Frost;
1998 
1999     /// RFC Phase 7b: PlateSpec role mechanics — flag derivation from window
2000     /// geometry, silhouette-vs-nominal radii selection, and the role-encoded
2001     /// frost (root positive-alpha, nested negative-alpha sentinel).
2002     #[test]
2003     fn plate_spec_roles() {
2004         // Flags: a full-window rect is root; an inset pane has none; a pane
2005         // flush to the window's right edge owns the two right corners.
2006         let root_flags = PlateSpec::window_corner_flags(
2007             Rect { x: 0.0, y: 0.0, width: 800.0, height: 600.0 }, 800.0, 600.0);
2008         assert_eq!(root_flags, (true, true, true, true));
2009         let inset = PlateSpec::window_corner_flags(
2010             Rect { x: 20.0, y: 20.0, width: 100.0, height: 100.0 }, 800.0, 600.0);
2011         assert_eq!(inset, (false, false, false, false));
2012         let right_pane = PlateSpec::window_corner_flags(
2013             Rect { x: 500.0, y: 0.0, width: 300.0, height: 600.0 }, 800.0, 600.0);
2014         assert_eq!(right_pane, (false, true, true, false));
2015 
2016         // Radii: flagged corners wear the shared silhouette curve, interior
2017         // ones the nominal plate radius (compared against the same getters,
2018         // so the assertion holds for any configured values).
2019         let window_r =
2020             crate::layout::window_corner_radius() * crate::layout::corner_span_factor();
2021         let nominal = crate::layout::plate_corner_radius();
2022         let r = PlateSpec::radii_for((false, true, true, false));
2023         assert_eq!(r, (nominal, window_r, window_r, nominal));
2024 
2025         // Frost encoding by role.
2026         let frosted = Frost::Frosted { compression: 0.0, refraction: 0.0, radius: Frost::DEFAULT_RADIUS };
2027         let mut spec = PlateSpec {
2028             rect: Rect { x: 0.0, y: 0.0, width: 10.0, height: 10.0 },
2029             material: Material::opaque([0.1, 0.2, 0.3, 0.8]).with_frost(frosted),
2030             window_corners: (true, true, true, true),
2031             depth: 3.0,
2032         };
2033         assert!(spec.is_root());
2034         assert_eq!(spec.role(), PlateRole::Root);
2035         assert!(spec.fill()[3] > 0.0, "root frost is the compositor's; alpha stays positive");
2036         spec.window_corners = (false, true, true, false);
2037         assert!(!spec.is_root());
2038         assert_eq!(spec.role(), PlateRole::Nested);
2039         assert!(spec.fill()[3] < 0.0, "nested frost = negative-alpha sentinel");
2040         spec.material.frost = Frost::Unfrosted;
2041         assert_eq!(spec.fill()[3], 0.8, "no frost, no encoding");
2042 
2043         // The detach role flip (RFC 7c): a frosted nested pane becomes a
2044         // root — silhouette corners, and the frost regime flips from the
2045         // in-app sentinel to the compositor's (alpha back to positive).
2046         spec.material.frost = frosted;
2047         assert!(spec.fill()[3] < 0.0);
2048         let det = spec.detached();
2049         assert!(det.is_root());
2050         assert!(det.fill()[3] > 0.0, "root frost is the compositor's again");
2051         let wr = crate::layout::window_silhouette_radius();
2052         assert_eq!(det.radii(), (wr, wr, wr, wr));
2053     }
2054 
2055     fn r(x: f32, y: f32, w: f32, h: f32) -> Rect {
2056         Rect { x, y, width: w, height: h }
2057     }
2058 
2059     /// The emission round-trip: `plate_spec` pre-divides by the span factor so
2060     /// `plate_push_raised(scale_corners = true)` lands each corner at exactly
2061     /// the spec's final radius. Guards the double-span regression (7b-2), and
2062     /// holds for any configured corner_shape because both sides use the same
2063     /// factor.
2064     #[test]
2065     fn plate_spec_emission_round_trips_the_span() {
2066         let spec = PlateSpec {
2067             rect: r(0.0, 0.0, 400.0, 300.0),
2068             material: Material::opaque([0.1, 0.2, 0.3, 0.8]),
2069             window_corners: (true, true, false, false),
2070             depth: 4.0,
2071         };
2072         let mut pc = PaintCtx::new();
2073         pc.plate_spec(&spec);
2074         let f = crate::layout::corner_span_factor();
2075         let emitted = pc
2076             .finish()
2077             .items
2078             .iter()
2079             .find_map(|it| match &it.prim {
2080                 Prim::Plate { radii, .. } => Some(*radii),
2081                 _ => None,
2082             })
2083             .expect("plate_spec emits a Prim::Plate");
2084         let want = spec.radii();
2085         let got = (emitted.0 * f, emitted.1 * f, emitted.2 * f, emitted.3 * f);
2086         for (g, w) in [(got.0, want.0), (got.1, want.1), (got.2, want.2), (got.3, want.3)] {
2087             assert!((g - w).abs() < 1e-3, "span round-trip drifted: {g} vs {w}");
2088         }
2089     }
2090 
2091     #[test]
2092     fn emits_in_order_unclipped() {
2093         let mut ctx = PaintCtx::new();
2094         ctx.quad(r(0.0, 0.0, 10.0, 10.0), [1.0, 0.0, 0.0, 1.0]);
2095         ctx.quad(r(5.0, 5.0, 10.0, 10.0), [0.0, 1.0, 0.0, 1.0]);
2096         let list = ctx.finish();
2097         assert_eq!(list.len(), 2);
2098         assert_eq!(list.items[0].clip, None);
2099         assert!(matches!(list.items[0].prim, Prim::Quad { color, .. } if color[0] == 1.0));
2100         assert!(matches!(list.items[1].prim, Prim::Quad { color, .. } if color[1] == 1.0));
2101     }
2102 
2103     #[test]
2104     fn clip_is_recorded_and_popped() {
2105         let mut ctx = PaintCtx::new();
2106         ctx.clip(r(0.0, 0.0, 50.0, 50.0), |ctx| {
2107             ctx.quad(r(10.0, 10.0, 5.0, 5.0), [0.0; 4]);
2108         });
2109         ctx.quad(r(60.0, 60.0, 5.0, 5.0), [0.0; 4]); // outside any clip now
2110         let list = ctx.finish();
2111         assert_eq!(list.items[0].clip, Some(r(0.0, 0.0, 50.0, 50.0)));
2112         assert_eq!(list.items[1].clip, None, "clip popped after the closure");
2113     }
2114 
2115     #[test]
2116     fn nested_clips_intersect() {
2117         let mut ctx = PaintCtx::new();
2118         ctx.clip(r(0.0, 0.0, 100.0, 100.0), |ctx| {
2119             ctx.clip(r(50.0, 50.0, 100.0, 100.0), |ctx| {
2120                 ctx.quad(r(0.0, 0.0, 1.0, 1.0), [0.0; 4]);
2121             });
2122         });
2123         // Intersection of (0,0,100,100) and (50,50,100,100) = (50,50,50,50).
2124         assert_eq!(ctx.finish().items[0].clip, Some(r(50.0, 50.0, 50.0, 50.0)));
2125     }
2126 
2127     #[test]
2128     fn non_overlapping_clips_produce_empty_scissor() {
2129         let mut ctx = PaintCtx::new();
2130         ctx.clip(r(0.0, 0.0, 10.0, 10.0), |ctx| {
2131             ctx.clip(r(100.0, 100.0, 10.0, 10.0), |ctx| {
2132                 ctx.quad(r(0.0, 0.0, 1.0, 1.0), [0.0; 4]);
2133             });
2134         });
2135         let clip = ctx.finish().items[0].clip.unwrap();
2136         assert_eq!((clip.width, clip.height), (0.0, 0.0), "empty intersection");
2137     }
2138 
2139     #[test]
2140     fn translate_applies_to_coordinates_and_restores() {
2141         let mut ctx = PaintCtx::new();
2142         ctx.translate(100.0, 200.0, |ctx| {
2143             ctx.quad(r(0.0, 0.0, 5.0, 5.0), [0.0; 4]);
2144         });
2145         ctx.quad(r(0.0, 0.0, 5.0, 5.0), [0.0; 4]); // back at origin
2146         let list = ctx.finish();
2147         assert!(matches!(list.items[0].prim, Prim::Quad { rect, .. } if rect.x == 100.0 && rect.y == 200.0));
2148         assert!(matches!(list.items[1].prim, Prim::Quad { rect, .. } if rect.x == 0.0 && rect.y == 0.0));
2149     }
2150 
2151     #[test]
2152     fn nested_translate_is_cumulative() {
2153         let mut ctx = PaintCtx::new();
2154         ctx.translate(10.0, 10.0, |ctx| {
2155             ctx.translate(5.0, 5.0, |ctx| {
2156                 ctx.circle(0.0, 0.0, 3.0, [0.0; 4]);
2157             });
2158         });
2159         assert!(matches!(ctx.finish().items[0].prim, Prim::Circle { cx, cy, .. } if cx == 15.0 && cy == 15.0));
2160     }
2161 
2162     #[test]
2163     fn clip_pushed_under_translation_is_absolute() {
2164         let mut ctx = PaintCtx::new();
2165         ctx.translate(20.0, 20.0, |ctx| {
2166             ctx.clip(r(0.0, 0.0, 30.0, 30.0), |ctx| {
2167                 ctx.quad(r(0.0, 0.0, 5.0, 5.0), [0.0; 4]);
2168             });
2169         });
2170         let item = &ctx.finish().items[0];
2171         // Clip translated to absolute (20,20,30,30); prim likewise at (20,20).
2172         assert_eq!(item.clip, Some(r(20.0, 20.0, 30.0, 30.0)));
2173         assert!(matches!(item.prim, Prim::Quad { rect, .. } if rect.x == 20.0 && rect.y == 20.0));
2174     }
2175 
2176     #[test]
2177     fn all_primitive_kinds_emit() {
2178         let mut ctx = PaintCtx::new();
2179         ctx.quad(r(0.0, 0.0, 1.0, 1.0), [0.0; 4]);
2180         ctx.rounded_rect(r(0.0, 0.0, 1.0, 1.0), 2.0, (true, false, true, false), [0.0; 4]);
2181         ctx.border(r(0.0, 0.0, 10.0, 10.0), (2.0, 2.0, 2.0, 2.0), [0.1; 4], [0.9; 4], 1.5);
2182         ctx.bevel(r(0.0, 0.0, 10.0, 10.0), (2.0, 2.0, 2.0, 2.0), &Material::opaque([0.3; 4]), 2.0);
2183         ctx.arc(5.0, 5.0, 4.0, 1.0, 0.0, std::f32::consts::PI, [0.0; 4]);
2184         ctx.vector(0.0, 0.0, 10.0, 0.0, 1.0, [0.0; 4], Cap::Arrow);
2185         ctx.circle(5.0, 5.0, 3.0, [0.0; 4]);
2186         ctx.text("hi", 1.0, 2.0, 12.0, [255, 255, 255]);
2187         assert_eq!(ctx.finish().len(), 8);
2188     }
2189 
2190     #[test]
2191     fn border_and_bevel_are_offset() {
2192         let mut ctx = PaintCtx::new();
2193         ctx.translate(10.0, 20.0, |ctx| {
2194             ctx.border(r(0.0, 0.0, 5.0, 5.0), (1.0, 1.0, 1.0, 1.0), [0.0; 4], [1.0; 4], 1.0);
2195             ctx.bevel(r(0.0, 0.0, 5.0, 5.0), (1.0, 1.0, 1.0, 1.0), &Material::opaque([0.0; 4]), 1.0);
2196         });
2197         let list = ctx.finish();
2198         assert!(matches!(list.items[0].prim, Prim::Border { rect, .. } if rect.x == 10.0 && rect.y == 20.0));
2199         assert!(matches!(list.items[1].prim, Prim::Bevel { rect, .. } if rect.x == 10.0 && rect.y == 20.0));
2200     }
2201     #[test]
2202     fn text_with_translates_position_and_bounds() {
2203         let mut ctx = PaintCtx::new();
2204         ctx.translate(10.0, 20.0, |ctx| {
2205             ctx.text_with("hi", 1.0, 2.0, 12.0, [1, 2, 3], Some("Mono".into()), Some([0.0, 0.0, 50.0, 30.0]));
2206             ctx.text("plain", 3.0, 4.0, 10.0, [9, 9, 9]);
2207         });
2208         let list = ctx.finish();
2209         match &list.items[0].prim {
2210             Prim::Text { x, y, font, bounds, .. } => {
2211                 assert_eq!((*x, *y), (11.0, 22.0), "position translated");
2212                 assert_eq!(font.as_deref(), Some("Mono"));
2213                 assert_eq!(*bounds, Some([10.0, 20.0, 60.0, 50.0]), "bounds translated");
2214             }
2215             other => panic!("expected Text, got {other:?}"),
2216         }
2217         match &list.items[1].prim {
2218             Prim::Text { font, bounds, .. } => {
2219                 assert_eq!(*font, None, "plain text carries no font");
2220                 assert_eq!(*bounds, None);
2221             }
2222             other => panic!("expected Text, got {other:?}"),
2223         }
2224     }
2225 
2226 }