GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
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())