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

commit080c6d4eb2da1c489802f7b7dedf7a5da8fc57ce
parent3cdc9f6e3f
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-02 13:21
Revert "fix: a text box is a field that is all well"

This reverts commit 6634ba3, except its unrelated cleanup in
dropdown.rs (an unused binding), which stays.

6634ba3 drew a plain text box as an all-well field so it would not be a
recess grouped into its plate, because grouped recesses drew a doubled
outline. 51d5f25 fixed that in the plate shader, and 3cdc9f6 holds a
grouped carve to its overlay pixel for pixel, so the workaround has
nothing left to work around. A text box is a Prim::Recess again,
grouped when its plate's grouping window is open. In the designer, with
the user's config, its text wells group (3 of 3) and differ from the
well_field rendering by at most 1/255, the 8-bit rounding between
in-shader and hardware blending. NVIDIA and Intel agree to 1/255.
PaintCtx::well_field and field_well_only go with it; nothing outside
cce-ui used them. CLAUDE.md keeps a note of the episode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 CLAUDE.md                             | 17 +++++--------
 src/backend/window_runner.rs          | 48 ++++++++++++++---------------------
 src/layout.rs                         | 29 +++++----------------
 src/scene/paint.rs                    | 16 ------------
 src/widget/container/parameters_bg.rs | 41 ++++++++----------------------
 src/widget/input/text_box.rs          | 29 +++------------------
 6 files changed, 47 insertions(+), 133 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index d636499..af2e0b5 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -382,15 +382,12 @@ explicit valley and `cce-relief`'s preview. `a_dropdown_trigger_wears_the_runs_e
 `focus_lights_the_plate_rim`, `a_breadcrumb_and_a_font_selector_wear_the_runs_edge` and
 `an_inset_plate_is_a_field_that_is_all_run` are the tests.
 
-**A text box is a field that is all well** (since 2026-10-02): `PaintCtx::well_field`, a
-`Prim::Field` whose seam is `FIELD_RUN_ONLY` px past its right end (`field_well_only`),
-in `TextBox`'s own paint and in `ParametersBg::fields` for every text row without a
-picker. It was a `Prim::Recess`, which the runner GROUPS into a live host plate as a CSG
-feature, and a grouped recess drew a doubled outline where the field beside it — never
-grouped, its own overlay — drew the single edge. That was first put down to the NVIDIA
-device; it was not (see "A grouped carve shades as its overlay does" below, which fixed
-the cause the same day, so the two now agree whichever is used). Both fallbacks draw an
-all-well field as one plain recess. `a_text_box_is_a_field_that_is_all_well` is the test.
+**A plain text box is a `Prim::Recess`**, grouped into the plate under it when the
+plate's grouping window is open. For a few hours on 2026-10-02 it was a field that was
+all well (`PaintCtx::well_field`, 6634ba3), to dodge a doubled outline that grouped
+recesses drew; the cause was fixed in the plate shader the same day (see "A grouped carve
+shades as its overlay does" below) and the workaround reverted, since a grouped recess
+and an overlaid one now draw the same pixels.
 
 The pieces that feed it: `ParametersBg::fields` (the pane's list, drawn after its troughs,
 hover-tinted like them; textpick rows and spinboxes with a run are in neither `reliefs`
@@ -433,7 +430,7 @@ overlay drew one soft edge. Two causes, both in how the plate path applied the c
 Measured with `examples/grouped_recess_probe.rs` (a plate whose recesses group beside the
 same recesses forced to overlay by a transparent quad): grouped and overlay columns
 5,462 px apart before, 0 after; in the designer with grouped text wells, 0 px from the
-`well_field` rendering.
+`well_field` rendering the text boxes then had (since reverted, above).
 
 **And it is tested by rendering, not by reading** (`vk::plate_probe`, `cfg(test)`): an
 offscreen 2D render — a `DisplayList` through the runner's own `tessellate_display_list`
diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index 41d1a3b..9fd99c5 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -2549,40 +2549,30 @@ pub fn tessellate_display_list(
                 // has no blended outline, and the legacy path exists only for
                 // A/B comparison.
                 let all = (true, true, true, true);
-                // All well (`PaintCtx::well_field`): the plain step, its own
-                // corners, and no run.
-                if *split >= rect.x + rect.width {
+                if *split > rect.x {
                     push_bevel_edge_vertices_banded(
-                        rect.x, rect.y, rect.width, rect.height, *radii, *depth,
+                        rect.x, rect.y, *split - rect.x, rect.height, (radii.0, 0.0, 0.0, radii.3), *depth,
                         sw, sh, [0.0; 4], no, -1.0, default_bevel_bands(*depth), all,
                         EdgeKind::Step, &mut verts,
                     );
-                } else {
-                    if *split > rect.x {
-                        push_bevel_edge_vertices_banded(
-                            rect.x, rect.y, *split - rect.x, rect.height, (radii.0, 0.0, 0.0, radii.3), *depth,
-                            sw, sh, [0.0; 4], no, -1.0, default_bevel_bands(*depth), all,
-                            EdgeKind::Step, &mut verts,
-                        );
-                    }
-                    let rx = split.max(rect.x);
-                    let rw = (rect.x + rect.width - rx).max(0.0);
-                    // All run (`PaintCtx::inset_plate`'s edge): its own left corners.
-                    let (l0, l3) = if *split > rect.x { (0.0, 0.0) } else { (radii.0, radii.3) };
-                    let half = *depth * 0.5;
-                    push_bevel_edge_vertices_banded(
-                        rx, rect.y, rw, rect.height, (l0, radii.1, radii.2, l3), half,
-                        sw, sh, [0.0; 4], no, -1.0, default_bevel_bands(half), all,
-                        EdgeKind::Step, &mut verts,
-                    );
-                    let ir = (radii.1 - half).max(0.0);
-                    let il = if *split > rect.x { 0.0 } else { (radii.0 - half).max(0.0) };
-                    push_bevel_edge_vertices_banded(
-                        rx + half, rect.y + half, rw - *depth, rect.height - *depth, (il, ir, ir, il), half,
-                        sw, sh, [0.0; 4], no, 1.0, default_bevel_bands(half), all,
-                        EdgeKind::Step, &mut verts,
-                    );
                 }
+                let rx = split.max(rect.x);
+                let rw = (rect.x + rect.width - rx).max(0.0);
+                // All run (`PaintCtx::inset_plate`'s edge): its own left corners.
+                let (l0, l3) = if *split > rect.x { (0.0, 0.0) } else { (radii.0, radii.3) };
+                let half = *depth * 0.5;
+                push_bevel_edge_vertices_banded(
+                    rx, rect.y, rw, rect.height, (l0, radii.1, radii.2, l3), half,
+                    sw, sh, [0.0; 4], no, -1.0, default_bevel_bands(half), all,
+                    EdgeKind::Step, &mut verts,
+                );
+                let ir = (radii.1 - half).max(0.0);
+                let il = if *split > rect.x { 0.0 } else { (radii.0 - half).max(0.0) };
+                push_bevel_edge_vertices_banded(
+                    rx + half, rect.y + half, rw - *depth, rect.height - *depth, (il, ir, ir, il), half,
+                    sw, sh, [0.0; 4], no, 1.0, default_bevel_bands(half), all,
+                    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/layout.rs b/src/layout.rs
index 3d97d52..72c3bae 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -4351,20 +4351,7 @@ pub fn render_widget<T: WidgetHost + 'static>(pc: &mut dyn RenderTarget, w: &mut
                 // All run and no well (`PaintCtx::inset_plate`'s edge): the
                 // plate alone, with the face that came before it.
                 let face = if split <= rect.x { pending_face.take().map(|(_, _, fill)| fill) } else { None }.unwrap_or([0.0; 4]);
-                // All well and no run (`PaintCtx::well_field`, a text box):
-                // the plain recess, its own corners.
-                if split >= rect.x + rect.width {
-                    pc.relief_carve(&ReliefCarve {
-                        kind: CarveKind::Recess { tint },
-                        x: rect.x,
-                        y: rect.y,
-                        w: rect.width,
-                        h: rect.height,
-                        radii,
-                        depth,
-                        edges: (true, true, true, true),
-                    });
-                } else if split > rect.x {
+                if split > rect.x {
                     pc.relief_carve(&ReliefCarve {
                         kind: CarveKind::Recess { tint },
                         x: rect.x,
@@ -4376,14 +4363,12 @@ pub fn render_widget<T: WidgetHost + 'static>(pc: &mut dyn RenderTarget, w: &mut
                         edges: (true, true, true, true),
                     });
                 }
-                if split < rect.x + rect.width {
-                    let r = radii.1.max(radii.2);
-                    let rx = split.max(rect.x);
-                    let rw = (rect.x + rect.width - rx).max(0.0);
-                    match tint {
-                        Some(t) => pc.inset_plate_tinted(face, rx, rect.y, rw, rect.height, r, depth, t),
-                        None => pc.inset_plate(face, rx, rect.y, rw, rect.height, r, depth),
-                    }
+                let r = radii.1.max(radii.2);
+                let rx = split.max(rect.x);
+                let rw = (rect.x + rect.width - rx).max(0.0);
+                match tint {
+                    Some(t) => pc.inset_plate_tinted(face, rx, rect.y, rw, rect.height, r, depth, t),
+                    None => pc.inset_plate(face, rx, rect.y, rw, rect.height, r, depth),
                 }
             }
             Prim::Trough { rect, radii, depth, tint, .. } => {
diff --git a/src/scene/paint.rs b/src/scene/paint.rs
index 848a08f..c709792 100644
--- a/src/scene/paint.rs
+++ b/src/scene/paint.rs
@@ -222,12 +222,6 @@ pub enum PlateStance {
 /// seam and the seam's own wall land nowhere near the field.
 pub const FIELD_RUN_ONLY: f32 = 1.0e4;
 
-/// The seam of a field that is all well and no run ([`PaintCtx::well_field`]):
-/// this far past the field's right end.
-pub fn field_well_only(rect: Rect) -> f32 {
-    rect.x + rect.width + FIELD_RUN_ONLY
-}
-
 /// A control plate: the thing you press, at the control rung of the plate
 /// ladder. One description for every control face — Button, Dropdown,
 /// FontSelector, Breadcrumb, a ButtonStrip's selected plateau — so their
@@ -1572,16 +1566,6 @@ impl PaintCtx {
         self.push(Prim::Trough { rect, radii, depth, edges, tint: Some(tint) });
     }
 
-    /// A sunken well and nothing else: a [`Prim::Field`] that is all well,
-    /// its seam [`FIELD_RUN_ONLY`] px past its right end — the well a text
-    /// box is. It shades as a field's well does, an overlay of its own
-    /// rather than a feature grouped into the plate under it, which is the
-    /// look a textpick row's well had beside a plain text box's (since
-    /// 2026-10-02; a plain box was a `Prim::Recess`, grouped).
-    pub fn well_field(&mut self, rect: Rect, radii: Radii, depth: f32, tint: Option<[f32; 3]>) {
-        self.field(rect, radii, depth, field_well_only(rect), tint);
-    }
-
     /// A sunken well ending in a flush run at `split` — see [`Prim::Field`].
     /// `tint` lights its rim (the focus and hover treatment).
     pub fn field(&mut self, rect: Rect, radii: Radii, depth: f32, split: f32, tint: Option<[f32; 3]>) {
diff --git a/src/widget/container/parameters_bg.rs b/src/widget/container/parameters_bg.rs
index 635a64d..36cb9ef 100644
--- a/src/widget/container/parameters_bg.rs
+++ b/src/widget/container/parameters_bg.rs
@@ -1732,9 +1732,9 @@ impl ParametersBg {
             }
             // (control, its configured corner radius, raised vs recessed)
             let ctl: Option<(&dyn WidgetHost, f32, bool)> = if is_text_row(&p.2) {
-                // A text row is a field ([`Self::fields`]): with its picker a
-                // well ending in a flush run, without one all well.
-                None
+                // The textpick picker is NOT in this list: with it the row is
+                // a field ([`Self::fields`]) — a well ending in a flush run.
+                self.texts[i].as_ref().map(|w| (w as &dyn WidgetHost, crate::layout::textbox_corner_radius(), false))
             } else if p.2.starts_with("choice") {
                 // The dropdown trigger is a FLUSH control, a field that is all
                 // run ([`Self::fields`]). A boss here read as a raised island
@@ -1817,6 +1817,11 @@ impl ParametersBg {
                 // The top-label band stays outside the relief like every other host.
                 let ty = w.label_strip();
                 let depth = crate::layout::bevel_width().min((h - ty) * 0.2);
+                // A text box joined to its picker is half of a field
+                // ([`Self::fields`]), drawn there.
+                if is_text_row(&p.2) && self.texts[i].as_ref().is_some_and(|t| t.inner().joined_right) {
+                    continue;
+                }
                 out.push((x, y + ty, ww, h - ty, r4(radius), depth, raised, all));
             }
         }
@@ -1843,24 +1848,7 @@ impl ParametersBg {
             if hidden[i] {
                 continue;
             }
-            let joined = self.texts[i].as_ref().is_some_and(|t| t.inner().joined_right);
-            if is_text_row(&p.2) && !joined {
-                // A plain text row (a textpick with no candidates included):
-                // a field that is all well, so it shades as the well of a
-                // textpick row beside it does — an overlay of its own, where
-                // as a recess in [`Self::reliefs`] it was grouped into the
-                // pane's plate and read flatter (until 2026-10-02).
-                if let Some(tb) = &self.texts[i] {
-                    let (x, y, w, h) = tb.rect();
-                    let ty = tb.label_strip();
-                    if w > 0.0 && h - ty > 0.0 {
-                        let depth = crate::layout::bevel_width().min((h - ty) * 0.2);
-                        let r = crate::layout::textbox_corner_radius();
-                        let band = Rect { x, y: y + ty, width: w, height: h - ty };
-                        out.push((x, y + ty, w, h - ty, (r, r, r, r), depth, crate::scene::paint::field_well_only(band)));
-                    }
-                }
-            } else if p.2.starts_with("textpick") {
+            if p.2.starts_with("textpick") {
                 if let (Some(tb), Some(d)) = (&self.texts[i], &self.choices[i]) {
                     if !tb.inner().joined_right {
                         continue;
@@ -3922,17 +3910,8 @@ mod tests {
         // there.
         let rr = crate::layout::textbox_corner_radius();
         let fields = p.inner().fields();
-        assert_eq!(fields.len(), 3, "{fields:?}");
+        assert_eq!(fields.len(), 1, "{fields:?}");
         let (fx, fy, fw, fh, fradii, _, split) = fields[0];
-        // The plain row and the textpick with nothing to pick are fields
-        // too, all well: their seam past their right end.
-        for (i, f) in fields[1..].iter().enumerate() {
-            let (x, _, w, _, radii, _, s) = *f;
-            let (bx, _, bw, _) = p.texts[i + 1].as_ref().unwrap().rect();
-            assert_eq!((x, w), (bx, bw), "row {}'s field is its box", i + 1);
-            assert_eq!(radii, (rr, rr, rr, rr));
-            assert!(s >= x + w + 100.0, "all well: {s}");
-        }
         assert_eq!((fx, fy, fx + fw, fy + fh), (tx, dy, dx + dw, dy + dh), "the field spans box and picker");
         assert_eq!(fradii, (rr, rr, rr, rr), "its own radius at all four corners");
         assert_eq!(split, dx, "the seam is where the picker begins");
diff --git a/src/widget/input/text_box.rs b/src/widget/input/text_box.rs
index 50704db..1513e57 100644
--- a/src/widget/input/text_box.rs
+++ b/src/widget/input/text_box.rs
@@ -1568,9 +1568,10 @@ impl Paint for TextBox {
                 if let Some((well, radii, depth, tint)) = self.well() {
                     // Focus lights the well's rim in the highlight accent (with
                     // the shader's complementary shadow) — the TreeList treatment.
-                    // A field that is all well (`PaintCtx::well_field`), the
-                    // well a textpick row's box is half of.
-                    ctx.well_field(well, radii, depth, tint);
+                    match tint {
+                        Some(t) => ctx.recess_tinted(well, radii, depth, t),
+                        None => ctx.recess(well, radii, depth),
+                    }
                 }
             }
 
@@ -1825,28 +1826,6 @@ unsafe impl Sync for TextBox {}
 mod tests {
     use super::*;
 
-    /// A text box's well is a field that is all well — the well a textpick
-    /// row's box is half of — and not a recess, which grouped into the plate
-    /// under it and read flatter beside the textpick's.
-    #[test]
-    fn a_text_box_is_a_field_that_is_all_well() {
-        use crate::scene::paint::{PaintCtx, Prim};
-        let mut tb = TextBox::new("x".to_string()).with_recessed(true);
-        tb.set_rect(10.0, 10.0, 200.0, 26.0);
-        let Some((well, radii, depth, _)) = tb.well() else {
-            return; // a square-cornered config draws no well
-        };
-        let mut pc = PaintCtx::new();
-        Paint::paint(tb.inner(), Rect { x: 10.0, y: 10.0, width: 200.0, height: 26.0 }, &mut pc);
-        let prims: Vec<Prim> = pc.finish().items.into_iter().map(|i| i.prim).collect();
-        assert!(
-            prims.iter().any(|p| matches!(p, Prim::Field { rect, radii: r, depth: d, split, .. }
-                if *rect == well && *r == radii && *d == depth && *split >= rect.x + rect.width + 100.0)),
-            "{prims:?}"
-        );
-        assert!(!prims.iter().any(|p| matches!(p, Prim::Recess { .. })), "no recess of its own");
-    }
-
     /// The multiline caret/click math reads shaped per-line offsets; a caret
     /// must land exactly where it is drawn, on every column of every line.
     #[test]