GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
refactor(widget): the legacy tuple views are gone
extra_quads, extra_arcs, extra_circles, all_quads, all_rounded_quads,
highlight_quad and the host-side corner_style leave WidgetHostExt, with the
Paint hooks that served only them (serves_legacy_plain_quads /
legacy_plain_quads, aggregates_child_extra_quads, forwarded_highlight), the
painter's paint_legacy_leaf and the tessellator's widget_vertices /
push_widget_vertices. Every app host already paints through paint_self.
The toolkit's own readers: ParametersBg, the ramp and the menubar read their
children's painted prims (widget::shown_prims / shown_quads /
shown_rounded_quads, crate-private); Adapted::paint_self computes the
TextBox's focus highlight itself; corner_radii and the flat-host bridge read
Paint::corner_style, which stays as what a widget says about its
silhouette. append_widget_plate is the plate alone: it also drew the
widget's arcs, which a host painting the widget after it drew twice. The
WidgetHost::paint_self default (test shims only) is the plate plus the
model's non-text prims. Tests that asserted on the views read painted_prims.
Pixel A/B in a scale-2 shadow: the designer (five pane layouts) and the
gallery identical before and after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CLAUDE.md | 29 ++--
docs/rfc-core-rebuild.md | 13 ++
src/backend/tessellate.rs | 19 ---
src/engine.rs | 2 +-
src/layout/bridge.rs | 3 +-
src/scene/paint.rs | 6 +-
src/scene/painter.rs | 41 +-----
src/widget/container/menu.rs | 20 ++-
src/widget/container/paginator.rs | 43 ++----
src/widget/container/parameters_bg.rs | 95 +++++--------
src/widget/container/spreadsheet.rs | 2 +-
src/widget/container/treelist.rs | 3 +-
src/widget/display/graph.rs | 58 ++++----
src/widget/display/progress_bar.rs | 13 +-
src/widget/display/separator.rs | 2 +-
src/widget/display/status_bar.rs | 9 +-
src/widget/display/status_dot.rs | 4 +-
src/widget/display/usage_bar.rs | 6 +-
src/widget/input/button.rs | 5 +-
src/widget/input/checkbox.rs | 5 +-
src/widget/input/color_selector.rs | 2 +-
src/widget/input/ramp.rs | 9 +-
src/widget/input/slider.rs | 5 +-
src/widget/input/text_box.rs | 19 +--
src/widget/mod.rs | 258 ++++++++--------------------------
src/widget/model.rs | 79 ++---------
26 files changed, 236 insertions(+), 514 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index 6b20c7c..f7886fa 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1690,14 +1690,24 @@ host, `dyn` included, carries what used to be one-line forwards: `focus_role`, `
`blocks_root_plate_drag`, `wants_tick`, `is_scrollable`, the `a11y_*` reads and acts,
`set_modifiers`, `context_action`, `color`, `solid_border`, `widget_font`,
`clips_children`, `renders_own_subtree`, `z_index`, `preferred_height`, plus the pure
-derivations `label`, `corner_radii`, `mark_dirty` — and the legacy tuple views
-(`extra_quads`, `extra_arcs`, `extra_circles`, `all_quads`, `all_rounded_quads`,
-`highlight_quad`, `corner_style`): what a widget paints projected onto the
-pre-display-list surface, computed from `paint_model().paint(content_rect())` the way
-`Adapted` computed them, for the hosts that still draw a widget through them rather than
-`paint_self` (the designer's render loop, the gallery, the display manager's greeter, the
-settings app's flat collector, cce-secrets, the flat-host bridge). Call them as before, with
-`cce_ui::widget::WidgetHostExt` in scope. A method stays ON the trait only when the host
+derivations `label`, `corner_radii`, `mark_dirty`, and `content_rect` / `painted_prims` (what
+the widget's model paints, as prims). Call them with `cce_ui::widget::WidgetHostExt` in
+scope.
+
+**The legacy tuple views are gone** (2026-10-08): `extra_quads`, `extra_arcs`,
+`extra_circles`, `all_quads`, `all_rounded_quads`, `highlight_quad` and the host-side
+`corner_style` — what a widget paints projected onto the pre-display-list surface — with
+the model hooks that served only them (`serves_legacy_plain_quads` / `legacy_plain_quads`,
+`aggregates_child_extra_quads`, `forwarded_highlight`), the painter's `paint_legacy_leaf`
+and the tessellator's `widget_vertices`. Every host paints a widget through `paint_self` or
+the paint walk (the designer, the gallery, the greeter, the settings app and cce-secrets
+moved the same day, each checked by pixel A/B). A composite that draws a child's chrome in
+its own order — the params pane's rows, the ramp's key editor, the menubar's strip — reads
+the child's `painted_prims` (`widget::shown_prims` / `shown_quads` / `shown_rounded_quads`,
+crate-private). `Paint::corner_style` stays: it is what a widget says about its silhouette,
+read by `corner_radii`, `append_widget_plate` and the bridge. `append_widget_plate` is the
+plate alone now; it drew the widget's arcs too, which a host painting the widget after it
+drew twice. A method stays ON the trait only when the host
adds something the model cannot (visibility gating, the content rect, child recursion,
registry state). `plate_bevel` is gone: nothing overrode it, so it was always `None`.
`Owned` forwards the trait's methods and the four accessors; the extension trait needs no
@@ -2054,8 +2064,7 @@ NOTHING until this change, when the wires were a hard-coded cyan 3 px.
`Graph::paint_wires` (also on `GraphController`), `Prim::Vector`s and
`Prim::Arc`s, since only one style is axis-aligned. `Paint::paint` calls
it after the grid; a host drawing the quads itself (the designer) calls
- it between `paint_grid` and the bodies. The legacy `extra_quads` view has
- no wires.
+ it between `paint_grid` and the bodies.
- **The run across is on the first lattice line below the source**
(`Graph::wire_turn_y`, since 2026-10-06), for orthogonal and rounded
wires running down. It was halfway between the ports, so a wire spanning
diff --git a/docs/rfc-core-rebuild.md b/docs/rfc-core-rebuild.md
index 540983b..ba0c381 100644
--- a/docs/rfc-core-rebuild.md
+++ b/docs/rfc-core-rebuild.md
@@ -2073,6 +2073,19 @@ Constraint respected: **each crate still builds standalone** — the new core is
radius, is-Ramp flag) at re-parent time instead of the pointer —
same refresh cadence, no deref of potentially-dead memory; watch
the one-frame rect lag on resize if reads move to snapshots.
+ - **The legacy tuple views retired (the same day).** The five apps moved to `paint_self`
+ (cce-secrets, the greeter, the settings app's dropdown and search box, the gallery, the
+ designer's render loop), then the toolkit's own readers: `ParametersBg`, the ramp and the
+ menubar read their children's `painted_prims`; `Adapted::paint_self` computes the
+ TextBox's focus highlight itself; the bridge and `corner_radii` read `Paint::corner_style`;
+ `append_widget_plate` stopped drawing the widget's arcs; `paint_legacy_leaf` and
+ `widget_vertices` / `push_widget_vertices` had no callers left. The views and the hooks
+ that served only them (`serves_legacy_plain_quads`, `legacy_plain_quads`,
+ `aggregates_child_extra_quads`, `forwarded_highlight`) are deleted, and the tests that
+ asserted on them read `painted_prims`. Pixel A/B in a scale-2 shadow: the designer (five
+ pane layouts) and the gallery identical before and after the toolkit change; each app's
+ own move is described in its commit (cce-secrets and the greeter changed look by choice,
+ the settings dropdown gained its face plate, the gallery lost a stray outline).
- **WidgetHost 38 → 31 (the same day).** The legacy tuple views (`extra_quads`,
`extra_arcs`, `extra_circles`, `all_quads`, `all_rounded_quads`, `highlight_quad`,
`corner_style`) moved to `WidgetHostExt` too, computed from the paint model exactly as
diff --git a/src/backend/tessellate.rs b/src/backend/tessellate.rs
index 288878d..3ab7499 100644
--- a/src/backend/tessellate.rs
+++ b/src/backend/tessellate.rs
@@ -1240,25 +1240,6 @@ pub fn push_plate_solid_border_vertices_legacy(
push_plate_solid_border_vertices(x, y, ww, h, radii, t, sw, sh, color, clip_circle, out);
}
-pub fn widget_vertices(w: &dyn crate::widget::WidgetHost, sw: f32, sh: f32, clip_circle: [f32; 3]) -> Vec<Vertex> {
- let mut verts = Vec::new();
- push_widget_vertices(w, sw, sh, clip_circle, &mut verts);
- verts
-}
-
-pub fn push_widget_vertices(w: &dyn crate::widget::WidgetHost, sw: f32, sh: f32, clip_circle: [f32; 3], out: &mut Vec<Vertex>) {
- let (x, y, ww, h) = w.rect();
- let radii = w.corner_radii();
- push_rounded_rect_vertices_corners(x, y, ww, h, radii, sw, sh, w.color(), clip_circle, None, out);
- if let Some((color, thickness)) = w.solid_border() {
- push_plate_solid_border_vertices(x, y, ww, h, radii, thickness, sw, sh, color, clip_circle, out);
- }
-
- for (cx, cy, r, t, start, end, qc) in w.extra_arcs() {
- push_arc_background_vertices(cx, cy, r, t, start, end, sw, sh, qc, 16, clip_circle, out);
- }
-}
-
/// A contiguous run of vertices sharing one scissor rect (Phase 3 single paint path) and one
/// rounded-rect clip. `scissor` is a logical-pixel clip (`None` = unclipped); `clip_rrect` is
/// the paint walk's `[cx, cy, bx, by, r]` rounded clip in logical px (`None` = unclipped),
diff --git a/src/engine.rs b/src/engine.rs
index 0f58940..20809f3 100644
--- a/src/engine.rs
+++ b/src/engine.rs
@@ -14,7 +14,7 @@ pub use crate::backend::tessellate::{
quad_vertices, quad_vertices_with_clip, quad_vertices_clipped, line_vertices,
vector_vertices, rounded_rect_vertices_corners, push_rounded_rect_vertices_corners,
rounded_rect_vertices, push_rounded_rect_vertices, plate_bevel_vertices,
- push_plate_bevel_vertices, widget_vertices, push_widget_vertices,
+ push_plate_bevel_vertices,
extra_quad_vertices, push_extra_quad_vertices, extra_quad_vertices_clipped,
push_extra_quad_vertices_clipped, circle_vertices, circle_border_vertices,
arc_background_vertices, push_arc_background_vertices, push_plate_solid_border_vertices,
diff --git a/src/layout/bridge.rs b/src/layout/bridge.rs
index 0a2d707..48c926c 100644
--- a/src/layout/bridge.rs
+++ b/src/layout/bridge.rs
@@ -193,7 +193,8 @@ pub fn render_widget<T: WidgetHost + 'static>(pc: &mut dyn RenderTarget, w: &mut
if let Ok(mut fs) = crate::geometry_font_system().lock() {
w.prepare_text(&mut fs);
}
- let (style_r, corners) = w.corner_style();
+ let (style_r, corners) =
+ w.paint_model().corner_style(w.content_rect()).unwrap_or((0.0, (false, false, false, false)));
let r = if corners != (false, false, false, false) { style_r } else { 0.0 };
let (wx, mut wy, www, mut whh) = w.rect();
let top_room = w.label_strip();
diff --git a/src/scene/paint.rs b/src/scene/paint.rs
index 0193467..f9da9ad 100644
--- a/src/scene/paint.rs
+++ b/src/scene/paint.rs
@@ -437,11 +437,11 @@ impl DropletFinish for DropletSpec {
pub enum Prim {
Quad { rect: Rect, color: [f32; 4] },
RoundedRect { rect: Rect, radius: f32, corners: (bool, bool, bool, bool), color: [f32; 4] },
- /// A rounded fill plus a solid border stroke — a widget's own "plate" (mirrors
- /// `push_widget_vertices`' non-bevel branch: rounded bg + `push_plate_solid_border_vertices`).
+ /// A rounded fill plus a solid border stroke — a widget's own "plate"
+ /// (`append_widget_plate`'s non-bevel branch).
Border { rect: Rect, radii: Radii, fill: [f32; 4], border: [f32; 4], thickness: f32 },
/// A beveled plate: a rounded fill at full size plus a light/shadow overlay lip
- /// (mirrors `push_widget_vertices`' bevel branch). `tint` colours the
+ /// (`append_widget_plate`'s bevel branch). `tint` colours the
/// roll's light and shadow — neutral white normally; a host sets it to a
/// highlight color to mark the plate (the focused-pane treatment) without a
/// separate border ring: the light goes to the tint, the shadow to a dark
diff --git a/src/scene/painter.rs b/src/scene/painter.rs
index b2b8aea..b920976 100644
--- a/src/scene/painter.rs
+++ b/src/scene/painter.rs
@@ -53,40 +53,10 @@ pub fn append_widget_text(ui: &UiContext, root: &dyn WidgetHost, pc: &mut PaintC
}
}
-/// The `WidgetHost` default `paint_self`'s LEAF branch as a reusable body: leaf geometry
-/// (rounded quads, plain quads, arcs, circles) followed by the widget's fonted labels.
-/// Legacy leaf widgets' `paint_self` overrides call this with their own labels — the
-/// labels are PASSED IN rather than fetched through the per-widget text getters, so this
-/// helper (and every override built on it) survives the getters' deletion. `pub` so
-/// app-local legacy widgets (display-manager's status/session widgets, cloud's fuzzel)
-/// can use it too.
-pub fn paint_legacy_leaf(
- w: &dyn WidgetHost,
- ui: &UiContext,
- pc: &mut PaintCtx,
- labels: Vec<(TextLabel, Option<String>, Option<[f32; 4]>)>,
-) {
- for (x, y, qw, qh, r, c, corners) in w.all_rounded_quads(ui) {
- pc.rounded_rect(Rect { x, y, width: qw, height: qh }, r, corners, c);
- }
- for (x, y, qw, qh, c) in w.all_quads(ui) {
- pc.quad(Rect { x, y, width: qw, height: qh }, c);
- }
- for (cx, cy, r, t, s, e, c) in w.extra_arcs() {
- pc.arc(cx, cy, r, t, s, e, c);
- }
- for (cx, cy, r, c) in w.extra_circles() {
- pc.circle(cx, cy, r, c);
- }
- for (tl, font, bounds) in labels {
- pc.text_with(tl.text, tl.x, tl.y, tl.font_size, tl.color, font, bounds);
- }
-}
-
-/// The prim-level mirror of the backend's `push_widget_vertices`: the widget's own plate —
-/// beveled, or rounded fill + optional solid border — plus its extra arcs. For hosts that
-/// hand-build their display list in their own draw order (the designer) instead of walking
-/// `paint_self`, but want a widget's background exactly as the vertex path drew it.
+/// The widget's own plate — beveled, or rounded fill + optional solid border — for hosts that
+/// hand-build their display list in their own draw order (the designer) and paint the widget
+/// on it with `paint_self`. The plate only: until 2026-10-08 it carried the widget's arcs too,
+/// which a host then painting the widget drew twice.
pub fn append_widget_plate(w: &dyn WidgetHost, pc: &mut PaintCtx) {
append_widget_plate_tinted(w, pc, None);
}
@@ -121,9 +91,6 @@ pub fn append_widget_plate_radii(w: &dyn WidgetHost, pc: &mut PaintCtx, tint: Op
} else {
pc.border(rect, radii_tuple, w.color(), [0.0; 4], 0.0);
}
- for (cx, cy, r, t, s, e, c) in w.extra_arcs() {
- pc.arc(cx, cy, r, t, s, e, c);
- }
}
/// The scroll-ancestor text clamp the deleted default fonted getter applied. Always `None`
diff --git a/src/widget/container/menu.rs b/src/widget/container/menu.rs
index 3ab1262..7597b71 100644
--- a/src/widget/container/menu.rs
+++ b/src/widget/container/menu.rs
@@ -547,9 +547,12 @@ impl Paint for MenuBar {
// separate groove rings), and the strip's full-height square state
// quads are replaced by the trigger fills — open matches the context
// dropdown's open tint rather than the strip's legacy palette.
+ let strip = crate::widget::shown_prims(&self.menus);
if flat_modes {
- for (qx, qy, qw, qh, qc) in self.menus.extra_quads() {
- ctx.quad(Rect { x: qx, y: qy, width: qw, height: qh }, qc);
+ for prim in &strip {
+ if let crate::scene::paint::Prim::Quad { rect, color } = prim {
+ ctx.quad(*rect, *color);
+ }
}
} else {
for i in 0..self.menus.buttons.len() {
@@ -567,11 +570,14 @@ impl Paint for MenuBar {
trough_chrome(ctx, r, fill);
}
}
- for (cx, cy, r, t, start, end, c) in self.menus.extra_arcs() {
- ctx.arc(cx, cy, r, t, start, end, c);
- }
- for (cx, cy, r, c) in self.menus.extra_circles() {
- ctx.circle(cx, cy, r, c);
+ for prim in strip {
+ match prim {
+ crate::scene::paint::Prim::Arc { cx, cy, radius, thickness, start, end, color } => {
+ ctx.arc(cx, cy, radius, thickness, start, end, color);
+ }
+ crate::scene::paint::Prim::Circle { cx, cy, radius, color } => ctx.circle(cx, cy, radius, color),
+ _ => {}
+ }
}
// Text: the sidebar label (vertical), the title (curved / vertical / horizontal), and
diff --git a/src/widget/container/paginator.rs b/src/widget/container/paginator.rs
index ab27673..b386d87 100644
--- a/src/widget/container/paginator.rs
+++ b/src/widget/container/paginator.rs
@@ -4,22 +4,16 @@
//! containers toggled visible/hidden) is gone — its only observable output, the page-area
//! background quad, is painted directly here.
//!
-//! Two legacy behaviors ride hooks from the 5r migration:
-//! - [`Layout::register_embedded_children`]: legacy `tick`/`layout` re-registered the strip into
-//! the ctx registry every frame — load-bearing for the spatial grid (the registered strip is
-//! what makes the sidebar block root plate drags).
-//! - [`Paint::aggregates_child_extra_quads`] + [`Paint::forwarded_highlight`]: legacy
-//! `extra_quads` served the strip's chrome only (cce-layout-interface renders the tab
-//! column through that getter plus `all_rounded_quads`, which carries the strip's rounded
-//! state fills — the paginator's own backgrounds live in `all_rounded_quads` alone), and
-//! legacy `highlight_quad` forwarded to the strip's (the hovered-tab tint
-//! cce-layout-interface draws directly).
+//! One legacy behavior rides a hook from the 5r migration:
+//! [`Layout::register_embedded_children`]: legacy `tick`/`layout` re-registered the strip into
+//! the ctx registry every frame — load-bearing for the spatial grid (the registered strip is
+//! what makes the sidebar block root plate drags).
use crate::colors;
use crate::scene::layout::Rect;
use crate::scene::paint::PaintCtx;
use crate::widget::input::ButtonStrip;
-use crate::widget::{Adapted, WidgetHost, Event, EventCtx, Input, Layout, MenuController, PageSelector, Paint, UiContext, WidgetId, WidgetHostExt};
+use crate::widget::{Adapted, WidgetHost, Event, EventCtx, Input, Layout, MenuController, PageSelector, Paint, UiContext, WidgetId};
pub struct Paginator {
/// In an [`Owned`](crate::widget::Owned) box of its own, so the registry points at a
@@ -196,19 +190,6 @@ impl Paint for Paginator {
}
}
}
-
- /// Legacy `extra_quads` served the strip's + selected page's chrome only —
- /// cce-layout-interface draws the tab column through this getter (and the strip's rounded
- /// state fills through `all_rounded_quads`), over its own backgrounds.
- fn aggregates_child_extra_quads(&self) -> bool {
- true
- }
-
- /// Legacy `highlight_quad` forwarded to the strip's (the hovered-tab tint
- /// cce-layout-interface draws directly).
- fn forwarded_highlight(&self, ctx: &UiContext) -> Option<Option<(f32, f32, f32, f32, [f32; 4])>> {
- Some(self.sidebar_menu.highlight_quad(ctx))
- }
}
impl Input for Paginator {
@@ -310,23 +291,19 @@ mod tests {
}
#[test]
- fn plain_quads_split_like_legacy_and_registration_heals_on_tick() {
+ fn the_sidebar_bg_is_rounded_and_registration_heals_on_tick() {
let mut ctx = UiContext::new();
let mut p = paginator();
ctx.register_host(&mut p);
- // Legacy split: `extra_quads` is the children's chrome only; the sidebar background
- // (a rounded rect) lives in `all_rounded_quads` alone (layout-interface draws its
- // own backgrounds under `extra_quads`).
- let extra = crate::widget::WidgetHostExt::extra_quads(&p);
- let strip_extra = p.sidebar_menu.extra_quads();
- assert_eq!(extra.len(), strip_extra.len(), "children-only plain view (pages emit none)");
+ // The sidebar background is a rounded rect of the paginator's own paint, never a
+ // plain quad.
let bg = colors::sidebar_bg_color();
if bg[3] > 0.0 {
let r = crate::layout::plate_corner_radius();
let bg_quad = (0.0, 0.0, 400.0, 300.0, r, bg, (true, true, true, true));
- assert!(!extra.iter().any(|q| q.4 == bg && q.2 == 400.0), "no own bg in extra_quads");
- assert!(crate::widget::WidgetHostExt::all_rounded_quads(&p, &ctx).contains(&bg_quad), "own bg in all_rounded_quads");
+ assert!(!crate::widget::shown_quads(&p).iter().any(|q| q.4 == bg && q.2 == 400.0), "no plain bg");
+ assert!(crate::widget::shown_rounded_quads(&p).contains(&bg_quad), "a rounded bg");
}
// The embedded strip + pages land in the registry on tick (the spatial grid feeds off
diff --git a/src/widget/container/parameters_bg.rs b/src/widget/container/parameters_bg.rs
index b1b51d8..1d24a9e 100644
--- a/src/widget/container/parameters_bg.rs
+++ b/src/widget/container/parameters_bg.rs
@@ -9,13 +9,11 @@
//! working unchanged.
//!
//! The model caches its laid-out rect via [`Layout::rect_assigned`] (all row geometry derives
-//! from it — the TextBox pattern), and serves BOTH legacy escape hatches: the plain-quad view
-//! ([`Paint::serves_legacy_plain_quads`] — the designer renders params through raw
-//! `extra_quads`, and the panel's own background is drawn by the host from
-//! [`Paint::color`]/[`Paint::corner_style`], NOT emitted here), and the per-label text view
-//! ([`Paint::serves_legacy_labels`], new with this migration — each label clips to the
-//! viewport but the code editor's clip to the code box, in monospace, which the one-font
-//! one-bounds prim bridge can't express).
+//! from it — the TextBox pattern). The panel's own background is drawn by the host from
+//! [`Paint::color`]/[`Paint::corner_style`], NOT emitted here, and its text rides the
+//! per-label view ([`Paint::serves_legacy_labels`] — each label clips to the viewport but
+//! the code editor's clip to the code box, in monospace, which the one-font one-bounds prim
+//! bridge can't express).
//!
//! The code row is an editor, not a text box (2026-09-24): a line-number gutter, shift+arrow /
//! shift+click selection with ctrl+c / ctrl+x / ctrl+v through the toolkit clipboard, tab and
@@ -1486,10 +1484,9 @@ impl ParametersBg {
self.focused_param = None;
}
- /// The legacy `extra_quads` body: section border boxes, every row's chrome (slider/spinbox
- /// backgrounds read via `rect()`+`color()`, the code editor's box/border/cursor), the raw
- /// children via [`collect_child_quads`], all clipped to the viewport — plus the unclipped
- /// scrollbar. Served verbatim through [`Paint::legacy_plain_quads`].
+ /// The pane's plain quads: section border boxes, every row's chrome (slider/spinbox
+ /// backgrounds read via `rect()`+`color()`, the code editor's box/border/cursor), the
+ /// controls' own plain quads, all clipped to the viewport — plus the unclipped scrollbar.
fn plain_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
if !self.visible {
return Vec::new();
@@ -1534,7 +1531,7 @@ impl ParametersBg {
if let Some(s) = &self.sliders[i] {
let (sx, sy, sw, sh) = s.rect();
param_quads.push((sx, sy, sw, sh, s.color()));
- param_quads.extend(s.extra_quads());
+ param_quads.extend(crate::widget::shown_quads(s));
}
} else if p.2 == "section" {
// Section header line is handled by the border box top border now
@@ -1544,7 +1541,7 @@ impl ParametersBg {
param_quads.push((r.0 + 6.0, r.1, (r.2 - 12.0).max(0.0), 1.0, [c[0], c[1], c[2], c[3] * 0.8]));
} else if is_vec_row(&p.2) {
if let Some(f) = &self.float3s[i] {
- param_quads.extend(f.extra_quads());
+ param_quads.extend(crate::widget::shown_quads(f));
}
} else if p.2 == "code" {
let (bx, by, bw, bh) = (r.0, r.1 + Self::CODE_BOX_TOP, r.2, r.3 - Self::CODE_BOX_TOP);
@@ -1608,31 +1605,31 @@ impl ParametersBg {
param_quads.push((bx + bw - 1.0, by, 1.0, bh, border_color));
} else if is_text_row(&p.2) {
if let Some(tb) = &self.texts[i] {
- param_quads.extend(tb.extra_quads());
+ param_quads.extend(crate::widget::shown_quads(tb));
}
if let Some(d) = &self.choices[i] {
- param_quads.extend(d.extra_quads());
+ param_quads.extend(crate::widget::shown_quads(d));
}
} else if p.2.starts_with("choice") {
if let Some(d) = &self.choices[i] {
- param_quads.extend(d.extra_quads());
+ param_quads.extend(crate::widget::shown_quads(d));
}
} else if p.2 == "button" {
if let Some(b) = &self.buttons[i] {
- param_quads.extend(b.extra_quads());
+ param_quads.extend(crate::widget::shown_quads(b));
}
} else if p.2.starts_with("spinbox") {
if let Some(sb) = &self.spinboxes[i] {
{ let (bx, by, bw, bh) = sb.rect(); param_quads.push((bx, by, bw, bh, sb.color())); }
- param_quads.extend(sb.extra_quads());
+ param_quads.extend(crate::widget::shown_quads(sb));
}
} else if p.2 == "toggle" || p.2 == "checkbox" {
if let Some(cb) = &self.toggles[i] {
- param_quads.extend(cb.extra_quads());
+ param_quads.extend(crate::widget::shown_quads(cb));
}
} else if p.2.starts_with("color") || p.2 == "rgb" || p.2 == "rgba" {
if let Some(c) = &self.colors[i] {
- param_quads.extend(c.extra_quads());
+ param_quads.extend(crate::widget::shown_quads(c));
}
}
}
@@ -1651,15 +1648,11 @@ impl ParametersBg {
}
/// The rounded companion to [`Self::plain_quads`]: the row controls whose boxes are
- /// `Prim::RoundedRect` (textbox, dropdown, button, toggle, color selector). Those
- /// backgrounds never reach the plain view — `own_plain_quads` keeps `Prim::Quad` only —
- /// so a host that renders this panel through the legacy plain-quad hatch must read this
- /// getter too or the controls draw as bare text. Returned unclipped; the host clips to
- /// the pane's scroll viewport when it pushes vertices. Tuple layout matches
- /// `all_rounded_quads`: (x, y, w, h, radius, color, (tl, tr, br, bl)).
+ /// `Prim::RoundedRect` (textbox, dropdown, button, toggle, color selector), which the
+ /// plain list does not carry. Returned unclipped; the pane's paint clips them to its
+ /// scroll viewport. (x, y, w, h, radius, color, (tl, tr, br, bl)).
pub fn rounded_quads(
&self,
- ctx: &UiContext,
) -> Vec<(f32, f32, f32, f32, f32, [f32; 4], (bool, bool, bool, bool))> {
if !self.visible {
return Vec::new();
@@ -1672,27 +1665,27 @@ impl ParametersBg {
}
if is_text_row(&p.2) {
if let Some(tb) = &self.texts[i] {
- out.extend(tb.all_rounded_quads(ctx));
+ out.extend(crate::widget::shown_rounded_quads(tb));
}
if let Some(d) = &self.choices[i] {
- out.extend(d.all_rounded_quads(ctx));
+ out.extend(crate::widget::shown_rounded_quads(d));
}
} else if p.2.starts_with("slider") {
// Track (square style only — the recessed style has no track
// background), value fill, and readout box; the thumb knob is a
// `Prim::Sphere` and rides `spheres()` instead.
if let Some(s) = &self.sliders[i] {
- out.extend(s.all_rounded_quads(ctx));
+ out.extend(crate::widget::shown_rounded_quads(s));
}
} else if is_vec_row(&p.2) {
// Three slider rows: the same set per row (readout boxes,
// square-style tracks and fills), through the group's own paint.
if let Some(f) = &self.float3s[i] {
- out.extend(f.all_rounded_quads(ctx));
+ out.extend(crate::widget::shown_rounded_quads(f));
}
} else if p.2.starts_with("choice") {
if let Some(d) = &self.choices[i] {
- out.extend(d.all_rounded_quads(ctx));
+ out.extend(crate::widget::shown_rounded_quads(d));
}
} else if p.2.starts_with("spinbox") {
// The spinbox's whole chrome (frame, display well, +/- button
@@ -1700,19 +1693,19 @@ impl ParametersBg {
// transparent and it has no extra_quads, so skipping it here
// renders the row as bare text.
if let Some(sb) = &self.spinboxes[i] {
- out.extend(sb.all_rounded_quads(ctx));
+ out.extend(crate::widget::shown_rounded_quads(sb));
}
} else if p.2 == "button" {
if let Some(b) = &self.buttons[i] {
- out.extend(b.all_rounded_quads(ctx));
+ out.extend(crate::widget::shown_rounded_quads(b));
}
} else if p.2 == "toggle" || p.2 == "checkbox" {
if let Some(cb) = &self.toggles[i] {
- out.extend(cb.all_rounded_quads(ctx));
+ out.extend(crate::widget::shown_rounded_quads(cb));
}
} else if p.2.starts_with("color") || p.2 == "rgb" || p.2 == "rgba" {
if let Some(c) = &self.colors[i] {
- out.extend(c.all_rounded_quads(ctx));
+ out.extend(crate::widget::shown_rounded_quads(c));
}
}
}
@@ -2128,7 +2121,7 @@ impl Paint for ParametersBg {
}
/// The panel IS its own background plate (the host draws it from `color()` + the corner
- /// style via `push_widget_vertices`) — there is no separate plate widget behind it, so
+ /// style via `append_widget_plate`) — there is no separate plate widget behind it, so
/// this carries the full plate treatment: `PARAM_BG` scaled by the global plate opacity,
/// with the alpha negated as the scenefx blur marker when plate blur is on. Transparent
/// while hidden. It must not ALSO be emitted as a quad anywhere or it would double-blend.
@@ -2164,13 +2157,13 @@ impl Paint for ParametersBg {
/// it around the pane plate), and text (`paint_self`'s own-labels bridge carries the
/// per-row fonts and code-box bounds). Hosts clip this to their pane viewport — a rect
/// clip pushed here would not survive `paint_self`'s replay.
- fn paint_ui(&self, ui: &UiContext, _rect: Rect, ctx: &mut PaintCtx) {
+ fn paint_ui(&self, _ui: &UiContext, _rect: Rect, ctx: &mut PaintCtx) {
if !self.visible {
return;
}
self.claim_typing(ctx);
self.paint_row_floors(ctx);
- for (qx, qy, qw, qh, qr, qc, corners) in self.rounded_quads(ui) {
+ for (qx, qy, qw, qh, qr, qc, corners) in self.rounded_quads() {
ctx.rounded_rect(Rect { x: qx, y: qy, width: qw, height: qh }, qr, corners, qc);
}
for (acx, acy, ar, at, a0, a1, ac) in self.arcs() {
@@ -2251,14 +2244,6 @@ impl Paint for ParametersBg {
}
}
- fn serves_legacy_plain_quads(&self) -> bool {
- true
- }
-
- fn legacy_plain_quads(&self, _rect: Rect) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
- self.plain_quads()
- }
-
fn serves_legacy_labels(&self) -> bool {
true
}
@@ -4721,21 +4706,19 @@ mod tests {
}
#[test]
- fn plain_view_serves_row_chrome_and_all_quads_stays_empty() {
+ fn the_row_chrome_is_the_panes_and_the_plate_the_hosts() {
let ctx = UiContext::new();
let p = panel_with(&[("Size", "1.00", "slider:0:2")]);
- // The designer's plain path: extra_quads carries the row chrome (clipped), including
- // the slider background it reads via rect()+color()...
- let extra = crate::widget::WidgetHostExt::extra_quads(&p);
- assert!(!extra.is_empty(), "row chrome served through extra_quads");
+ // The pane's plain quads carry the row chrome (clipped), including the slider
+ // background it reads via rect()+color()...
+ let plain = p.plain_quads();
+ assert!(!plain.is_empty(), "row chrome in the pane's plain quads");
// ...but NOT the panel's own PARAM_BG plate (the host draws that from color()).
let (x, y, w, h) = WidgetHost::rect(&p);
assert!(
- !extra.iter().any(|q| (q.0, q.1, q.2, q.3) == (x, y, w, h)),
- "panel bg plate is the host's, not extra_quads'"
+ !plain.iter().any(|q| (q.0, q.1, q.2, q.3) == (x, y, w, h)),
+ "panel bg plate is the host's, not the pane's"
);
- // The no-double-draw contract of the plain-quad hatch.
- assert!(crate::widget::WidgetHostExt::all_quads(&p, &ctx).is_empty());
// Per-label hatch: the walk's text prims carry the widget font and viewport bounds.
let mut scratch = crate::scene::paint::PaintCtx::new();
crate::scene::painter::append_widget_text(&ctx, &p, &mut scratch);
diff --git a/src/widget/container/spreadsheet.rs b/src/widget/container/spreadsheet.rs
index 73acf35..758f92d 100644
--- a/src/widget/container/spreadsheet.rs
+++ b/src/widget/container/spreadsheet.rs
@@ -28,7 +28,7 @@
//! strings) still works: its cells become text columns.
//!
//! The `PARAM_BG` background is NOT emitted here: the designer's render path draws every
-//! widget's background itself from `color()` + `corner_style()` (`push_widget_vertices`), and
+//! widget's background itself from `color()` + `corner_style()` (`append_widget_plate`), and
//! `PARAM_BG` is translucent — emitting it again would double-blend. This widget's own
//! geometry starts at the header strip, exactly like the legacy `extra_quads`.
diff --git a/src/widget/container/treelist.rs b/src/widget/container/treelist.rs
index 0f71937..6d30537 100644
--- a/src/widget/container/treelist.rs
+++ b/src/widget/container/treelist.rs
@@ -1669,7 +1669,6 @@ mod tests {
#[test]
fn test_treelist_separators() {
- let ctx = UiContext::new();
let mut tree_list = TreeList::new();
tree_list.set_rect(10.0, 52.0, 380.0, 500.0);
tree_list.set_flat_keys(vec![
@@ -1687,7 +1686,7 @@ mod tests {
println!("scroll_box scroll_y: {}", tree_list.scroll_box.scroll_y);
println!("item_height: {}", tree_list.item_height);
- let quads = tree_list.all_rounded_quads(&ctx);
+ let quads = crate::widget::shown_rounded_quads(&tree_list);
println!("Rounded quads count: {}", quads.len());
for (i, q) in quads.iter().enumerate() {
println!("Quad {}: {:?}", i, q);
diff --git a/src/widget/display/graph.rs b/src/widget/display/graph.rs
index 55560a3..fe9219a 100644
--- a/src/widget/display/graph.rs
+++ b/src/widget/display/graph.rs
@@ -2,12 +2,10 @@
//! draggable nodes with geometry toggles, input/output ports, wire routing, and interactive
//! connection dragging. [`GraphController`] rides the `Input` capability hooks.
//!
-//! Rendering serves the legacy dual-geometry contract through the adapter's escape hatch:
-//! [`Paint::paint`] emits the ROUNDED view (what `render_widget` hosts — cce-files — and the
-//! scene walk — cce-graph — consume), while [`Paint::legacy_plain_quads`] serves the same
-//! geometry as plain quads for raw `extra_quads` readers (the designer's render path), with
-//! `all_quads` emptied by the adapter so no host draws it twice. Node-name text is clipped to
-//! the widget rect via [`Paint::text_bounds`]. All grid geometry is in absolute screen space
+//! [`Paint::paint`] emits the graph as rounded geometry (what `render_widget` hosts — cce-files —
+//! and the scene walk — cce-graph — consume); a host that draws the nodes in its own order (the
+//! designer) reads [`Graph::geometry_quads_tagged`] and calls [`Graph::paint_wires`] itself. Node-name
+//! text is clipped to the widget rect via [`Paint::text_bounds`]. All grid geometry is in absolute screen space
//! (hosts pan by moving `grid_origin`); the widget rect only culls and clips.
//!
//! The WIRES are not quads: they are strokes in a [`WireStyle`] — orthogonal, rounded,
@@ -901,15 +899,6 @@ impl Graph {
quads
}
- /// [`geometry_quads_tagged`](Self::geometry_quads_tagged) with the cell tags
- /// stripped — the unchanged legacy `extra_quads` shape.
- fn geometry_quads(&self, rect: Rect) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
- self.geometry_quads_tagged(rect)
- .into_iter()
- .map(|(qx, qy, qw, qh, qc, _)| (qx, qy, qw, qh, qc))
- .collect()
- }
-
/// The rounded view of the same geometry — the legacy `all_rounded_quads` conversion: the
/// widget background, then each plain quad either as a grid cell (superellipse cell arcs),
/// a node body (node corner radius, all corners), or with the widget's edge-corner
@@ -1112,14 +1101,6 @@ impl Paint for Graph {
}
}
- fn serves_legacy_plain_quads(&self) -> bool {
- true
- }
-
- fn legacy_plain_quads(&self, rect: Rect) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
- self.geometry_quads(rect)
- }
-
fn text_bounds(&self, rect: Rect) -> Option<[f32; 4]> {
Some([rect.x, rect.y, rect.x + rect.width, rect.y + rect.height])
}
@@ -2265,21 +2246,34 @@ mod tests {
}
#[test]
- fn dual_geometry_views_stay_consistent() {
+ fn the_painted_geometry_is_the_tagged_geometry_rounded() {
+ use crate::widget::WidgetHostExt;
let g = two_nodes();
- let ctx = UiContext::new();
- // The plain view (designer path) and the rounded view (render_widget path) describe
- // the same quads: the rounded view adds only the widget background entry up front.
- let plain = crate::widget::WidgetHostExt::extra_quads(&g);
- let rounded = crate::widget::WidgetHostExt::all_rounded_quads(&g, &ctx);
+ // What a host drawing the nodes itself reads (the designer) and what the graph paints
+ // describe the same quads: the paint adds only the widget background up front.
+ let plain: Vec<_> = g
+ .geometry_quads_tagged(g.content_rect())
+ .into_iter()
+ .map(|(qx, qy, qw, qh, qc, _)| (qx, qy, qw, qh, qc))
+ .collect();
+ let rounded: Vec<_> = g
+ .painted_prims()
+ .into_iter()
+ .filter_map(|prim| match prim {
+ crate::scene::paint::Prim::RoundedRect { rect, radius, corners, color } => {
+ Some((rect.x, rect.y, rect.width, rect.height, radius, color, corners))
+ }
+ _ => None,
+ })
+ .collect();
assert!(!plain.is_empty());
assert_eq!(rounded.len(), plain.len() + 1);
for ((px, py, pw, ph, pc), (rx, ry, rw, rh, _, rc, _)) in plain.iter().zip(rounded.iter().skip(1)) {
assert_eq!((px, py, pw, ph, pc), (rx, ry, rw, rh, rc));
}
- // Node bodies carry the node corner radius in the rounded view.
+ // Node bodies carry the node corner radius.
let node_radius = crate::layout::graph_node_corner_radius();
let node_entries: Vec<_> = rounded
.iter()
@@ -2290,9 +2284,5 @@ mod tests {
assert_eq!(entry.4, node_radius);
assert_eq!(entry.6, (true, true, true, true));
}
-
- // And `all_quads` stays empty so render_widget hosts (reading BOTH getters) never
- // draw the geometry twice — the legacy Graph override's contract.
- assert!(crate::widget::WidgetHostExt::all_quads(&g, &ctx).is_empty());
}
}
diff --git a/src/widget/display/progress_bar.rs b/src/widget/display/progress_bar.rs
index 9c8ba89..91d38e1 100644
--- a/src/widget/display/progress_bar.rs
+++ b/src/widget/display/progress_bar.rs
@@ -96,17 +96,16 @@ impl Input for ProgressBar {}
#[cfg(test)]
mod tests {
use super::*;
- use crate::widget::{WidgetHost, UiContext};
+ use crate::widget::WidgetHost;
/// The reverse bridge reproduces the legacy `all_rounded_quads` output: track quad at the
/// content rect, fill quad at `w * value` with the radius clamped to half the height.
#[test]
fn reverse_bridge_matches_legacy_geometry() {
- let ctx = UiContext::new();
let mut bar = ProgressBar::new(0.5).with_recessed(false);
WidgetHost::set_rect(&mut bar, 10.0, 20.0, 100.0, 8.0);
- let quads = crate::widget::WidgetHostExt::all_rounded_quads(&bar, &ctx);
+ let quads = crate::widget::shown_rounded_quads(&bar);
let radius = crate::layout::slider_corner_radius();
assert_eq!(quads.len(), 2, "track + fill");
assert_eq!(quads[0], (10.0, 20.0, 100.0, 8.0, radius, colors::progress_bg(), (true, true, true, true)));
@@ -119,15 +118,14 @@ mod tests {
/// Value is clamped like the legacy widget: over 1.0 fills the whole track, 0 emits no fill.
#[test]
fn fill_clamps_to_track() {
- let ctx = UiContext::new();
let mut over = ProgressBar::new(2.0).with_recessed(false);
WidgetHost::set_rect(&mut over, 0.0, 0.0, 100.0, 8.0);
- let quads = crate::widget::WidgetHostExt::all_rounded_quads(&over, &ctx);
+ let quads = crate::widget::shown_rounded_quads(&over);
assert_eq!(quads[1].2, 100.0, "over-1 value fills the whole track");
let mut empty = ProgressBar::new(0.0).with_recessed(false);
WidgetHost::set_rect(&mut empty, 0.0, 0.0, 100.0, 8.0);
- assert_eq!(crate::widget::WidgetHostExt::all_rounded_quads(&empty, &ctx).len(), 1, "zero value emits track only");
+ assert_eq!(crate::widget::shown_rounded_quads(&empty).len(), 1, "zero value emits track only");
}
/// The recessed style draws no track of its own: the fill on the well floor, then
@@ -152,7 +150,6 @@ mod tests {
/// strip is read back from the widget).
#[test]
fn label_strip_heads_the_block_and_insets_paint() {
- let ctx = UiContext::new();
let mut bar = ProgressBar::new(0.5).with_recessed(false).with_label("Progress");
let offset = WidgetHost::label_strip(&bar);
assert!(offset > 0.0, "a labeled bar carries a strip");
@@ -162,7 +159,7 @@ mod tests {
assert_eq!(h, 8.0 + offset, "the rect is the block it was given");
assert_eq!(y, 10.0, "origin is unchanged");
- let quads = crate::widget::WidgetHostExt::all_rounded_quads(&bar, &ctx);
+ let quads = crate::widget::shown_rounded_quads(&bar);
assert_eq!(quads[0].1, 10.0 + offset, "track is painted below the label region");
assert_eq!(quads[0].3, 8.0, "track keeps the assigned height");
diff --git a/src/widget/display/separator.rs b/src/widget/display/separator.rs
index 520ca7f..5b8e754 100644
--- a/src/widget/display/separator.rs
+++ b/src/widget/display/separator.rs
@@ -89,7 +89,7 @@ mod tests {
assert!(!crate::widget::WidgetHostExt::blocks_root_plate_drag(&sep));
if !crate::layout::control_relief() {
assert_eq!(crate::widget::WidgetHostExt::color(&sep), [0.3, 0.3, 0.3, 1.0]);
- assert_eq!(crate::widget::WidgetHostExt::extra_quads(&sep), vec![(100.0, 0.0, 1.0, 24.0, [0.3, 0.3, 0.3, 1.0])]);
+ assert_eq!(crate::widget::shown_quads(&sep), vec![(100.0, 0.0, 1.0, 24.0, [0.3, 0.3, 0.3, 1.0])]);
}
}
diff --git a/src/widget/display/status_bar.rs b/src/widget/display/status_bar.rs
index 947199f..6085e94 100644
--- a/src/widget/display/status_bar.rs
+++ b/src/widget/display/status_bar.rs
@@ -231,10 +231,11 @@ mod tests {
assert!(bar.text_buf.is_some());
assert_eq!(bar.text, "world");
- // Parentless: cornerless plain bg through the plain-quad bridge, at STATUS_BG.
- let extra = crate::widget::WidgetHostExt::extra_quads(&bar);
- assert_eq!(extra.len(), 1, "cornerless bg quad");
- assert_eq!(crate::widget::WidgetHostExt::corner_style(&bar).1, (false, false, false, false));
+ // Parentless: a cornerless plain bg quad, at STATUS_BG.
+ let plain = crate::widget::shown_quads(&bar);
+ assert_eq!(plain.len(), 1, "cornerless bg quad");
+ let cr = crate::widget::WidgetHostExt::corner_radii(&bar);
+ assert_eq!((cr.top_left, cr.top_right, cr.bottom_right, cr.bottom_left), (0.0, 0.0, 0.0, 0.0));
assert!(!crate::widget::WidgetHostExt::blocks_root_plate_drag(&bar));
}
diff --git a/src/widget/display/status_dot.rs b/src/widget/display/status_dot.rs
index dfa5526..10bb150 100644
--- a/src/widget/display/status_dot.rs
+++ b/src/widget/display/status_dot.rs
@@ -79,11 +79,11 @@ mod tests {
let mut dot = StatusDot::new(DotStatus::Warning);
WidgetHost::set_rect(&mut dot, 5.0, 6.0, 10.0, 10.0);
assert_eq!(
- crate::widget::WidgetHostExt::all_rounded_quads(&dot, &UiContext::new()),
+ crate::widget::shown_rounded_quads(&dot),
vec![(5.0, 6.0, 10.0, 10.0, 5.0, [0.90, 0.60, 0.10, 1.0], (true, true, true, true))],
"a disc: the rect at half-side radius",
);
- assert!(crate::widget::WidgetHostExt::extra_quads(&dot).is_empty(), "nothing on the plain path (apps read both)");
+ assert!(crate::widget::shown_quads(&dot).is_empty(), "nothing on the plain path (apps read both)");
// Drags pass through, as legacy declared.
assert!(!crate::widget::WidgetHostExt::blocks_root_plate_drag(&dot));
// State mutation through Deref, as call sites write it.
diff --git a/src/widget/display/usage_bar.rs b/src/widget/display/usage_bar.rs
index 4becbcb..34c3979 100644
--- a/src/widget/display/usage_bar.rs
+++ b/src/widget/display/usage_bar.rs
@@ -110,13 +110,13 @@ mod tests {
let radius = crate::layout::slider_corner_radius();
let all = (true, true, true, true);
assert_eq!(
- crate::widget::WidgetHostExt::all_rounded_quads(&bar, &crate::widget::UiContext::new()),
+ crate::widget::shown_rounded_quads(&bar),
vec![
(12.0, 30.0, 200.0, 8.0, radius, [0.4, 0.5, 0.6, 1.0], all),
(12.0, 30.0, 100.0, 8.0, radius.min(4.0), [0.1, 0.2, 0.3, 1.0], all),
],
);
- assert!(crate::widget::WidgetHostExt::extra_quads(&bar).is_empty(), "no plain quad leaks to flat hosts");
+ assert!(crate::widget::shown_quads(&bar).is_empty(), "no plain quad leaks to flat hosts");
}
/// Recessed: no bg quad at all — the fill on the floor, then the carve.
@@ -130,7 +130,7 @@ mod tests {
assert_eq!(prims.len(), 2, "{prims:?}");
assert!(matches!(prims[0], Prim::RoundedRect { .. }));
assert!(matches!(prims[1], Prim::Recess { .. }));
- assert!(crate::widget::WidgetHostExt::extra_quads(&bar).is_empty(), "no plain bg quad leaks to flat hosts");
+ assert!(crate::widget::shown_quads(&bar).is_empty(), "no plain bg quad leaks to flat hosts");
}
#[test]
diff --git a/src/widget/input/button.rs b/src/widget/input/button.rs
index 8413391..673d342 100644
--- a/src/widget/input/button.rs
+++ b/src/widget/input/button.rs
@@ -840,14 +840,13 @@ mod tests {
/// radius, and the label through the prim-derived text bridge with center justification.
#[test]
fn geometry_and_label_parity() {
- let ctx = UiContext::new();
// Pin the flat style: this test is about the legacy quad-bridge paths,
// which the config-default raised plate bypasses entirely.
let b = Button::new(0.0, 0.0, 100.0, 24.0).with_label("Go").with_raised(false);
let radius = crate::layout::button_corner_radius();
- let rounded = crate::widget::WidgetHostExt::all_rounded_quads(&b, &ctx);
- let plain = crate::widget::WidgetHostExt::extra_quads(&b);
+ let rounded = crate::widget::shown_rounded_quads(&b);
+ let plain = crate::widget::shown_quads(&b);
if radius > 0.0 {
assert!(!rounded.is_empty() && plain.is_empty(), "rounded config -> rounded path only");
assert_eq!(rounded[0].4, radius);
diff --git a/src/widget/input/checkbox.rs b/src/widget/input/checkbox.rs
index d6362f8..229b22d 100644
--- a/src/widget/input/checkbox.rs
+++ b/src/widget/input/checkbox.rs
@@ -707,7 +707,10 @@ mod tests {
let (fcx, fcy) = (f.rect.x + f.rect.width * 0.5, f.rect.y + f.rect.height * 0.5);
assert!((pcx - fcx).abs() < 1e-3 && (pcy - fcy).abs() < 1e-3, "centred");
assert!(!p.has_well(), "the plate is all run");
- assert!(crate::widget::WidgetHostExt::extra_circles(&cb).is_empty(), "no mark");
+ assert!(
+ !crate::widget::shown_prims(&cb).iter().any(|p| matches!(p, crate::scene::paint::Prim::Circle { .. })),
+ "no mark"
+ );
if crate::layout::control_relief() {
cb.inner_mut().set_checked(false);
diff --git a/src/widget/input/color_selector.rs b/src/widget/input/color_selector.rs
index 061d750..7f6ad90 100644
--- a/src/widget/input/color_selector.rs
+++ b/src/widget/input/color_selector.rs
@@ -1094,7 +1094,7 @@ mod tests {
!items.iter().any(|it| matches!(it.prim, Prim::Quad { color, .. } if color == [1.0; 4])),
"no cell is a plain quad, so the legacy plain view cannot carry it"
);
- let plain = crate::widget::WidgetHostExt::extra_quads(&cs);
+ let plain = crate::widget::shown_quads(&cs);
assert!(plain.iter().all(|q| q.4 != [1.0, 1.0, 1.0, 1.0]), "the plain view has no white cells: {plain:?}");
}
diff --git a/src/widget/input/ramp.rs b/src/widget/input/ramp.rs
index f137029..93cec65 100644
--- a/src/widget/input/ramp.rs
+++ b/src/widget/input/ramp.rs
@@ -385,11 +385,10 @@ impl Paint for ColorRamp {
}
if self.selected_key_idx.is_some() {
- let ctx_dummy = crate::context::UiContext::new();
- quads.extend(self.r_slider.all_quads(&ctx_dummy));
- quads.extend(self.g_slider.all_quads(&ctx_dummy));
- quads.extend(self.b_slider.all_quads(&ctx_dummy));
- quads.extend(self.del_button.all_quads(&ctx_dummy));
+ quads.extend(crate::widget::shown_quads(&self.r_slider));
+ quads.extend(crate::widget::shown_quads(&self.g_slider));
+ quads.extend(crate::widget::shown_quads(&self.b_slider));
+ quads.extend(crate::widget::shown_quads(&self.del_button));
}
quads
diff --git a/src/widget/input/slider.rs b/src/widget/input/slider.rs
index ba11444..6d5dc20 100644
--- a/src/widget/input/slider.rs
+++ b/src/widget/input/slider.rs
@@ -1120,12 +1120,11 @@ mod tests {
fn probe_slider_bridge() {
- let ctx = UiContext::new();
let mut sl = Slider::new().with_label("Slider");
WidgetHost::set_rect(&mut sl, 20.0, 220.0, 200.0, 40.0);
eprintln!("rect = {:?}", WidgetHost::rect(&sl));
- eprintln!("extra_quads = {:?}", crate::widget::WidgetHostExt::extra_quads(&sl));
- eprintln!("rounded = {:?}", crate::widget::WidgetHostExt::all_rounded_quads(&sl, &ctx));
+ eprintln!("quads = {:?}", crate::widget::shown_quads(&sl));
+ eprintln!("rounded = {:?}", crate::widget::shown_rounded_quads(&sl));
eprintln!("labels = {:?}", sl.own_text_labels().iter().map(|l| l.text.clone()).collect::<Vec<_>>());
}
diff --git a/src/widget/input/text_box.rs b/src/widget/input/text_box.rs
index 91dca63..ce588db 100644
--- a/src/widget/input/text_box.rs
+++ b/src/widget/input/text_box.rs
@@ -2811,24 +2811,17 @@ mod tests {
#[test]
fn test_textbox_multiline_selection_highlight() {
- let dummy = crate::context::UiContext::new();
let mut tb = TextBox::new("Line 1\nLine 2\nLine 3".to_string()).with_multiline(true);
tb.set_rect(10.0, 10.0, 200.0, 100.0);
tb.select_anchor = Some(7); // starts at "Line 2"
tb.cursor_idx = 13; // ends at end of "Line 2"
- let has_rounded = crate::widget::WidgetHostExt::corner_style(&tb).1 != (false, false, false, false);
- let has_highlight = if has_rounded {
- let rounded = tb.all_rounded_quads(&dummy);
- println!("Rounded quads: {:?}", rounded);
- let quads = tb.all_quads(&dummy);
- rounded.iter().any(|q| q.5 == [0.20, 0.50, 0.85, 0.3])
- || quads.iter().any(|q| q.4 == [0.20, 0.50, 0.85, 0.3])
- } else {
- let extra = tb.extra_quads();
- println!("Extra quads: {:?}", extra);
- extra.iter().any(|q| q.4 == [0.20, 0.50, 0.85, 0.3])
- };
+ use crate::scene::paint::Prim;
+ let highlight = [0.20, 0.50, 0.85, 0.3];
+ let has_highlight = crate::widget::shown_prims(&tb).iter().any(|p| match p {
+ Prim::Quad { color, .. } | Prim::RoundedRect { color, .. } => *color == highlight,
+ _ => false,
+ });
assert!(has_highlight, "Should have a highlight quad!");
}
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index 4da0d65..828f145 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -389,57 +389,36 @@ pub trait WidgetHost {
/// Emit this widget's OWN primitives (non-recursive) into the single paint pass (Phase 3).
- /// The default composes the pieces the legacy recursive `all_*` emit for one node: rounded
- /// background, plain/decoration quads, circles, and own text. Widgets with richer painting
- /// (borders, relief primitives, arcs, vectors, SVGs) can override. Recursion into children and clipping
- /// are handled by the paint walk (`scene::painter`), not here.
+ /// Every production host overrides this (`Adapted`, and `Owned` forwarding to it); the
+ /// default serves a host with no widget of its own (the test shims): its plate — a solid
+ /// border, or a rounded fill in its colour where its corner style rounds — then what its
+ /// model paints, text aside. Recursion into children and clipping are the paint walk's
+ /// (`scene::painter`), not this.
fn paint_self(&self, ui: &UiContext, ctx: &mut crate::scene::paint::PaintCtx) {
use crate::scene::layout::Rect;
+ use crate::scene::paint::{PaintCtx, Prim};
let (x, y, w, h) = self.rect();
let rect = Rect { x, y, width: w, height: h };
let color = self.color();
-
- if ui.tree.children_ptrs(self.base().id()).is_empty() {
- // Leaf: its rounded background, from its colour and corner style, then the rounded
- // quads its model paints. (Every production host overrides `paint_self`; this
- // default serves a host without a model of its own.)
- let (radius, corners) = self.corner_style();
+ if let Some((border_color, thickness)) = self.solid_border() {
+ let cr = self.corner_radii();
+ ctx.border(rect, (cr.top_left, cr.top_right, cr.bottom_right, cr.bottom_left), color, border_color, thickness);
+ } else if let Some((radius, corners)) = self.paint_model().corner_style(self.content_rect()) {
if corners != (false, false, false, false) && color[3].abs() > 0.001 {
ctx.rounded_rect(rect, radius, corners, color);
}
- for (qx, qy, qw, qh, r, c, corners) in self.all_rounded_quads(ui) {
- ctx.rounded_rect(Rect { x: qx, y: qy, width: qw, height: qh }, r, corners, c);
- }
- } else {
- // Container: reconstruct its own plate (bevel / border / rounded background) — mirrors
- // `push_widget_vertices`. Its children are drawn by the paint walk, so we must NOT call
- // `all_rounded_quads` here (that would recurse and double-draw them).
- let cr = self.corner_radii();
- let radii = (cr.top_left, cr.top_right, cr.bottom_right, cr.bottom_left);
- if let Some((border_color, thickness)) = self.solid_border() {
- ctx.border(rect, radii, color, border_color, thickness);
- } else if color[3].abs() > 0.001 {
- let (radius, (r1, r2, r3, r4)) = self.corner_style();
- if r1 || r2 || r3 || r4 {
- ctx.rounded_rect(rect, radius, (r1, r2, r3, r4), color);
- }
- }
}
-
- for (qx, qy, qw, qh, c) in self.all_quads(ui) {
- ctx.quad(Rect { x: qx, y: qy, width: qw, height: qh }, c);
- }
- for (cx, cy, r, t, start, end, c) in self.extra_arcs() {
- ctx.arc(cx, cy, r, t, start, end, c);
+ if !self.visible() {
+ return;
}
- for (cx, cy, r, c) in self.extra_circles() {
- ctx.circle(cx, cy, r, c);
+ // Text: none. A host with text of its own overrides this; emitting the model's text
+ // here would double what the walk's descent draws for a container (the Phase 6d trap).
+ let mut tmp = PaintCtx::new();
+ self.paint_model().paint_ui(ui, self.content_rect(), &mut tmp);
+ for item in tmp.finish().items {
+ // `replay` emits every prim but Text, which it hands back; dropping it is the point.
+ let _text: Option<Prim> = ctx.replay(item.prim);
}
- // Text: NONE by default. Every live legacy widget with own text carries a
- // paint_self override (most via `scene::painter::paint_legacy_leaf` + its own
- // labels); containers' text_labels aggregates are covered by the walk's descent
- // (emitting them here would double-draw every descendant's text — the Phase 6d
- // trap). Migrated widgets go through `Adapted::paint_self`, never this default.
}
@@ -448,8 +427,7 @@ pub trait WidgetHost {
// The per-widget text getters (text_labels / text_labels_with_bounds /
// text_labels_with_font_and_bounds / get_text_items) are GONE: every widget emits
- // its own text as display-list prims via paint_self (Adapted::paint_self for
- // migrated widgets; paint_legacy_leaf-based overrides for the legacy leaves). The
+ // its own text as display-list prims via paint_self (Adapted::paint_self). The
// deleted default's base-label synthesis lives on in Adapted's base-label fallback,
// and its scroll-ancestor clamp in scene::painter::scroll_ancestor_text_bounds.
@@ -616,7 +594,8 @@ pub trait WidgetHostExt: WidgetHost {
}
fn corner_radii(&self) -> CornerRadii {
- let (r, (tl, tr, br, bl)) = self.corner_style();
+ let (r, (tl, tr, br, bl)) =
+ self.paint_model().corner_style(self.content_rect()).unwrap_or((0.0, (false, false, false, false)));
CornerRadii::new(
if tl { r } else { 0.0 },
if tr { r } else { 0.0 },
@@ -640,14 +619,11 @@ pub trait WidgetHostExt: WidgetHost {
}
}
- // ── The legacy tuple views ──────────────────────────────────────────────────
- // What a widget paints, projected onto the pre-display-list surface: plain quads,
- // rounded quads, arcs, circles, the shared highlight, the corner style. Hosts that
- // still draw a widget through these rather than `paint_self` — the designer's render
- // loop, the gallery, the display manager, the settings app's flat collector,
- // cce-secrets, the flat-host bridge — read them here. They were `WidgetHost` methods
- // until 2026-10-08, each implemented by `Adapted` from its widget's `Paint`; they are
- // computed from the models now, the same way.
+ // ── What the widget paints, read from its model ──────────────────────────────
+ // The legacy tuple views (`extra_quads`, `all_quads`, `all_rounded_quads`,
+ // `extra_arcs`, `extra_circles`, `highlight_quad`, `corner_style`) are gone since
+ // 2026-10-08: every host paints a widget through `paint_self` / the paint walk, and a
+ // composite that draws a child's chrome in its own order reads `painted_prims`.
/// The rect the widget's model paints into: the host's rect below its detached label.
fn content_rect(&self) -> crate::scene::layout::Rect {
@@ -674,159 +650,43 @@ pub trait WidgetHostExt: WidgetHost {
}
model.container_children().into_iter().filter(|c| model.child_visible(*c)).collect()
}
+}
- /// The corner radius and which corners it rounds; 12 and none for a widget without a
- /// corner style.
- fn corner_style(&self) -> (f32, (bool, bool, bool, bool)) {
- self.paint_model().corner_style(self.content_rect()).unwrap_or((12.0, (false, false, false, false)))
- }
-
- /// The legacy hover/focus highlight over the row span: only for a widget that opts into
- /// it ([`Paint::legacy_focus_highlight`], the TextBox) or forwards one
- /// ([`Paint::forwarded_highlight`], the Paginator's ButtonStrip). Primary tint when
- /// focused, secondary when hovered.
- fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, f32, [f32; 4])> {
- if let Some(forwarded) = self.paint_model().forwarded_highlight(ctx) {
- return forwarded;
- }
- if !self.paint_model().legacy_focus_highlight() {
- return None;
- }
- let b = self.base();
- let hc = if ctx.is_focused_id(b.id()) {
- colors::highlight_primary_color()
- } else if b.hovered {
- colors::HIGHLIGHT_SECONDARY
- } else {
- return None;
- };
- let hx = if b.row_w > 0.0 { b.row_x } else { b.x };
- let hw = if b.row_w > 0.0 { b.row_w } else { b.w };
- Some((hx, b.y, hw, b.h, hc))
- }
-
- /// The widget's own plain-quad prims.
- fn own_plain_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
- self.painted_prims()
- .into_iter()
- .filter_map(|prim| match prim {
- crate::scene::paint::Prim::Quad { rect, color } => Some((rect.x, rect.y, rect.width, rect.height, color)),
- _ => None,
- })
- .collect()
- }
-
- /// The plain-quad view: a widget's own plain quads, or the legacy view it serves
- /// ([`Paint::legacy_plain_quads`]), or — for an aggregating container (the Paginator) —
- /// its visible children's.
- fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
- if !self.visible() {
- return Vec::new();
- }
- if self.paint_model().serves_legacy_plain_quads() {
- return self.paint_model().legacy_plain_quads(self.content_rect());
- }
- if self.paint_model().aggregates_child_extra_quads() {
- let mut out = Vec::new();
- for child in self.visible_children() {
- out.extend(unsafe { &*child }.extra_quads());
- }
- return out;
- }
- self.own_plain_quads()
- }
+impl<T: WidgetHost + ?Sized> WidgetHostExt for T {}
- /// Every plain quad the widget and its visible children draw, the opted-in highlight
- /// included (not the hover tint), a rounded child's background left to
- /// [`all_rounded_quads`](Self::all_rounded_quads). Empty for a widget serving the
- /// legacy plain-quad view, which reaches flat hosts through `all_rounded_quads` only.
- fn all_quads(&self, ctx: &UiContext) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
- if self.paint_model().serves_legacy_plain_quads() {
- return Vec::new();
- }
- let mut quads = if self.visible() { self.own_plain_quads() } else { Vec::new() };
- if self.paint_model().legacy_focus_highlight() {
- if let Some(hq) = self.highlight_quad(ctx) {
- if hq.4 != colors::HIGHLIGHT_SECONDARY {
- quads.push(hq);
- }
+/// A shown widget's prims as its model paints them, nothing while it is hidden — for a
+/// composite that draws a child's chrome in its own order rather than walking it (the params
+/// pane's rows, the ramp's key editor, the menubar's strip).
+pub(crate) fn shown_prims<W: WidgetHost + ?Sized>(w: &W) -> Vec<crate::scene::paint::Prim> {
+ if w.visible() { w.painted_prims() } else { Vec::new() }
+}
+
+/// The plain quads among [`shown_prims`], as `(x, y, w, h, colour)`.
+pub(crate) fn shown_quads<W: WidgetHost + ?Sized>(w: &W) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
+ shown_prims(w)
+ .into_iter()
+ .filter_map(|prim| match prim {
+ crate::scene::paint::Prim::Quad { rect, color } => Some((rect.x, rect.y, rect.width, rect.height, color)),
+ _ => None,
+ })
+ .collect()
+}
+
+/// The rounded quads among [`shown_prims`], as `(x, y, w, h, radius, colour, corners)`.
+pub(crate) fn shown_rounded_quads<W: WidgetHost + ?Sized>(
+ w: &W,
+) -> Vec<(f32, f32, f32, f32, f32, [f32; 4], (bool, bool, bool, bool))> {
+ shown_prims(w)
+ .into_iter()
+ .filter_map(|prim| match prim {
+ crate::scene::paint::Prim::RoundedRect { rect, radius, corners, color } => {
+ Some((rect.x, rect.y, rect.width, rect.height, radius, color, corners))
}
- }
- if self.visible() {
- for child in self.visible_children() {
- let widget = unsafe { &*child };
- let (wx, wy, ww, wh) = widget.rect();
- let has_rounded = widget.corner_style().1 != (false, false, false, false);
- for (qx, qy, qw, qh, qc) in widget.all_quads(ctx) {
- if has_rounded
- && (qx - wx).abs() < 0.1
- && (qy - wy).abs() < 0.1
- && (qw - ww).abs() < 0.1
- && (qh - wh).abs() < 0.1
- {
- continue;
- }
- quads.push((qx, qy, qw, qh, qc));
- }
- }
- }
- quads
- }
-
- /// Every rounded quad the widget and its visible children draw.
- fn all_rounded_quads(&self, ctx: &UiContext) -> Vec<(f32, f32, f32, f32, f32, [f32; 4], (bool, bool, bool, bool))> {
- if !self.visible() {
- return Vec::new();
- }
- let mut out: Vec<_> = self
- .painted_prims()
- .into_iter()
- .filter_map(|prim| match prim {
- crate::scene::paint::Prim::RoundedRect { rect, radius, corners, color } => {
- Some((rect.x, rect.y, rect.width, rect.height, radius, color, corners))
- }
- _ => None,
- })
- .collect();
- for child in self.visible_children() {
- out.extend(unsafe { &*child }.all_rounded_quads(ctx));
- }
- out
- }
-
- /// The widget's arc prims.
- fn extra_arcs(&self) -> Vec<(f32, f32, f32, f32, f32, f32, [f32; 4])> {
- if !self.visible() {
- return Vec::new();
- }
- self.painted_prims()
- .into_iter()
- .filter_map(|prim| match prim {
- crate::scene::paint::Prim::Arc { cx, cy, radius, thickness, start, end, color } => {
- Some((cx, cy, radius, thickness, start, end, color))
- }
- _ => None,
- })
- .collect()
- }
-
- /// The widget's circle prims.
- fn extra_circles(&self) -> Vec<(f32, f32, f32, [f32; 4])> {
- if !self.visible() {
- return Vec::new();
- }
- self.painted_prims()
- .into_iter()
- .filter_map(|prim| match prim {
- crate::scene::paint::Prim::Circle { cx, cy, radius, color } => Some((cx, cy, radius, color)),
- _ => None,
- })
- .collect()
- }
+ _ => None,
+ })
+ .collect()
}
-impl<T: WidgetHost + ?Sized> WidgetHostExt for T {}
-
/// The narrow traits' defaults, for a host that has no widget model of its own (the test
/// shims that implement `WidgetHost` directly): transparent, no focus role, no value.
pub struct NoModel;
diff --git a/src/widget/model.rs b/src/widget/model.rs
index 4993467..ab43e38 100644
--- a/src/widget/model.rs
+++ b/src/widget/model.rs
@@ -175,10 +175,9 @@ pub trait Paint {
/// Corner rounding `(radius, per-corner flags)` of the widget's background, given its
/// laid-out rect (MenuBar's corners depend on where it sits against its parent's edges).
- /// **Transitional:** this exists only for legacy render paths that draw widget backgrounds
- /// themselves from style properties (`widget_vertices` / `push_widget_vertices` readers of
- /// `WidgetHost::corner_radius` + `rounded_corners`) — the widget's real geometry is whatever
- /// [`paint`](Paint::paint) emits. Dies with those paths. Default: sharp corners.
+ /// What a host drawing the widget's PLATE reads — `WidgetHostExt::corner_radii`, the
+ /// designer's `append_widget_plate`, the flat-host bridge; the widget's own geometry is
+ /// whatever [`paint`](Paint::paint) emits. Default: sharp corners.
fn corner_style(&self, _rect: Rect) -> Option<(f32, (bool, bool, bool, bool))> {
None
}
@@ -231,25 +230,6 @@ pub trait Paint {
/// base-label machinery.
fn sync_label(&mut self, _label: &str) {}
- // --- Legacy dual-geometry escape hatch (transitional; Graph is the only user). Legacy
- // hosts read DIFFERENT getters: the designer's raw render path draws `extra_quads` as
- // PLAIN quads, while `render_widget` and the scene walk consume the rounded view
- // (`all_rounded_quads` / `paint_self`). Legacy Graph served both by overriding all three
- // getters. A migrated widget emits the rounded view from `paint`; when it also serves a
- // plain view, the adapter returns it verbatim from `extra_quads` and empties `all_quads`
- // (mirroring legacy Graph's highlight-only override) so render_widget-style hosts that
- // read BOTH getters never draw the geometry twice. Dies with `WidgetHost`.
-
- /// Whether this widget serves [`legacy_plain_quads`](Paint::legacy_plain_quads).
- fn serves_legacy_plain_quads(&self) -> bool {
- false
- }
-
- /// The plain-quad view of this widget's geometry for legacy `extra_quads` readers.
- fn legacy_plain_quads(&self, _rect: Rect) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
- Vec::new()
- }
-
/// Clip rect `[x1, y1, x2, y2]` for this widget's text on the legacy bounded-text paths
/// (`text_labels_with_bounds` / `text_labels_with_font_and_bounds`). `None` (default) keeps
/// the legacy behavior: unbounded, except inside a scroll ancestor. Graph clips its node
@@ -274,38 +254,13 @@ pub trait Paint {
}
- /// Whether the adapter re-enables the legacy shared focus/hover highlight overlay
- /// (`WidgetHost::highlight_quad`'s default) for this widget. The adapter suppresses it for
- /// migrated widgets — matching the `None` overrides most legacy controls carried — but
- /// legacy TextBox kept the default: the focused editor gets the primary-highlight tint
- /// over its background (data-editor's teal editing wash). Default: suppressed.
+ /// Whether the widget wears the shared focus highlight: the primary tint over its row
+ /// span while it holds focus, drawn by the adapter over its background. Only the TextBox
+ /// opts in (the focused editor's teal wash in the data editor). Default: none.
fn legacy_focus_highlight(&self) -> bool {
false
}
- /// Legacy container `extra_quads` aggregation: when `true`, the adapter's `extra_quads`
- /// serves the visible children's `extra_quads` — and ONLY those, like the legacy container
- /// overrides (Paginator returned its strip's + selected page's chrome; its own background
- /// quad lived in `all_quads` alone). Hosts that render a container through the plain
- /// `extra_quads` getter (cce-mail's and cce-layout-interface's sidebar draw) read exactly
- /// this view. The widget's own [`paint`](Paint::paint) prims still reach `all_quads` and
- /// the scene walk. Default: off (a leaf's `extra_quads` is its own prims).
- fn aggregates_child_extra_quads(&self) -> bool {
- false
- }
-
- /// Forward the legacy `WidgetHost::highlight_quad` to somewhere else entirely — Paginator
- /// served its ButtonStrip's highlight (the hovered-tab tint cce-layout-interface draws by
- /// calling `highlight_quad` directly). Outer `Some` replaces the adapter's highlight logic
- /// with the inner value; `None` (default) keeps the standard behavior
- /// ([`legacy_focus_highlight`](Paint::legacy_focus_highlight)). A forwarded highlight is
- /// served ONLY through the direct `highlight_quad` getter — the adapter keeps it out of
- /// `all_quads`/`paint_self`, where the child's own aggregation already carries it (legacy
- /// containers likewise excluded it from their `all_quads` overrides).
- fn forwarded_highlight(&self, _ctx: &UiContext) -> Option<Option<(f32, f32, f32, f32, [f32; 4])>> {
- None
- }
-
/// Whether this widget serves
/// [`legacy_labels_with_font_and_bounds`](Paint::legacy_labels_with_font_and_bounds) —
/// the text sibling of the dual-geometry escape hatch. The adapter's standard text bridge
@@ -1297,15 +1252,12 @@ impl<W: Layout + Paint + Input + 'static> WidgetHost for Adapted<W> {
ctx.pop_clip_circle();
}
}
- // The legacy default `paint_self` drained `all_quads`, which carries the focus
- // highlight — replicate for opt-in widgets, over the background (same draw order).
- // Forwarded highlights stay out: the paint walk reaches the owning child itself.
- if Paint::legacy_focus_highlight(&self.inner) {
- if let Some((hx, hy, hw, hh, hc)) = crate::widget::WidgetHostExt::highlight_quad(self, ui) {
- if hc != crate::colors::HIGHLIGHT_SECONDARY {
- ctx.quad(Rect { x: hx, y: hy, width: hw, height: hh }, hc);
- }
- }
+ // The focus highlight a widget opts into (the TextBox): the primary tint over the
+ // row span while focused, over the background. (The hover tint never drew here.)
+ if Paint::legacy_focus_highlight(&self.inner) && ui.is_focused_id(self.base.id()) {
+ let b = &self.base;
+ let (hx, hw) = if b.row_w > 0.0 { (b.row_x, b.row_w) } else { (b.x, b.w) };
+ ctx.quad(Rect { x: hx, y: b.y, width: hw, height: b.h }, crate::colors::highlight_primary_color());
}
// Own text with per-label font+bounds: the hatch view verbatim for hatched widgets
// (caveat: its contract includes raw container children — those few widgets keep the
@@ -1346,13 +1298,6 @@ impl<W: Layout + Paint + Input + 'static> WidgetHost for Adapted<W> {
// detached base label), and composites that need a concrete Adapted child's labels
// call `own_labels_with_font_and_bounds` directly (pub(crate)).
- // --- Reverse bridges: [`Paint::paint`] output converted back to the legacy geometry
- // getters external render loops read (cce-test-interface's `all_*` calls, `render_widget`'s
- // `all_quads` loop, the demo's `extra_*` loops). Each prim kind maps to the getter legacy
- // widgets used for it — plain quads to `extra_quads` (→ `all_quads`), rounded to
- // `all_rounded_quads` — so apps that read BOTH getters draw each prim exactly once. Covers
- // the widget's OWN geometry only: adapted widgets are leaves for now; recursion belongs to
- // `scene::painter`.