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

commit4b52ba6583d33c3f13f3807a3207ea1a246c5796
parent58af8bcb54
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-02 11:07
feat: every flush control wears the run's edge

PaintCtx::inset_plate draws its face over an all-run Field rather than a
trough, so every flush plate -- the toolkit's buttons, triggers,
breadcrumbs and font selector, and the apps' own (calendar, cloud, files,
system interface) -- has the edge the joined wells' runs have. The
per-widget opt-in (ControlPlate::with_run_edge, flush_run) is gone with
nothing left to opt out of.

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

 CLAUDE.md                             | 31 +++++++--------
 src/backend/window_runner.rs          |  2 +-
 src/layout.rs                         | 19 ++++-----
 src/scene/paint.rs                    | 72 +++++++++++++++++------------------
 src/widget/container/breadcrumb.rs    |  7 ++--
 src/widget/container/menu.rs          |  2 +-
 src/widget/container/parameters_bg.rs |  4 +-
 src/widget/input/button.rs            |  7 +---
 src/widget/input/dropdown.rs          |  5 +--
 src/widget/input/font_selector.rs     |  7 +---
 10 files changed, 70 insertions(+), 86 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index eafea34..e9a85d1 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -354,21 +354,22 @@ short of its picker accordingly, so its text stops at the well. Never grouped in
 plate (its profile is not a monotonic step); the overlay's host-box slot carries `split`.
 The legacy banded path and flat hosts (`layout.rs`'s bridge) draw the two-box form.
 
-**A dropdown trigger wears the run's edge too** (the same day): a field that is all run,
-its seam `FIELD_RUN_ONLY` px to its left (`PaintCtx::flush_run`), in place of
-`Prim::Trough` — `ControlPlate::with_run_edge`, which the Dropdown's raised trigger sets,
-the expanded menu's plate (`RenderTarget::flush_run`, which a host without the prim
-answers with its inset plate), and the pane's dropdown rows, now in `fields` (the pane's
-`troughs()` list went: nothing in the pane draws a trough any more). **So does every
-button's flush plate** (`Button::plate` sets the run edge; the pane's button rows are
-fields too, where they had been a raised boss the button itself never drew). So every
-flush control in a parameter pane has one edge. **And so do the rest** (2026-10-02): a
-breadcrumb's flush run, the font selector's field and a menubar's dropdown-look triggers
-(`menu.rs`, `flush_run` in place of `inset_plate`). No widget draws a trough for its
-own edge now; `PaintCtx::inset_plate` and `Prim::Trough` remain for callers outside the
-toolkit's widgets and for `cce-relief`'s preview. `a_dropdown_trigger_wears_the_runs_edge`,
-`focus_lights_the_plate_rim` and `a_breadcrumb_and_a_font_selector_wear_the_runs_edge` are
-the tests.
+**Every flush control wears the run's edge** (since 2026-10-02): `PaintCtx::inset_plate`
+— the one flush control plate, which `ControlPlate`'s Flush stance, every widget and the
+apps' own plates all come through — draws a face and a field that is all run (its seam
+`FIELD_RUN_ONLY` px to its left), where it drew a `Prim::Trough`, whose outer half is a
+compressed copy of a step and read differently from a well beside it. So a dropdown
+trigger and its grown menu, a button, a breadcrumb's run, the font selector, a menubar's
+triggers, and the calendar's, cce-cloud's, cce-files' and the system interface's own
+plates have the edge of the run at the end of a text row's field. It went in a widget at
+a time the day before (`ControlPlate::with_run_edge`, `PaintCtx::flush_run`), both gone
+now that the plate itself has it. In a parameter pane the dropdown and button rows are
+fields too (`fields`; the pane's `troughs()` list went, and its button rows had been a
+raised boss the button never drew). The flat-host bridge pairs a plate's face with its
+field as it did with its trough. `Prim::Trough` / `PaintCtx::trough` stay for an
+explicit valley and `cce-relief`'s preview. `a_dropdown_trigger_wears_the_runs_edge`,
+`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.
 
 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`
diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index f2ea8dd..9fd99c5 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -2558,7 +2558,7 @@ pub fn tessellate_display_list(
                 }
                 let rx = split.max(rect.x);
                 let rw = (rect.x + rect.width - rx).max(0.0);
-                // All run (`PaintCtx::flush_run`): its own left corners.
+                // 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(
diff --git a/src/layout.rs b/src/layout.rs
index 061489f..72c3bae 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -4119,15 +4119,6 @@ pub trait RenderTarget {
     fn inset_plate_tinted(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, radius: f32, depth: f32, _tint: [f32; 3]) {
         self.inset_plate(color, x, y, w, h, radius, depth);
     }
-    /// A flush plate with a field run's edge
-    /// ([`crate::scene::paint::PaintCtx::flush_run`]) — the dropdown's grown
-    /// trigger. Hosts without the field prim draw the inset plate.
-    fn flush_run(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, radius: f32, depth: f32, tint: Option<[f32; 3]>) {
-        match tint {
-            Some(t) => self.inset_plate_tinted(color, x, y, w, h, radius, depth, t),
-            None => self.inset_plate(color, x, y, w, h, radius, depth),
-        }
-    }
     /// One step carve from a widget's `paint` ([`ReliefCarve`]) — offered here
     /// for the same reason as `inset_plate`: the legacy `all_quads` stream
     /// carries no relief prims, so a flat-path host never sees them.
@@ -4280,6 +4271,8 @@ pub fn render_widget<T: WidgetHost + 'static>(pc: &mut dyn RenderTarget, w: &mut
         use crate::scene::paint::Prim;
         let trough_for_face = match (&item.prim, &pending_face) {
             (Prim::Trough { rect, .. }, Some((face_rect, _, _))) => same_rect(*rect, *face_rect),
+            // `inset_plate`'s edge: a field that is all run.
+            (Prim::Field { rect, split, .. }, Some((face_rect, _, _))) => *split <= rect.x && same_rect(*rect, *face_rect),
             _ => false,
         };
         if !trough_for_face {
@@ -4355,7 +4348,9 @@ pub fn render_widget<T: WidgetHost + 'static>(pc: &mut dyn RenderTarget, w: &mut
             // `Prim::Field` replaced — the well to the seam, the run's flush
             // plate past it.
             Prim::Field { rect, radii, depth, split, tint } => {
-                // All run and no well (`PaintCtx::flush_run`): the plate alone.
+                // 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]);
                 if split > rect.x {
                     pc.relief_carve(&ReliefCarve {
                         kind: CarveKind::Recess { tint },
@@ -4372,8 +4367,8 @@ pub fn render_widget<T: WidgetHost + 'static>(pc: &mut dyn RenderTarget, w: &mut
                 let rx = split.max(rect.x);
                 let rw = (rect.x + rect.width - rx).max(0.0);
                 match tint {
-                    Some(t) => pc.inset_plate_tinted([0.0; 4], rx, rect.y, rw, rect.height, r, depth, t),
-                    None => pc.inset_plate([0.0; 4], rx, rect.y, rw, rect.height, r, depth),
+                    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 dcb3233..c709792 100644
--- a/src/scene/paint.rs
+++ b/src/scene/paint.rs
@@ -218,7 +218,7 @@ pub enum PlateStance {
 }
 
 /// How far left of a [`Prim::Field`] its seam is put to make it all run and
-/// no well ([`PaintCtx::flush_run`]): far enough that the blend across the
+/// no well ([`PaintCtx::inset_plate`]): far enough that the blend across the
 /// seam and the seam's own wall land nowhere near the field.
 pub const FIELD_RUN_ONLY: f32 = 1.0e4;
 
@@ -249,12 +249,6 @@ pub struct ControlPlate {
     /// ring, drawn on the plate's own relief rather than as extra geometry.
     /// `None` untinted.
     pub tint: Option<[f32; 3]>,
-    /// A flush plate's edge in the profile a field's run wears
-    /// ([`PaintCtx::flush_run`]) rather than [`Prim::Trough`]'s: the outer
-    /// half a well's own fall, the inner half mirrored back up to the face.
-    /// The dropdown trigger sets it, so its edge matches the run at the end
-    /// of a text row's field beside it.
-    pub run_edge: bool,
 }
 
 impl ControlPlate {
@@ -262,14 +256,9 @@ impl ControlPlate {
     /// the DE relief width, capped at a fifth of the plate's height.
     pub fn control(rect: Rect, radius: f32, stance: PlateStance, face: Option<Material>) -> Self {
         let depth = crate::layout::bevel_width().min(rect.height * 0.2);
-        Self { rect, radii: (radius, radius, radius, radius), stance, face, depth, tint: None, run_edge: false }
+        Self { rect, radii: (radius, radius, radius, radius), stance, face, depth, tint: None }
     }
 
-    /// [`Self::run_edge`].
-    pub fn with_run_edge(mut self, on: bool) -> Self {
-        self.run_edge = on;
-        self
-    }
 
     /// Light the rim — the focus ring on the plate's silhouette. Pass the
     /// highlight colour while the control holds keyboard focus, `None` otherwise.
@@ -1302,10 +1291,6 @@ impl PaintCtx {
             }
             PlateStance::Flush => {
                 let (trough, radii) = crate::layout::carve_inside(plate.rect, plate.radii, plate.depth);
-                if plate.run_edge {
-                    self.flush_run(trough, radii, plate.faced(), plate.depth, plate.tint);
-                    return;
-                }
                 match plate.tint {
                     Some(t) => self.inset_plate_tinted(trough, radii, plate.faced(), plate.depth, t),
                     None => self.inset_plate(trough, radii, plate.faced(), plate.depth),
@@ -1442,21 +1427,17 @@ impl PaintCtx {
             // `Prim::RoundedRect`'s single radius cannot.
             self.border(rect, radii, face.fill(PlateRole::Nested), [0.0; 4], 0.0);
         }
-        self.trough(rect, radii, depth);
-    }
-
-    /// A flush plate whose edge is a field's RUN ([`Prim::Field`] with no
-    /// well — its seam put [`FIELD_RUN_ONLY`] px to the left of it): the
-    /// outer half of the edge a well's fall, the inner half that fall
-    /// mirrored back up to the face. [`inset_plate`](Self::inset_plate) with
-    /// that edge in place of [`Prim::Trough`]'s, whose outer half is a
-    /// compressed copy of a step — so a dropdown trigger's edge matches the
-    /// run at the end of a text row's field. `tint` lights it.
-    pub fn flush_run(&mut self, rect: Rect, radii: Radii, face: Option<&Material>, depth: f32, tint: Option<[f32; 3]>) {
-        if let Some(face) = face.filter(|m| m.tint[3] > 0.001) {
-            self.border(rect, radii, face.fill(PlateRole::Nested), [0.0; 4], 0.0);
-        }
-        self.field(rect, radii, depth, rect.x - FIELD_RUN_ONLY, tint);
+        // The edge is a field's RUN (a field with no well, its seam put
+        // [`FIELD_RUN_ONLY`] px to the left): the outer half a well's own
+        // fall, the inner half that fall mirrored back up to the face — so
+        // every flush control has the edge of the run at the end of a text
+        // row's field, and the well beside it. Until 2026-10-02 this was a
+        // [`Prim::Trough`], whose outer half is a compressed copy of a step;
+        // the dropdown, button, breadcrumb, font selector and menubar
+        // triggers had been switched to the run's edge one by one the day
+        // before, and the apps' own flush plates (the calendar's, cce-cloud's,
+        // cce-files', the system interface's) kept the trough until here.
+        self.field(rect, radii, depth, rect.x - FIELD_RUN_ONLY, None);
     }
 
     /// [`inset_plate`](Self::inset_plate) with the rim lit — the focused flush
@@ -1466,7 +1447,7 @@ impl PaintCtx {
         if let Some(face) = face.filter(|m| m.tint[3] > 0.001) {
             self.border(rect, radii, face.fill(PlateRole::Nested), [0.0; 4], 0.0);
         }
-        self.trough_tinted(rect, radii, depth, tint);
+        self.field(rect, radii, depth, rect.x - FIELD_RUN_ONLY, Some(tint));
     }
 
     /// A canvas well's floor — the opening you look into or draw in (a
@@ -1879,9 +1860,6 @@ impl crate::layout::RenderTarget for PaintCtx {
     fn inset_plate(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, radius: f32, depth: f32) {
         PaintCtx::inset_plate(self, Rect { x, y, width: w, height: h }, (radius, radius, radius, radius), Material::face(color).as_ref(), depth);
     }
-    fn flush_run(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, radius: f32, depth: f32, tint: Option<[f32; 3]>) {
-        PaintCtx::flush_run(self, Rect { x, y, width: w, height: h }, (radius, radius, radius, radius), Material::face(color).as_ref(), depth, tint);
-    }
     fn inset_plate_tinted(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, radius: f32, depth: f32, tint: [f32; 3]) {
         PaintCtx::inset_plate_tinted(self, Rect { x, y, width: w, height: h }, (radius, radius, radius, radius), Material::face(color).as_ref(), depth, tint);
     }
@@ -1893,6 +1871,28 @@ impl crate::layout::RenderTarget for PaintCtx {
 #[cfg(test)]
 mod tests {
     use super::*;
+
+    /// The one flush control plate draws a face and a field that is all
+    /// run — the edge every flush control wears — and no trough.
+    #[test]
+    fn an_inset_plate_is_a_field_that_is_all_run() {
+        let rect = Rect { x: 10.0, y: 20.0, width: 120.0, height: 24.0 };
+        let face = Material::face([0.2, 0.2, 0.25, 1.0]);
+        for tint in [None, Some([1.0, 0.5, 0.0])] {
+            let mut pc = PaintCtx::new();
+            match tint {
+                Some(t) => pc.inset_plate_tinted(rect, (4.0, 4.0, 4.0, 4.0), face.as_ref(), 4.0, t),
+                None => pc.inset_plate(rect, (4.0, 4.0, 4.0, 4.0), face.as_ref(), 4.0),
+            }
+            let prims: Vec<Prim> = pc.finish().items.into_iter().map(|i| i.prim).collect();
+            assert!(!prims.iter().any(|p| matches!(p, Prim::Trough { .. })), "{prims:?}");
+            assert!(prims.iter().any(|p| matches!(p, Prim::Border { .. })), "the face");
+            assert!(
+                prims.iter().any(|p| matches!(p, Prim::Field { rect: r, split, tint: t, .. } if *r == rect && *split <= rect.x - 100.0 && *t == tint)),
+                "{prims:?}"
+            );
+        }
+    }
     use crate::scene::material::Frost;
 
     /// RFC Phase 7b: PlateSpec role mechanics — flag derivation from window
diff --git a/src/widget/container/breadcrumb.rs b/src/widget/container/breadcrumb.rs
index 37a52ac..19448cb 100644
--- a/src/widget/container/breadcrumb.rs
+++ b/src/widget/container/breadcrumb.rs
@@ -53,7 +53,7 @@ pub struct Breadcrumb {
     pub network_opacity: f32,
     /// Relief stance of the segment run. `false` (the default) is the
     /// dropdown's flush plate: the run's face level with the surface, its
-    /// edge a field run's (`ControlPlate::with_run_edge`), as the dropdown
+    /// edge a field run's (`PaintCtx::inset_plate`), as the dropdown
     /// trigger and the buttons wear. `true` swaps it for a boss — the same
     /// silhouette raised out of the surface, for hosts whose breadcrumb
     /// floats in front of its plate (the designer's network editor) rather
@@ -361,8 +361,7 @@ impl Paint for Breadcrumb {
                 ctx.control_plate(
                     &crate::widget::ControlPlate::control(run_rect, r, stance, face)
                         .with_depth(depth)
-                        .with_tint(self.focused.then(crate::widget::ControlPlate::focus_tint))
-                        .with_run_edge(stance == crate::widget::PlateStance::Flush),
+                        .with_tint(self.focused.then(crate::widget::ControlPlate::focus_tint)),
                 );
                 for (a, b) in self.seams(rect) {
                     ctx.groove(a, b, Self::SEAM_WIDTH, depth, run_rect);
@@ -612,7 +611,7 @@ mod tests {
 
     /// The flush controls that are not dropdowns or buttons wear their edge
     /// too: a breadcrumb's flush run and a font selector's field draw a
-    /// field that is all run (`ControlPlate::with_run_edge`), never a trough.
+    /// field that is all run (`PaintCtx::inset_plate`), never a trough.
     #[test]
     fn a_breadcrumb_and_a_font_selector_wear_the_runs_edge() {
         use crate::scene::paint::{PaintCtx, Prim};
diff --git a/src/widget/container/menu.rs b/src/widget/container/menu.rs
index 1e544e0..807b4c7 100644
--- a/src/widget/container/menu.rs
+++ b/src/widget/container/menu.rs
@@ -517,7 +517,7 @@ impl Paint for MenuBar {
             let radius = crate::layout::dropdown_corner_radius();
             let depth = crate::layout::bevel_width().min(trough_h * 0.2);
             let (trough, radii) = crate::layout::carve_inside(trough, (radius, radius, radius, radius), depth);
-            ctx.flush_run(trough, radii, None, depth, None);
+            ctx.inset_plate(trough, radii, None, depth);
             if let Some(c) = fill {
                 ctx.rounded_rect(trough, radius, (true, true, true, true), c);
             }
diff --git a/src/widget/container/parameters_bg.rs b/src/widget/container/parameters_bg.rs
index ca00376..eb6ef6f 100644
--- a/src/widget/container/parameters_bg.rs
+++ b/src/widget/container/parameters_bg.rs
@@ -1860,7 +1860,7 @@ impl ParametersBg {
                 }
             } else if p.2 == "button" {
                 // A button: a field that is all run, as its own flush plate
-                // (`Button::plate`, `ControlPlate::with_run_edge`) on its
+                // (`Button::plate`, `PaintCtx::inset_plate`) on its
                 // band, at the button radius.
                 if let Some(b) = &self.buttons[i] {
                     let (x, y, w, h) = b.rect();
@@ -1873,7 +1873,7 @@ impl ParametersBg {
                 }
             } else if p.2.starts_with("choice") {
                 // The dropdown trigger: a field that is all run — the
-                // widget's own raised paint (`ControlPlate::with_run_edge`)
+                // widget's own raised paint (`PaintCtx::inset_plate`)
                 // on the same band, radius and depth cap. Until 2026-10-01
                 // a trough, whose edge differed from the run's at the end of
                 // a text row's field.
diff --git a/src/widget/input/button.rs b/src/widget/input/button.rs
index 261e73b..3d76745 100644
--- a/src/widget/input/button.rs
+++ b/src/widget/input/button.rs
@@ -293,14 +293,9 @@ impl Button {
         let radius = crate::layout::button_corner_radius();
         // Keyboard focus lights the plate's own rim — the ring IS the silhouette.
         let tint = self.focused.then(crate::widget::ControlPlate::focus_tint);
-        // A flush plate wears a field run's edge (`ControlPlate::with_run_edge`),
-        // as the dropdown trigger does: one edge for every flush control
-        // beside the wells and fields of a pane, where a trough's outer half
-        // read as a compressed copy of a well's.
         Some(
             crate::widget::ControlPlate::control(rect, radius, stance, crate::scene::Material::face(self.color()))
-                .with_tint(tint)
-                .with_run_edge(stance == crate::widget::PlateStance::Flush),
+                .with_tint(tint),
         )
     }
 
diff --git a/src/widget/input/dropdown.rs b/src/widget/input/dropdown.rs
index b00e51e..5f98926 100644
--- a/src/widget/input/dropdown.rs
+++ b/src/widget/input/dropdown.rs
@@ -650,9 +650,6 @@ impl Dropdown {
             )
             .with_radii((r4[0], r4[1], r4[2], r4[3]))
             .with_depth(depth)
-            // The edge a field's run wears, not a trough's: the same edge
-            // as the picker at the end of a text row beside it.
-            .with_run_edge(true)
             .with_tint(self.focused.then(crate::widget::ControlPlate::focus_tint));
             ctx.control_plate(&plate);
             return;
@@ -1085,7 +1082,7 @@ impl Paint for Dropdown {
                 (radius, radius, radius, radius),
                 depth,
             );
-            pc.flush_run(face, t.x, t.y, t.width, t.height, tr.0, depth, None);
+            pc.inset_plate(face, t.x, t.y, t.width, t.height, tr.0, depth);
         } else {
             pc.rect_with_radius(self.border_color(), ux, uy, uw, uh, radius);
             pc.rect_with_radius(face, ux + 1.0, uy + 1.0, uw - 2.0, uh - 2.0, (radius - 1.0).max(0.0));
diff --git a/src/widget/input/font_selector.rs b/src/widget/input/font_selector.rs
index c3621cb..f9fec27 100644
--- a/src/widget/input/font_selector.rs
+++ b/src/widget/input/font_selector.rs
@@ -17,7 +17,7 @@ pub struct FontSelector {
     hovered: bool,
     child: Arc<Mutex<Option<std::process::Child>>>,
     /// Raised style, the closed Dropdown's: the field is a flush plate with a
-    /// field run's edge (`ControlPlate::with_run_edge`) and a transparent face (the plate shows through), the hover and press
+    /// field run's edge (`PaintCtx::inset_plate`) and a transparent face (the plate shows through), the hover and press
     /// states a wash inside it. Defaults to `control_relief()`; the flat style
     /// keeps the framed dark field.
     raised: Option<bool>,
@@ -172,10 +172,7 @@ impl Paint for FontSelector {
             // transparent face, the state fill rounded to sit inside it.
             ctx.control_plate(
                 &crate::widget::ControlPlate::control(rect, r, crate::widget::PlateStance::Flush, None)
-                    .with_tint(self.focused.then(crate::widget::ControlPlate::focus_tint))
-                    // The edge every flush control wears: a field run's, as
-                    // the dropdown trigger and the buttons do.
-                    .with_run_edge(true),
+                    .with_tint(self.focused.then(crate::widget::ControlPlate::focus_tint)),
             );
             let wash = if self.pressed {
                 Some(colors::button_press_color())