GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
feat: breadcrumbs, the font selector and menubar triggers wear the run's edge
The last widgets drawing a trough for their flush edge: a breadcrumb's
flush run, the font selector's field and a menubar's dropdown-look
triggers now draw a field that is all run, as dropdowns and buttons do.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CLAUDE.md | 10 +++++++---
src/widget/container/breadcrumb.rs | 34 +++++++++++++++++++++++++++++++---
src/widget/container/menu.rs | 6 +++---
src/widget/input/font_selector.rs | 9 ++++++---
4 files changed, 47 insertions(+), 12 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index 5199f25..3423e65 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -350,9 +350,13 @@ answers with its inset plate), and the pane's dropdown rows, now in `fields` (th
`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. Breadcrumbs, the font selector and the
-other `inset_plate` callers still wear the trough. `a_dropdown_trigger_wears_the_runs_edge`
-and `focus_lights_the_plate_rim` are the tests.
+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.
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/widget/container/breadcrumb.rs b/src/widget/container/breadcrumb.rs
index f3e2c45..37a52ac 100644
--- a/src/widget/container/breadcrumb.rs
+++ b/src/widget/container/breadcrumb.rs
@@ -52,8 +52,9 @@ pub struct Breadcrumb {
pub right_clicked_seg: Option<usize>,
pub network_opacity: f32,
/// Relief stance of the segment run. `false` (the default) is the
- /// dropdown-mirror trough: the run sits flush, sunk into the surface
- /// behind a valley seam. `true` swaps the trough for a boss — the same
+ /// 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
+ /// 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
/// than sitting inset into a toolbar. Flat (non-relief) styling and the
@@ -360,7 +361,8 @@ 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_tint(self.focused.then(crate::widget::ControlPlate::focus_tint))
+ .with_run_edge(stance == crate::widget::PlateStance::Flush),
);
for (a, b) in self.seams(rect) {
ctx.groove(a, b, Self::SEAM_WIDTH, depth, run_rect);
@@ -608,6 +610,32 @@ mod tests {
s.x + s.w / 2.0
}
+ /// 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.
+ #[test]
+ fn a_breadcrumb_and_a_font_selector_wear_the_runs_edge() {
+ use crate::scene::paint::{PaintCtx, Prim};
+ if !crate::layout::control_relief() {
+ return;
+ }
+ let all_run = |items: Vec<crate::scene::paint::PaintItem>| {
+ assert!(!items.iter().any(|i| matches!(i.prim, Prim::Trough { .. })), "no trough");
+ items.iter().any(|i| matches!(i.prim, Prim::Field { rect, split, .. } if split < rect.x - 100.0))
+ };
+ let rect = Rect { x: 10.0, y: 20.0, width: 300.0, height: 24.0 };
+ let mut b = Breadcrumb::new();
+ b.set_path(&["home".to_string(), "lsgalante".to_string()]);
+ b.set_rect(rect.x, rect.y, rect.width, rect.height);
+ let mut pc = PaintCtx::new();
+ Paint::paint(b.inner(), rect, &mut pc);
+ assert!(all_run(pc.finish().items), "a flush breadcrumb run");
+ let f = crate::widget::input::FontSelector::new("Sans".to_string());
+ let mut pc = PaintCtx::new();
+ Paint::paint(f.inner(), rect, &mut pc);
+ assert!(all_run(pc.finish().items), "a font selector");
+ }
+
#[test]
fn test_breadcrumb_clicks() {
let mut breadcrumb = Breadcrumb::new();
diff --git a/src/widget/container/menu.rs b/src/widget/container/menu.rs
index 2934b7f..1e544e0 100644
--- a/src/widget/container/menu.rs
+++ b/src/widget/container/menu.rs
@@ -505,8 +505,8 @@ impl Paint for MenuBar {
// Dropdown-trigger chrome shared by the context title and the menu
// buttons on horizontal bars: the DE-wide closed-dropdown look — a
- // flush inset trough with a transparent face (Dropdown::
- // paint_background's raised path) carved on a band-inset rect, with
+ // flush plate with a field run's edge and a transparent face
+ // (Dropdown::paint_background's raised path) on a band-inset rect, with
// the state fill rounded to sit inside it. The vertical and curved
// modes keep their plain quads — their geometry is exotic and gets no
// trough.
@@ -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.inset_plate(trough, radii, None, depth);
+ ctx.flush_run(trough, radii, None, depth, None);
if let Some(c) = fill {
ctx.rounded_rect(trough, radius, (true, true, true, true), c);
}
diff --git a/src/widget/input/font_selector.rs b/src/widget/input/font_selector.rs
index b5d2d16..c3621cb 100644
--- a/src/widget/input/font_selector.rs
+++ b/src/widget/input/font_selector.rs
@@ -16,8 +16,8 @@ pub struct FontSelector {
pressed: bool,
hovered: bool,
child: Arc<Mutex<Option<std::process::Child>>>,
- /// Raised style, the closed Dropdown's: the field is a flush inset trough
- /// with a transparent face (the plate shows through), the hover and press
+ /// 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
/// states a wash inside it. Defaults to `control_relief()`; the flat style
/// keeps the framed dark field.
raised: Option<bool>,
@@ -172,7 +172,10 @@ 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)),
+ .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),
);
let wash = if self.pressed {
Some(colors::button_press_color())