GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
feat: a dropdown trigger wears the field run's edge
The trigger's flush plate was a trough, whose outer half is a compressed
copy of a step; beside a text row's field its edge read differently from
the picker's. It is a field that is all run now (PaintCtx::flush_run,
ControlPlate::with_run_edge), on the trigger, the expanded menu and the
parameter pane's dropdown rows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CLAUDE.md | 10 +++
src/backend/window_runner.rs | 23 +++---
src/layout.rs | 35 ++++++---
src/scene/paint.rs | 40 +++++++++-
src/widget/container/parameters_bg.rs | 144 ++++++++++++++--------------------
src/widget/input/dropdown.rs | 5 +-
6 files changed, 148 insertions(+), 109 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index a08a273..6aea560 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -342,6 +342,16 @@ nothing at the outline, where both sides stand at half the step. Never grouped i
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 every flush
+control in a parameter pane has one edge. Other flush controls — buttons, breadcrumbs,
+the font selector — still wear the trough. `a_dropdown_trigger_wears_the_runs_edge` is
+the test.
+
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`
nor `troughs`), `TextBox::joined_right` (the box stops at the seam), `Dropdown::set_radii`
diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index 722da17..f2ea8dd 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -2549,22 +2549,27 @@ 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);
- let lw = (*split - rect.x).max(0.0);
- push_bevel_edge_vertices_banded(
- rect.x, rect.y, lw, 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, rw) = (*split, (rect.x + rect.width - *split).max(0.0));
+ 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::flush_run`): 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, (0.0, radii.1, radii.2, 0.0), half,
+ 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, (0.0, ir, ir, 0.0), half,
+ 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,
);
diff --git a/src/layout.rs b/src/layout.rs
index faefeed..061489f 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -4119,6 +4119,15 @@ 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.
@@ -4346,18 +4355,22 @@ 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 } => {
- pc.relief_carve(&ReliefCarve {
- kind: CarveKind::Recess { tint },
- x: rect.x,
- y: rect.y,
- w: (split - rect.x).max(0.0),
- h: rect.height,
- radii: (radii.0, 0.0, 0.0, radii.3),
- depth,
- edges: (true, true, true, true),
- });
+ // All run and no well (`PaintCtx::flush_run`): the plate alone.
+ if split > rect.x {
+ pc.relief_carve(&ReliefCarve {
+ kind: CarveKind::Recess { tint },
+ x: rect.x,
+ y: rect.y,
+ w: split - rect.x,
+ h: rect.height,
+ radii: (radii.0, 0.0, 0.0, radii.3),
+ depth,
+ edges: (true, true, true, true),
+ });
+ }
let r = radii.1.max(radii.2);
- let (rx, rw) = (split, (rect.x + rect.width - split).max(0.0));
+ 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),
diff --git a/src/scene/paint.rs b/src/scene/paint.rs
index 0cb92e8..dcb3233 100644
--- a/src/scene/paint.rs
+++ b/src/scene/paint.rs
@@ -217,6 +217,11 @@ pub enum PlateStance {
Flat,
}
+/// 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
+/// seam and the seam's own wall land nowhere near the field.
+pub const FIELD_RUN_ONLY: f32 = 1.0e4;
+
/// 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
@@ -244,6 +249,12 @@ 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 {
@@ -251,7 +262,13 @@ 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 }
+ Self { rect, radii: (radius, radius, radius, radius), stance, face, depth, tint: None, run_edge: false }
+ }
+
+ /// [`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
@@ -1285,6 +1302,10 @@ 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),
@@ -1424,6 +1445,20 @@ impl PaintCtx {
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);
+ }
+
/// [`inset_plate`](Self::inset_plate) with the rim lit — the focused flush
/// control plate's ring (`ControlPlate::with_tint`); the face fill as
/// there, the trough tinted.
@@ -1844,6 +1879,9 @@ 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);
}
diff --git a/src/widget/container/parameters_bg.rs b/src/widget/container/parameters_bg.rs
index 18d43f2..cad86ee 100644
--- a/src/widget/container/parameters_bg.rs
+++ b/src/widget/container/parameters_bg.rs
@@ -1722,19 +1722,13 @@ impl ParametersBg {
}
// (control, its configured corner radius, raised vs recessed)
let ctl: Option<(&dyn WidgetHost, f32, bool)> = if is_text_row(&p.2) {
- // The textpick picker button is NOT in this list: it is a
- // flush inset control (face level with the well floor, a
- // valley seam around it — the dropdown trigger's trough
- // language, not a boss), and troughs travel through
- // [`Self::picker_troughs`]. A boss here read as a raised
- // island, which no other inset control in the DE does.
+ // 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 inset control: the widget's
- // own raised paint is one `inset_plate` (face level with the
- // plate, a valley seam around it), so its ring travels through
- // [`Self::troughs`]. A boss here read as a raised island the
- // widget itself never draws.
+ // The dropdown trigger is a FLUSH control, a field that is all
+ // run ([`Self::fields`]). A boss here read as a raised island
+ // the widget itself never draws.
None
} else if p.2 == "button" {
self.buttons[i].as_ref().map(|w| (w as &dyn WidgetHost, crate::layout::button_corner_radius(), true))
@@ -1743,8 +1737,7 @@ impl ParametersBg {
// widget's own (`Toggle::flat_carves`, exactly what its paint
// emits, in the order it emits them) — ARE the control: the
// track's well (a recess) and the plate gliding on its floor
- // (a boss). Neither is flush, so nothing here rides
- // [`Self::troughs`].
+ // (a boss). Neither is flush.
if let Some(t) = &self.toggles[i] {
let (x, y, w, h) = t.rect();
if w > 0.0 && h > 0.0 {
@@ -1762,12 +1755,10 @@ impl ParametersBg {
}
None
} else if p.2.starts_with("spinbox") {
- // The well recess only — the -/+ run's trough and its seam
- // travel through [`Self::troughs`] / [`Self::grooves`] (this
- // tuple speaks boss/recess). The widget's own `relief_parts`
- // decides where the well ends: at the run, square there, the
- // run being the control's right end. Same side-label inset,
- // same content band, same depth cap as its paint.
+ // A plain well only when there is no -/+ run; with one the
+ // control is a field ([`Self::fields`]) and its -/+ seam a
+ // groove ([`Self::grooves`]). Same side-label inset, same
+ // content band, same depth cap as its paint.
if let Some(sb) = &self.spinboxes[i] {
let (x, y, w, h) = sb.rect();
let ty = sb.label_strip();
@@ -1813,8 +1804,6 @@ 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 square at the seam,
- // as the box's own carve is (`TextBox::well`).
// 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) {
@@ -1826,65 +1815,14 @@ impl ParametersBg {
out
}
- /// The textpick picker buttons' trough rings — `(x, y, w, h, radii, depth)`
- /// for [`crate::scene::paint::PaintCtx::trough_edges`], drawn by the host
- /// AFTER [`Self::reliefs`]. These are the rows' FLUSH inset controls — the
- /// textpick picker button, the spinbox's -/+ run, the dropdown trigger
- /// (the widget's own `inset_plate`): faces
- /// level with the surface they sit in, so the control reads as part of
- /// the plate, marked off by its seam alone. On the sides a control adjoins its well, the WELL'S
- /// OWN WALL is the seam's far side (the face reaches the wall's base and
- /// that trough edge is suppressed — a lip of its own there doubles the
- /// valley); only edges facing open floor carve their own wall. They
- /// cannot ride in [`Self::reliefs`], whose tuple only speaks boss/recess.
- /// Radii are the well radius's parallel curve at each control's inset;
- /// depths match the well's carve, so seam and wall read as one family.
- #[allow(clippy::type_complexity)]
- pub fn troughs(
- &self,
- ) -> Vec<(f32, f32, f32, f32, (f32, f32, f32, f32), f32, (bool, bool, bool, bool))> {
- if !self.visible || !crate::layout::control_relief() {
- return Vec::new();
- }
- let mut out = Vec::new();
- let hidden = self.hidden_rows();
- for (i, p) in self.display_params.iter().enumerate() {
- if hidden[i] {
- continue;
- }
- // A textpick row's picker and a spinbox's -/+ run are the flush
- // ends of fields ([`Self::fields`]), not troughs of their own.
- if p.2.starts_with("textpick") || p.2.starts_with("spinbox") {
- continue;
- }
- if p.2.starts_with("choice") {
- // The dropdown trigger: the widget's own raised paint is one
- // `inset_plate` on its content band — the same ring here, on
- // the same band (top-label band excluded),
- // same radius, same depth cap. The face stays the plate: the
- // pane carries no dropdown fill (a `Border` face never reaches
- // the rounded-quad view), so the trigger is flush and bare.
- if let Some(d) = &self.choices[i] {
- let (x, y, w, h) = d.rect();
- if w > 0.0 && h > 0.0 {
- let ty = d.label_strip();
- let depth = crate::layout::bevel_width().min((h - ty) * 0.2);
- let r = crate::layout::dropdown_corner_radius();
- out.push((x, y + ty, w, h - ty, (r, r, r, r), depth, (true, true, true, true)));
- }
- }
- }
- // A toggle contributes nothing here: its well and its glider plate
- // are a recess and a boss, and both ride [`Self::reliefs`].
- }
- out
- }
-
/// The rows that are ONE field ([`crate::scene::paint::Prim::Field`]):
/// a sunken well ending in a flush run, one outline round both —
- /// `(x, y, w, h, radii, depth, split)`, drawn AFTER [`Self::troughs`].
+ /// `(x, y, w, h, radii, depth, split)`, drawn AFTER [`Self::reliefs`].
/// A textpick row is its text box's well and its picker; a spinbox, its
- /// value's well and its -/+ run. On the pane's own rule for wells and
+ /// value's well and its -/+ run; a dropdown row, a field that is all run
+ /// (its seam [`crate::scene::paint::FIELD_RUN_ONLY`] to the left), so
+ /// every flush control in the pane has the same edge. They replaced
+ /// `troughs()` on 2026-10-01. On the pane's own rule for wells and
/// troughs: the content band as the outline, the wall straddling it.
#[allow(clippy::type_complexity)]
pub fn fields(&self) -> Vec<(f32, f32, f32, f32, (f32, f32, f32, f32), f32, f32)> {
@@ -1911,6 +1849,21 @@ impl ParametersBg {
out.push((x, y + ty, dx + dw - x, h - ty, (r, r, r, r), depth, dx));
}
}
+ } 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`)
+ // 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.
+ if let Some(d) = &self.choices[i] {
+ let (x, y, w, h) = d.rect();
+ let ty = d.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::dropdown_corner_radius();
+ out.push((x, y + ty, w, h - ty, (r, r, r, r), depth, x - crate::scene::paint::FIELD_RUN_ONLY));
+ }
+ }
} else if p.2.starts_with("spinbox") {
if let Some(sb) = &self.spinboxes[i] {
let (x, y, w, h) = sb.rect();
@@ -1929,7 +1882,7 @@ impl ParametersBg {
}
/// The engraved seams companion — `(a, b, width, depth, host)` for
- /// [`crate::scene::paint::PaintCtx::groove`], drawn AFTER [`Self::troughs`]
+ /// [`crate::scene::paint::PaintCtx::groove`], drawn AFTER [`Self::fields`]
/// (a groove engraves the surface the trough's control face provides).
/// Today: the seam dividing a spinbox's -/+ run into its two buttons —
/// the breadcrumb's segment-seam language at miniature scale.
@@ -2135,14 +2088,6 @@ impl Paint for ParametersBg {
(false, false) => ctx.recess_edges(rect, radii, rd, edges),
}
}
- for (tx2, ty2, tw2, th2, radii, td, tedges) in self.troughs() {
- let rect = Rect { x: tx2, y: ty2, width: tw2, height: th2 };
- if hovered(tx2, ty2, tw2, th2) {
- ctx.trough_edges_tinted(rect, radii, td, tedges, Self::HOVER_TINT);
- } else {
- ctx.trough_edges(rect, radii, td, tedges);
- }
- }
for (fx, fy, fw, fh, radii, fd, split) in self.fields() {
let rect = Rect { x: fx, y: fy, width: fw, height: fh };
let tint = hovered(fx, fy, fw, fh).then_some(Self::HOVER_TINT);
@@ -3923,7 +3868,6 @@ mod tests {
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");
- assert!(p.inner().troughs().iter().all(|t| t.0 != dx), "the picker draws no ring of its own");
assert!(p.inner().reliefs().iter().all(|r| !(r.0 == tx && r.2 == tw)), "nor the box a well of its own");
// Both text variants lay out at the same row height.
@@ -3943,6 +3887,32 @@ mod tests {
assert!(py_ >= ty + th - 1.0, "menu hangs below the box");
}
+ /// A dropdown trigger's edge is a field's run: its own paint and the
+ /// pane's re-emission both draw a field that is all run, so its edge
+ /// matches the picker at the end of a text row and the -/+ run of a
+ /// spinbox. Never a trough, whose outer half differed.
+ #[test]
+ fn a_dropdown_trigger_wears_the_runs_edge() {
+ use crate::scene::paint::{PaintCtx, Prim, FIELD_RUN_ONLY};
+ if !crate::layout::control_relief() {
+ return;
+ }
+ let p = panel_with(&[("Mode", "b", "choice:a,b,c")]);
+ let d = p.choices[0].as_ref().expect("a dropdown");
+ let (x, y, w, h) = d.rect();
+ let ty = d.label_strip();
+ let fields = p.inner().fields();
+ assert_eq!(fields.len(), 1, "{fields:?}");
+ let (fx, fy, fw, fh, _, _, split) = fields[0];
+ assert_eq!((fx, fy, fw, fh), (x, y + ty, w, h - ty), "on the trigger's band");
+ assert_eq!(split, x - FIELD_RUN_ONLY, "all run, no well");
+ let mut pc = PaintCtx::new();
+ Paint::paint(d.inner(), Rect { x, y: y + ty, width: w, height: h - ty }, &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, split, .. } if *split < rect.x - 100.0)), "{prims:?}");
+ assert!(!prims.iter().any(|p| matches!(p, Prim::Trough { .. })), "no trough");
+ }
+
#[test]
fn param_controller_roundtrip_and_row_widgets() {
let p = panel_with(&[
diff --git a/src/widget/input/dropdown.rs b/src/widget/input/dropdown.rs
index 5f98926..b00e51e 100644
--- a/src/widget/input/dropdown.rs
+++ b/src/widget/input/dropdown.rs
@@ -650,6 +650,9 @@ 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;
@@ -1082,7 +1085,7 @@ impl Paint for Dropdown {
(radius, radius, radius, radius),
depth,
);
- pc.inset_plate(face, t.x, t.y, t.width, t.height, tr.0, depth);
+ pc.flush_run(face, t.x, t.y, t.width, t.height, tr.0, depth, None);
} 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));