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

commit35f5183e113fbca2047dad8eecb0665f5c3a5547
parent0cc89694a3
authorLucas Galante <[email protected]>
date2026-08-15 19:10
feat: Prim::Trough — one lighting pass for the flush inset seam

`inset_plate` built the seam around every flush control from two prims: a
Recess on a rect outset by depth/2 and a Boss on the rect. Both walls
straddle their own boundary by ±depth/2 (shader2d `u = fd/t + 0.5`), so at
depth 4.8 they spanned [-4.8, +9.6] and [-4.8, +4.8] device px about the
edge — overlapping over half their width and shading twice there.

Measured on the cce-files view dropdown against a ridge of the same depth,
both over one flat field: the band ran 15px instead of 8 with THREE lobes
(bright +29 / dark -17 / bright +99) because the recess ring's own lit rim
landed ~depth outside the control rather than merging, and the highlight
peaked 22% hotter than a single evaluation. It read as two concentric
rings, which is what it was.

Prim::Trough is the sunken twin of Prim::Ridge and shares its shader
branch with the profile inverted, so neither can drift back into a
two-pass stack. Same measurement after: band 10px (target 9.6), one wall
down, one back up, peak +91.

The opaque face is now a zero-stroke Border rather than a Bevel — the
Bevel's lip WAS the second wall. Deliberately not a RoundedRect: the
legacy reverse bridges in widget/model.rs extract RoundedRect but neither
Bevel nor Border, so a RoundedRect would newly leak every raised control's
face into all_rounded_quads. Border also keeps all four radii.

Verified in the demo gallery (button, toggle, dropdown): fills, colors and
corners unchanged, only the seam; the diff bbox covers exactly those three
controls and cce-system-interface, which never calls inset_plate, is
byte-identical.

 src/backend/window_runner.rs | 31 ++++++++++++++--
 src/scene/paint.rs           | 85 +++++++++++++++++++++++++++++++++-----------
 src/vk/shader2d.wgsl         | 22 ++++++++----
 3 files changed, 109 insertions(+), 29 deletions(-)

diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index 9ca4734..6b506f8 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -1521,7 +1521,7 @@ fn prim_kind(p: &crate::scene::paint::Prim) -> &'static str {
         P::Quad { .. } => "Quad", P::RoundedRect { .. } => "RoundedRect",
         P::Border { .. } => "Border", P::Bevel { .. } => "Bevel",
         P::Recess { .. } => "Recess", P::Boss { .. } => "Boss",
-        P::Ridge { .. } => "Ridge", P::Plate { .. } => "Plate",
+        P::Ridge { .. } => "Ridge", P::Trough { .. } => "Trough", P::Plate { .. } => "Plate",
         P::Arc { .. } => "Arc", P::ArcShaded { .. } => "ArcShaded",
         P::Vector { .. } => "Vector", P::Circle { .. } => "Circle",
         P::Sphere { .. } => "Sphere", P::ConcaveFillet { .. } => "ConcaveFillet",
@@ -1660,6 +1660,7 @@ pub fn tessellate_display_list(
             Prim::Recess { rect, radii, depth, edges, .. }
             | Prim::Boss { rect, radii, depth, edges, .. }
             | Prim::Ridge { rect, radii, depth, edges }
+            | Prim::Trough { rect, radii, depth, edges }
                 if shader_plates =>
             {
                 let tint = match &item.prim {
@@ -1669,11 +1670,13 @@ pub fn tessellate_display_list(
                 };
                 // Recess carves down into the surface; Boss raises a plateau out
                 // of it (same machinery, depth sign flipped); Ridge is a raised
-                // rim straddling the boundary (its own overlay profile — never
-                // grouped, the CSG features only model monotonic steps).
+                // rim straddling the boundary and Trough the sunken valley twin
+                // (their own overlay profiles — never grouped, the CSG features
+                // only model monotonic steps).
                 let mode = match &item.prim {
                     Prim::Boss { .. } => 3.0f32,
                     Prim::Ridge { .. } => 4.0,
+                    Prim::Trough { .. } => 9.0,
                     _ => 2.0,
                 };
                 let raised = mode > 2.5;
@@ -1738,6 +1741,7 @@ pub fn tessellate_display_list(
                             let kind = match &item.prim {
                                 Prim::Boss { .. } => "boss",
                                 Prim::Ridge { .. } => "ridge",
+                                Prim::Trough { .. } => "trough",
                                 _ => "recess",
                             };
                             let infl = *depth * 0.5 + 2.0;
@@ -1942,6 +1946,27 @@ pub fn tessellate_display_list(
                     EdgeKind::Step, &mut verts,
                 );
             }
+            Prim::Trough { rect, radii, depth, edges } => {
+                // Legacy approximation, the Ridge arm's two steps with the light
+                // signs swapped: down at the boundary, back up half a width in.
+                // The banded machinery has no valley profile, so this is the old
+                // stacked look — accepted here, as the legacy path exists only
+                // for A/B comparison against the SDF one.
+                let half = *depth * 0.5;
+                push_bevel_edge_vertices_banded(
+                    rect.x, rect.y, rect.width, rect.height, *radii, half,
+                    sw, sh, [0.0; 4], no, -1.0, default_bevel_bands(half), *edges,
+                    EdgeKind::Step, &mut verts,
+                );
+                let ir = (radii.0 - half).max(0.0);
+                push_bevel_edge_vertices_banded(
+                    rect.x + half, rect.y + half,
+                    rect.width - *depth, rect.height - *depth,
+                    (ir, ir, ir, ir), half,
+                    sw, sh, [0.0; 4], no, 1.0, default_bevel_bands(half), *edges,
+                    EdgeKind::Step, &mut verts,
+                );
+            }
             Prim::Arc { cx, cy, radius, thickness, start: sa, end: ea, color } => {
                 push_arc_background_vertices(*cx, *cy, *radius, *thickness, *sa, *ea, sw, sh, *color, segs(*radius), no, &mut verts);
             }
diff --git a/src/scene/paint.rs b/src/scene/paint.rs
index 7ee3631..3c285ca 100644
--- a/src/scene/paint.rs
+++ b/src/scene/paint.rs
@@ -108,6 +108,25 @@ pub enum Prim {
     /// a Boss plus an inset Recess stacks two shading passes (double specular /
     /// shoulder terms at the crest) and reads far hotter than a plate edge.
     Ridge { rect: Rect, radii: Radii, depth: f32, edges: (bool, bool, bool, bool) },
+    /// The sunken twin of [`Prim::Ridge`]: a VALLEY riding the rect's boundary —
+    /// a bump profile straddling the outline (span ±depth/2), falling from the
+    /// surrounding surface to a trough on the boundary and rising back to the
+    /// same level inside, so both faces sit at the underlying surface's own
+    /// level. This is the seam a flush inset control leaves ([`PaintCtx::inset_plate`]).
+    ///
+    /// Same reason to exist as `Ridge`, measured: building this from a `Recess`
+    /// on an outset rect plus a `Boss` on the rect (what `inset_plate` used to
+    /// emit) stacks two independent shading passes. At depth 4.8 that read as a
+    /// band 15px wide instead of 8 with THREE lobes — bright, dark, brighter —
+    /// because the recess ring's own lit rim lands ~depth outside the control
+    /// instead of merging into one wall, and the highlight peaked 22% hotter
+    /// than a single evaluation of the same depth. It looked like two concentric
+    /// rings, which is what it was.
+    ///
+    /// `edges` and the host-box fade behave exactly as [`Prim::Recess`]'s.
+    /// SDF path only; the legacy banded tessellation approximates it with the
+    /// old two-step stack (like `Ridge`, which approximates itself there).
+    Trough { rect: Rect, radii: Radii, depth: f32, edges: (bool, bool, bool, bool) },
     /// The window's glass slab: a rounded fill plus a rolled, lit edge around its whole
     /// perimeter, drawn at full size. Distinct from `Bevel`, which insets its fill by
     /// `depth` — a plate must fill the window exactly, or the compositor's rounded window
@@ -486,6 +505,7 @@ impl PaintCtx {
                 None => self.boss_edges(rect, radii, depth, edges),
             },
             Prim::Ridge { rect, radii, depth, edges } => self.ridge_edges(rect, radii, depth, edges),
+            Prim::Trough { rect, radii, depth, edges } => self.trough_edges(rect, radii, depth, edges),
             Prim::Plate { rect, radii, color, depth } => self.plate(rect, radii, color, depth),
             Prim::Arc { cx, cy, radius, thickness, start, end, color } => {
                 self.arc(cx, cy, radius, thickness, start, end, color)
@@ -584,29 +604,54 @@ impl PaintCtx {
     }
 
     /// A flush inset control: `rect`'s plate sits SUNKEN into the surface with
-    /// its face level with it — a groove ring carved around the control (the
-    /// outward wall steps down) and the control's own beveled lip rising back
-    /// up inside. Two opposite-facing bevels; the face never leaves the
-    /// surface plane. An opaque `color` fills the face (Bevel); transparent
-    /// degrades to edges-only (Boss), the surface below showing through as
-    /// the face. `depth` is the roll width of both walls; the ring is
-    /// expanded by depth/2, so the descending wall meets the rising lip in a
-    /// tight V-groove with no flat floor between them.
+    /// its face level with it — a valley seam runs the boundary, the surface
+    /// falling into it on the way out and the control's own face rising back
+    /// out of it inside. The face never leaves the surface plane; the seam is
+    /// the only thing saying it is a separate part. `depth` is the full width
+    /// of that valley, which straddles the boundary by ±depth/2.
+    ///
+    /// One [`Prim::Trough`] — ONE lighting evaluation. This used to emit a
+    /// `Recess` on a rect outset by depth/2 plus a `Boss` on the rect, whose
+    /// walls overlapped over half their width and shaded twice; see
+    /// `Prim::Trough` for what that measured as. Do not re-expand this into its
+    /// parts.
+    ///
+    /// An opaque `color` fills the face; transparent leaves the surface below
+    /// showing through as the face.
     pub fn inset_plate(&mut self, rect: Rect, radii: Radii, color: [f32; 4], depth: f32) {
-        let g = depth * 0.5;
-        let outer = Rect {
-            x: rect.x - g,
-            y: rect.y - g,
-            width: rect.width + 2.0 * g,
-            height: rect.height + 2.0 * g,
-        };
-        let (r1, r2, r3, r4) = radii;
-        self.recess(outer, (r1 + g, r2 + g, r3 + g, r4 + g), depth);
         if color[3] > 0.001 {
-            self.bevel(rect, radii, color, depth);
-        } else {
-            self.boss(rect, radii, depth);
+            // Flat fill only — the relief is the trough's, so the face must not
+            // carry a lip of its own (that lip WAS the second wall).
+            //
+            // Deliberately a zero-stroke `Border` and NOT `rounded_rect`: this
+            // fill used to be a `Bevel`, and the legacy reverse bridges
+            // (`all_rounded_quads` and friends in `widget/model.rs`) extract
+            // `Prim::RoundedRect` but neither `Bevel` nor `Border`. Emitting a
+            // RoundedRect here would newly leak every raised control's face into
+            // those getters — a change to the legacy surface that has nothing to
+            // do with the relief. Border also keeps all four radii, which
+            // `Prim::RoundedRect`'s single radius cannot.
+            self.border(rect, radii, color, [0.0; 4], 0.0);
         }
+        self.trough(rect, radii, depth);
+    }
+
+    /// Sink a valley along `rect`'s boundary — see [`Prim::Trough`]. `depth` is
+    /// the full width of the seam (it straddles the outline by ±depth/2).
+    pub fn trough(&mut self, rect: Rect, radii: Radii, depth: f32) {
+        self.trough_edges(rect, radii, depth, (true, true, true, true));
+    }
+
+    /// [`PaintCtx::trough`] with only some of the walls — see [`Prim::Trough`].
+    pub fn trough_edges(
+        &mut self,
+        rect: Rect,
+        radii: Radii,
+        depth: f32,
+        edges: (bool, bool, bool, bool),
+    ) {
+        let rect = self.apply_offset(rect);
+        self.push(Prim::Trough { rect, radii, depth, edges });
     }
 
     /// Raise a rim along `rect`'s boundary — see `Prim::Ridge`. `depth` is the
diff --git a/src/vk/shader2d.wgsl b/src/vk/shader2d.wgsl
index 05ecdac..59d380f 100644
--- a/src/vk/shader2d.wgsl
+++ b/src/vk/shader2d.wgsl
@@ -128,6 +128,7 @@ const MODE_SPHERE: i32 = 5;       // hemisphere-lit disc
 const MODE_FILLET_DOWN: i32 = 6;  // concave inside-corner wall, recessed
 const MODE_FILLET_UP: i32 = 7;    // concave inside-corner wall, raised
 const MODE_GROOVE: i32 = 8;       // slab carve about an arbitrary line
+const MODE_TROUGH: i32 = 9;       // sunken valley straddling the boundary
 // Fillet modes rejoin the shared free-carve path as their flat equivalents.
 const FILLET_TO_STEP: i32 = 4;    // 6 -> RECESS, 7 -> BOSS
 
@@ -375,7 +376,9 @@ fn plate_shade(frag: vec2f, vcol: vec4f) -> vec4f {
     // the same wall with the height sign flipped. MODE_RIDGE is a
     // raised bump straddling the boundary, both sides at the base level — ONE
     // profile evaluation, so its crest carries a single specular/shoulder term
-    // instead of a boss+recess double-stack.
+    // instead of a boss+recess double-stack. MODE_TROUGH is that bump inverted
+    // (a valley), for the same reason: it replaced the recess-ring+boss stack
+    // `inset_plate` used to emit for every flush control in the DE.
     // All profiles straddle the boundary (span [-t/2, t/2]). Darkening is exact
     // multiplicative shading (black at alpha 1 - shade); brightening is a
     // translucent white screen.
@@ -419,18 +422,25 @@ fn plate_shade(frag: vec2f, vcol: vec4f) -> vec4f {
     let u = clamp(fd / t + 0.5, 0.0, 1.0);
     var slope = 0.0;
     var curv = 0.0;
-    if (eff == MODE_RIDGE) {
+    if (eff == MODE_RIDGE || eff == MODE_TROUGH) {
         // Ridge bump: the carve profile mirrored about the boundary (rising
         // outer half, falling inner half), amplitude halved so the wall tilt
-        // matches a step's despite the doubled profile rate.
+        // matches a step's despite the doubled profile rate. MODE_TROUGH is the
+        // same profile inverted — falling outer half, rising inner half — the
+        // valley a flush inset control leaves. Sharing this branch is the point:
+        // both get ONE evaluation, so neither can drift into the two-pass
+        // double-shading the stacked form had.
         let w = clamp(select(2.0 * u, 2.0 - 2.0 * u, u > 0.5), 0.0, 1.0);
-        let rising = select(-1.0, 1.0, u <= 0.5);
+        let up = select(-1.0, 1.0, eff == MODE_RIDGE);
+        let rising = select(-1.0, 1.0, u <= 0.5) * up;
         slope = rising * 0.5 * RECESS_DEPTH * 2.0 * carve_slope(w);
         // Each half-wall is a boss wall: concave fillet at its base, convex
         // shoulder toward the crest — and ZERO at the plateaus and crest, so
         // flat ground composites to exactly nothing (a constant term here
-        // tints the whole cover quad).
-        curv = -rrect_clip.p_mat.w * sin(w * TAU);
+        // tints the whole cover quad). A trough's curvature flips with it: the
+        // convex shoulders sit at the plateau lips, the concave fillet at the
+        // floor.
+        curv = -up * rrect_clip.p_mat.w * sin(w * TAU);
     } else {
         let dir = select(-1.0, 1.0, eff == MODE_BOSS);
         // The profile slope is carve_slope's family: smoothstep-derived