GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
refactor(widget): WidgetHost 57 -> 38; what the widget model answers is WidgetHostExt
Most of what Adapted implemented on WidgetHost was a one-line forward to its
widget's narrow traits. Those traits are dyn-compatible, so the host now hands
its widget out as them (layout_model / paint_model / input_model /
input_model_mut; Adapted returns its inner widget, a test shim gets NoModel's
defaults) and WidgetHostExt, blanket-implemented for every host (dyn included),
carries the 19 forwards (focus_role, keeps_tab, 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) and 3 derivations (label,
corner_radii, mark_dirty). Call sites read as before with the extension trait
in scope. plate_bevel is gone: nothing overrode it, so it was always None.
What stays on the trait adds host state to the model's answer.
Checked: the workspace builds, every touched crate's tests pass, and the
designer, the demo, the gallery, cce-files, the data editor and the settings
app draw identically to the pixel before and after in a scale-2 shadow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CLAUDE.md | 20 +++-
docs/rfc-core-rebuild.md | 10 ++
src/a11y.rs | 2 +-
src/backend/a11y_unix.rs | 1 +
src/backend/driver.rs | 2 +-
src/backend/tessellate.rs | 15 +--
src/context.rs | 7 +-
src/layout/bridge.rs | 1 +
src/layout/mod.rs | 6 -
src/layout/section.rs | 1 +
src/scene/arena.rs | 16 ++-
src/scene/painter.rs | 30 +++--
src/scene/tree.rs | 3 -
src/widget/container/parameters_bg.rs | 2 +-
src/widget/container/spreadsheet.rs | 8 +-
src/widget/core.rs | 12 +-
src/widget/display/label.rs | 2 +-
src/widget/display/progress_bar.rs | 2 +-
src/widget/display/separator.rs | 4 +-
src/widget/display/status_bar.rs | 2 +-
src/widget/display/status_dot.rs | 2 +-
src/widget/input/checkbox.rs | 2 +-
src/widget/input/dropdown.rs | 2 +-
src/widget/input/text_box.rs | 10 +-
src/widget/mod.rs | 208 ++++++++++++++++++++++++----------
src/widget/model.rs | 79 +++----------
src/widget/owned.rs | 34 +-----
27 files changed, 258 insertions(+), 225 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index 68b0e29..af4bf1e 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1671,7 +1671,7 @@ Modules:
### `WidgetHost` (formerly the `Element` god-trait)
-`WidgetHost` (`src/widget/mod.rs`) is the single ~52-method host surface the machinery
+`WidgetHost` (`src/widget/mod.rs`) is the single 38-method host surface the machinery
(context routing, paint walk, render loop, app dyn broadcasts) sees, produced by the RFC's 6bd
shrink-then-rename of the old ~125-method `Element` god-trait. Its ONE production implementor
is `Adapted<W>`; concrete widget behavior lives on the narrow `Layout`/`Paint`/`Input` traits
@@ -1681,6 +1681,22 @@ are GONE from the trait — events route through `handle_event`, and apps drain
through the concrete inherent `Adapted<W>` methods. See the RFC's blueprint notes before
adding anything to this trait.
+**What a host's widget model answers is not a trait slot** (since 2026-10-08, 57 → 38).
+The host hands out its widget as its narrow traits — `layout_model()`, `paint_model()`,
+`input_model()` / `input_model_mut()` (`Adapted` returns its inner widget; a test shim that
+implements `WidgetHost` directly gets `NoModel`'s defaults, or returns itself after
+implementing the narrow trait it needs) — and `WidgetHostExt`, blanket-implemented for every
+host, `dyn` included, carries what used to be one-line forwards: `focus_role`, `keeps_tab`,
+`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`. Call them as before, with
+`cce_ui::widget::WidgetHostExt` in scope. 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
+forwarding.
+
### Global state has a plan (`docs/rfc-global-state.md`)
About 300 statics and 18 thread-locals: style (≈200 `RwLock`s beside the style registry),
@@ -1720,7 +1736,7 @@ while that token exists.
**App widgets live in `Owned` boxes** (`widget::Owned<W>`, since 2026-10-07). An `Owned` keeps
the widget in a heap allocation of its own and carries a token for that ALLOCATION. Moving the
`Owned` (a `Vec` reallocating, a struct returned by value) does not move the widget, and the token
-dies only when the box is freed. `Owned` is itself a `WidgetHost`, forwarding every method, and
+dies only when the box is freed. `Owned` is itself a `WidgetHost`, forwarding every trait method, and
reports the boxed widget through `WidgetHost::stable_target`. So `register_host(&mut self.x)`,
`set_focused`, `render_widget` and `link_parent_child` all record the boxed widget and the box's
token without the caller doing anything. `Deref`/`DerefMut` reach the widget, so
diff --git a/docs/rfc-core-rebuild.md b/docs/rfc-core-rebuild.md
index 4b5d7d0..e215d0e 100644
--- a/docs/rfc-core-rebuild.md
+++ b/docs/rfc-core-rebuild.md
@@ -2073,6 +2073,16 @@ 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.
+ - **WidgetHost 57 → 38 (2026-10-08).** The trait had grown back past the blueprint's ~55
+ with the keyboard and accessibility hooks, and most of what `Adapted` implemented was a
+ one-line forward to a narrow trait. The narrow traits are dyn-compatible, so the host now
+ hands its widget out as them (`layout_model` / `paint_model` / `input_model` /
+ `input_model_mut`) and `WidgetHostExt` (blanket over every host, `dyn` included) carries
+ the 19 forwards and 3 derivations (`label`, `corner_radii`, `mark_dirty`) as provided
+ methods; `plate_bevel`, never overridden and so always `None`, went. What stays on the
+ trait adds host state to the model's answer. Apps changed only by importing the
+ extension trait; the designer, the demo, the gallery, cce-files, the data editor and
+ the settings app drew identically to the pixel before and after (scale-2 shadow).
- **The `Element` endgame design (6bd, decided 2026-07-12).** The
endgame is a **trait replacement, not an app rewrite**. Grounding
facts (consumer survey): direct per-method dispatch
diff --git a/src/a11y.rs b/src/a11y.rs
index d702292..4a8836f 100644
--- a/src/a11y.rs
+++ b/src/a11y.rs
@@ -34,7 +34,7 @@
use accesskit::{Action, Affine, Node, NodeId, Rect, Role, Toggled, TreeId, TreeInfo, TreeUpdate};
use crate::context::UiContext;
-use crate::widget::{FocusRole, NamedKey, WidgetHost, WidgetId};
+use crate::widget::{FocusRole, NamedKey, WidgetHost, WidgetId, WidgetHostExt};
/// The window's node, the root every widget hangs from.
pub const WINDOW: NodeId = NodeId(0);
diff --git a/src/backend/a11y_unix.rs b/src/backend/a11y_unix.rs
index ee66de8..6327926 100644
--- a/src/backend/a11y_unix.rs
+++ b/src/backend/a11y_unix.rs
@@ -20,6 +20,7 @@
//! Without the `a11y` feature the same API compiles to a stub whose [`Publisher::start`] is
//! `None`, so the runner carries no `cfg`.
+use crate::widget::WidgetHostExt;
use accesskit::{Action, ActionData, ActionRequest};
use crate::widget::NamedKey;
diff --git a/src/backend/driver.rs b/src/backend/driver.rs
index 1838bf0..b8bf2d5 100644
--- a/src/backend/driver.rs
+++ b/src/backend/driver.rs
@@ -17,7 +17,7 @@ use web_time::Instant;
use super::app::{Application, LogicalPosition, LogicalSize};
use cursor_icon::CursorIcon;
-use crate::widget::{ElementState, Key, KeyEvent, MouseButton, MouseScrollDelta, NamedKey, Position, ScrollPhase};
+use crate::widget::{ElementState, Key, KeyEvent, MouseButton, MouseScrollDelta, NamedKey, Position, ScrollPhase, WidgetHostExt};
/// A key held down, for the runner's own key repeat.
pub struct PressedKey {
diff --git a/src/backend/tessellate.rs b/src/backend/tessellate.rs
index 59196f7..288878d 100644
--- a/src/backend/tessellate.rs
+++ b/src/backend/tessellate.rs
@@ -4,6 +4,7 @@
//! draws and knows nothing of the window system; moved out of
//! `window_runner` so another shell can share it.
+use crate::widget::WidgetHostExt;
use crate::widget::WidgetHost;
use crate::draw::Batch2D;
@@ -1248,17 +1249,9 @@ pub fn widget_vertices(w: &dyn crate::widget::WidgetHost, sw: f32, sh: f32, clip
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();
- if let Some(thickness) = w.plate_bevel() {
- let t = thickness;
- // Full-size fill: the bevel lip is a shading overlay now, not a paint of the
- // outer ring, so an inset fill would leave the ring unfilled.
- push_rounded_rect_vertices_corners(x, y, ww, h, radii, sw, sh, w.color(), clip_circle, None, out);
- push_plate_bevel_vertices(x, y, ww, h, radii.top_left, t, sw, sh, w.color(), clip_circle, out);
- } else {
- 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);
- }
+ 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() {
diff --git a/src/context.rs b/src/context.rs
index 5453696..4e40643 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -1,5 +1,5 @@
use std::collections::HashMap;
-use crate::widget::{WidgetHost, WidgetId, Key, NamedKey, MouseButton, ElementState, Event};
+use crate::widget::{WidgetHost, WidgetId, Key, NamedKey, MouseButton, ElementState, Event, WidgetHostExt};
pub struct SpatialGrid {
pub cell_size: f32,
@@ -1508,9 +1508,6 @@ mod tests {
}
impl WidgetHost for Block {
crate::impl_widget_base!(Block);
- fn color(&self) -> [f32; 4] {
- [0.0, 0.0, 0.0, 1.0]
- }
}
/// `drag_allowed_at` — the window-drag question: allowed on empty surface, denied over a
@@ -1570,7 +1567,7 @@ mod focus_step_tests {
// A widget with no role is not a stop.
let mut sep = crate::widget::Separator::new(0.0, 0.0, 10.0, 1.0, [1.0; 4]);
WidgetHost::set_rect(&mut sep, 300.0, 10.0, 10.0, 1.0);
- assert_eq!(WidgetHost::focus_role(&sep), crate::widget::FocusRole::None);
+ assert_eq!(crate::widget::WidgetHostExt::focus_role(&sep), crate::widget::FocusRole::None);
// A group's members walk together, where the group's first member falls:
// grouping a and t (skipping b, which sits between them in reading order)
diff --git a/src/layout/bridge.rs b/src/layout/bridge.rs
index 38f6e41..0a2d707 100644
--- a/src/layout/bridge.rs
+++ b/src/layout/bridge.rs
@@ -3,6 +3,7 @@
//! onto one, and the carve and popover types that cross it. Legacy: new code paints
//! through `scene::paint::PaintCtx`.
+use crate::widget::WidgetHostExt;
use crate::widget::WidgetHost;
use crate::context::UiContext;
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 5496bde..9597ab0 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -2004,9 +2004,6 @@ mod tests {
self.w = w;
self.h = h;
}
- fn color(&self) -> [f32; 4] {
- [0.0, 0.0, 0.0, 0.0]
- }
}
struct MockWidgetWithLabel {
@@ -2026,9 +2023,6 @@ mod tests {
self.base.w = w;
self.base.h = (h - offset).max(0.0);
}
- fn color(&self) -> [f32; 4] {
- [0.0, 0.0, 0.0, 0.0]
- }
}
/// The vstack flow is checked against the LIVE style — the label margin, the
diff --git a/src/layout/section.rs b/src/layout/section.rs
index ea342e8..9fe37c6 100644
--- a/src/layout/section.rs
+++ b/src/layout/section.rs
@@ -14,6 +14,7 @@
//! box and a one- or two-column grid for the widgets that do not span it. Its geometry is the
//! settings app's, and documented on each placer.
+use crate::widget::WidgetHostExt;
use super::*;
use crate::widget::WidgetHost;
use crate::context::UiContext;
diff --git a/src/scene/arena.rs b/src/scene/arena.rs
index 315f316..780dcee 100644
--- a/src/scene/arena.rs
+++ b/src/scene/arena.rs
@@ -567,21 +567,27 @@ mod tests {
fn holds_and_trees_real_dyn_element_payloads() {
use crate::widget::WidgetHost;
- // A minimal real `WidgetHost` — `color` is the trait's only required method, everything
- // else is defaulted, so this exercises the actual trait object without dragging in a
- // heavyweight widget constructor.
+ // A minimal real `WidgetHost` — its base and a paint model are all it supplies,
+ // everything else is defaulted, so this exercises the actual trait object without
+ // dragging in a heavyweight widget constructor.
+ use crate::widget::WidgetHostExt;
struct Marker {
base: crate::widget::Widget,
tint: [f32; 4],
painted: std::cell::Cell<bool>,
}
- impl WidgetHost for Marker {
- crate::impl_widget_base!(Marker);
+ impl crate::widget::Paint for Marker {
fn color(&self) -> [f32; 4] {
self.painted.set(true);
self.tint
}
}
+ impl WidgetHost for Marker {
+ crate::impl_widget_base!(Marker);
+ fn paint_model(&self) -> &dyn crate::widget::Paint {
+ self
+ }
+ }
let mut arena: Arena<Box<dyn WidgetHost>> = Arena::new();
let root = arena.insert(Box::new(Marker { base: crate::widget::Widget::new(), tint: [1.0, 0.0, 0.0, 1.0], painted: false.into() }));
diff --git a/src/scene/painter.rs b/src/scene/painter.rs
index e5b4392..cae6c02 100644
--- a/src/scene/painter.rs
+++ b/src/scene/painter.rs
@@ -14,7 +14,7 @@
use crate::scene::layout::Rect;
use crate::scene::paint::{DisplayList, PaintCtx, Prim};
-use crate::widget::{WidgetHost, TextLabel, UiContext};
+use crate::widget::{WidgetHost, TextLabel, UiContext, WidgetHostExt};
/// Walk the widget subtree rooted at `root` and produce its ordered, clipped [`DisplayList`].
/// The walk only reads through the widgets; descent resolves children through the registry
@@ -112,9 +112,7 @@ pub fn append_widget_plate_radii(w: &dyn WidgetHost, pc: &mut PaintCtx, tint: Op
let (x, y, ww, h) = w.rect();
let rect = Rect { x, y, width: ww, height: h };
let tint = tint.unwrap_or([1.0, 1.0, 1.0]);
- if let Some(thickness) = w.plate_bevel() {
- pc.bevel_tinted(rect, radii_tuple, &crate::scene::material::Material::from_fill(w.color()), thickness, tint);
- } else if let Some((border_color, thickness)) = w.solid_border() {
+ if let Some((border_color, thickness)) = w.solid_border() {
if crate::layout::control_relief() {
pc.bevel_tinted(rect, radii_tuple, &crate::scene::material::Material::from_fill(w.color()), crate::colors::plate_bevel_width(), tint);
} else {
@@ -217,17 +215,22 @@ mod tests {
Box::new(P { base: Widget::new(), tag, clips: false, vis: true })
}
}
- impl WidgetHost for P {
- crate::impl_widget_base!(P);
+ impl crate::widget::Paint for P {
fn color(&self) -> [f32; 4] {
[self.tag, 0.0, 0.0, 1.0]
}
- fn visible(&self) -> bool {
- self.vis
- }
fn clips_children(&self) -> bool {
self.clips
}
+ }
+ impl WidgetHost for P {
+ crate::impl_widget_base!(P);
+ fn paint_model(&self) -> &dyn crate::widget::Paint {
+ self
+ }
+ fn visible(&self) -> bool {
+ self.vis
+ }
fn paint_self(&self, _ui: &UiContext, ctx: &mut PaintCtx) {
let (x, y, w, h) = self.rect();
ctx.quad(Rect { x, y, width: w, height: h }, [self.tag, 0.0, 0.0, 1.0]);
@@ -362,11 +365,16 @@ mod tests {
struct Rounded {
base: Widget,
}
- impl WidgetHost for Rounded {
- crate::impl_widget_base!(Rounded);
+ impl crate::widget::Paint for Rounded {
fn color(&self) -> [f32; 4] {
[0.2, 0.4, 0.6, 1.0]
}
+ }
+ impl WidgetHost for Rounded {
+ crate::impl_widget_base!(Rounded);
+ fn paint_model(&self) -> &dyn crate::widget::Paint {
+ self
+ }
fn corner_style(&self) -> (f32, (bool, bool, bool, bool)) {
(4.0, (true, true, true, true))
}
diff --git a/src/scene/tree.rs b/src/scene/tree.rs
index 859224a..1265b9b 100644
--- a/src/scene/tree.rs
+++ b/src/scene/tree.rs
@@ -280,9 +280,6 @@ mod tests {
}
impl WidgetHost for Marker {
crate::impl_widget_base!(Marker);
- fn color(&self) -> [f32; 4] {
- [0.0, 0.0, 0.0, 0.0]
- }
}
/// Owns marker widgets and hands out stable raw pointers + ids for them.
diff --git a/src/widget/container/parameters_bg.rs b/src/widget/container/parameters_bg.rs
index 6ce9836..670f38a 100644
--- a/src/widget/container/parameters_bg.rs
+++ b/src/widget/container/parameters_bg.rs
@@ -4764,7 +4764,7 @@ mod tests {
ParamController::set_display_params(&mut *p, &rows);
WidgetHost::set_rect(&mut p, 0.0, 0.0, 300.0, 200.0);
assert!(p.content_h > 200.0);
- assert!(WidgetHost::is_scrollable(&p));
+ assert!(crate::widget::WidgetHostExt::is_scrollable(&p));
// Wheel over the panel body but off every slider row's x-span is impossible (rows are
// full-width), so scroll via the region below the last visible row: use a y between
// rows (the 2px slack above a row) — simplest is the bottom padding strip.
diff --git a/src/widget/container/spreadsheet.rs b/src/widget/container/spreadsheet.rs
index 9546557..73acf35 100644
--- a/src/widget/container/spreadsheet.rs
+++ b/src/widget/container/spreadsheet.rs
@@ -1436,7 +1436,7 @@ mod tests {
assert_eq!(s.inner().selected_rows(), vec![2]);
// Ctrl adds and removes.
- WidgetHost::set_modifiers(&mut s, true, false, false);
+ crate::widget::WidgetHostExt::set_modifiers(&mut s, true, false, false);
s.handle_event(&body_click(row_y(0)), &mut ctx);
assert_eq!(s.inner().selected_rows(), vec![0, 2]);
s.handle_event(&body_click(row_y(2)), &mut ctx);
@@ -1444,12 +1444,12 @@ mod tests {
// Shift runs from the last row pressed without it (row 2, the ctrl
// press) to this one.
- WidgetHost::set_modifiers(&mut s, false, true, false);
+ crate::widget::WidgetHostExt::set_modifiers(&mut s, false, true, false);
s.handle_event(&body_click(row_y(0)), &mut ctx);
assert_eq!(s.inner().selected_rows(), vec![0, 1, 2]);
// A plain press on the one selected row clears it.
- WidgetHost::set_modifiers(&mut s, false, false, false);
+ crate::widget::WidgetHostExt::set_modifiers(&mut s, false, false, false);
s.handle_event(&body_click(row_y(3)), &mut ctx);
s.handle_event(&body_click(row_y(3)), &mut ctx);
assert!(s.inner().selected_rows().is_empty());
@@ -1483,7 +1483,7 @@ mod tests {
t.handle_event(&header_click(mid(&t, 0)), &mut ctx); // ascending: 2, 9, 10 = rows 2, 1, 0
t.handle_event(&body_click(row_y(0)), &mut ctx);
assert_eq!(t.inner().selected_rows(), vec![2], "the first row shown is the data's third");
- WidgetHost::set_modifiers(&mut t, false, true, false);
+ crate::widget::WidgetHostExt::set_modifiers(&mut t, false, true, false);
t.handle_event(&body_click(row_y(1)), &mut ctx);
assert_eq!(t.inner().selected_rows(), vec![1, 2]);
diff --git a/src/widget/core.rs b/src/widget/core.rs
index df70c8b..9991299 100644
--- a/src/widget/core.rs
+++ b/src/widget/core.rs
@@ -2623,16 +2623,18 @@ mod context_menu_action_tests {
base: Widget,
got: Option<ContextAction>,
}
- impl WidgetHost for Recorder {
- crate::impl_widget_base!(Recorder);
- fn color(&self) -> [f32; 4] {
- [0.0; 4]
- }
+ impl crate::widget::Input for Recorder {
fn context_action(&mut self, action: ContextAction) -> bool {
self.got = Some(action);
true
}
}
+ impl WidgetHost for Recorder {
+ crate::impl_widget_base!(Recorder);
+ fn input_model_mut(&mut self) -> &mut dyn crate::widget::Input {
+ self
+ }
+ }
fn press_row(ctx: &mut UiContext, idx: usize) {
let (x, y) = (context_menu::x() + 10.0, context_menu::row_y(idx) + ROW_H * 0.5);
diff --git a/src/widget/display/label.rs b/src/widget/display/label.rs
index 4e5d41e..7881020 100644
--- a/src/widget/display/label.rs
+++ b/src/widget/display/label.rs
@@ -120,7 +120,7 @@ mod tests {
let size = l.intrinsic_size().unwrap();
assert!(size.width > 0.0);
- assert!(!WidgetHost::blocks_root_plate_drag(&l));
+ assert!(!crate::widget::WidgetHostExt::blocks_root_plate_drag(&l));
}
}
diff --git a/src/widget/display/progress_bar.rs b/src/widget/display/progress_bar.rs
index dcac390..06a01ba 100644
--- a/src/widget/display/progress_bar.rs
+++ b/src/widget/display/progress_bar.rs
@@ -167,7 +167,7 @@ mod tests {
assert_eq!(quads[0].3, 8.0, "track keeps the assigned height");
// preferred_height is the content height; the label strip is `label_strip`.
- assert_eq!(WidgetHost::preferred_height(&bar), Some(crate::layout::progressbar_height()));
+ assert_eq!(crate::widget::WidgetHostExt::preferred_height(&bar), Some(crate::layout::progressbar_height()));
assert_eq!(WidgetHost::label_strip(&bar), offset);
// Runtime type-name matching still sees "ProgressBar", not Adapted<..>.
assert_eq!(WidgetHost::type_name(&bar), "ProgressBar");
diff --git a/src/widget/display/separator.rs b/src/widget/display/separator.rs
index 6280f2b..62d3f59 100644
--- a/src/widget/display/separator.rs
+++ b/src/widget/display/separator.rs
@@ -86,9 +86,9 @@ mod tests {
fn constructor_places_the_rect_and_bridge_emits_it() {
let sep = Separator::new(100.0, 0.0, 1.0, 24.0, [0.3, 0.3, 0.3, 1.0]);
assert_eq!(WidgetHost::rect(&sep), (100.0, 0.0, 1.0, 24.0));
- assert!(!WidgetHost::blocks_root_plate_drag(&sep));
+ assert!(!crate::widget::WidgetHostExt::blocks_root_plate_drag(&sep));
if !crate::layout::control_relief() {
- assert_eq!(WidgetHost::color(&sep), [0.3, 0.3, 0.3, 1.0]);
+ assert_eq!(crate::widget::WidgetHostExt::color(&sep), [0.3, 0.3, 0.3, 1.0]);
assert_eq!(WidgetHost::extra_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 f9f107e..b97a3a0 100644
--- a/src/widget/display/status_bar.rs
+++ b/src/widget/display/status_bar.rs
@@ -235,7 +235,7 @@ mod tests {
let extra = WidgetHost::extra_quads(&bar);
assert_eq!(extra.len(), 1, "cornerless bg quad");
assert_eq!(WidgetHost::corner_style(&bar).1, (false, false, false, false));
- assert!(!WidgetHost::blocks_root_plate_drag(&bar));
+ assert!(!crate::widget::WidgetHostExt::blocks_root_plate_drag(&bar));
}
/// The paint walk strips prim fonts and re-fonts labels via `Paint::text_font` — the
diff --git a/src/widget/display/status_dot.rs b/src/widget/display/status_dot.rs
index 3e9d221..8c0370e 100644
--- a/src/widget/display/status_dot.rs
+++ b/src/widget/display/status_dot.rs
@@ -85,7 +85,7 @@ mod tests {
);
assert!(WidgetHost::extra_quads(&dot).is_empty(), "nothing on the plain path (apps read both)");
// Drags pass through, as legacy declared.
- assert!(!WidgetHost::blocks_root_plate_drag(&dot));
+ assert!(!crate::widget::WidgetHostExt::blocks_root_plate_drag(&dot));
// State mutation through Deref, as call sites write it.
dot.set_status(DotStatus::Error);
assert_eq!(dot.status, DotStatus::Error);
diff --git a/src/widget/input/checkbox.rs b/src/widget/input/checkbox.rs
index fad06df..f980aea 100644
--- a/src/widget/input/checkbox.rs
+++ b/src/widget/input/checkbox.rs
@@ -795,7 +795,7 @@ mod tests {
assert!(painted(&t) != before, "toggling changes the emitted geometry");
// preferred_height forwards the legacy toggle height.
- assert_eq!(WidgetHost::preferred_height(&t), Some(crate::layout::toggle_height()));
+ assert_eq!(crate::widget::WidgetHostExt::preferred_height(&t), Some(crate::layout::toggle_height()));
}
/// The toggle is ONE field, the form a text row's picker and a spinbox's
diff --git a/src/widget/input/dropdown.rs b/src/widget/input/dropdown.rs
index 3b4b883..4362d36 100644
--- a/src/widget/input/dropdown.rs
+++ b/src/widget/input/dropdown.rs
@@ -1673,7 +1673,7 @@ mod tests {
dd.parent_snapshot = Some(ParentSnapshot {
rect: crate::widget::WidgetHost::rect(&ramp),
is_ramp: true,
- color: crate::widget::WidgetHost::color(&ramp),
+ color: crate::widget::WidgetHostExt::color(&ramp),
});
// Compute geometry
diff --git a/src/widget/input/text_box.rs b/src/widget/input/text_box.rs
index 9855300..a099575 100644
--- a/src/widget/input/text_box.rs
+++ b/src/widget/input/text_box.rs
@@ -2617,7 +2617,7 @@ mod tests {
assert_eq!(tb.history.undo_len(), 5);
// Undo through the ContextAction route, then the raw-chord route.
- assert!(WidgetHost::context_action(&mut tb, crate::widget::ContextAction::Undo));
+ assert!(crate::widget::WidgetHostExt::context_action(&mut tb, crate::widget::ContextAction::Undo));
assert_eq!(tb.edit_buffer, "hello worlXYd");
assert!(tb.keyboard_input(&key("z", true, false), &mut dummy));
assert_eq!(tb.edit_buffer, "hello world");
@@ -2633,7 +2633,7 @@ mod tests {
assert!(!tb.undo_edit(), "history exhausted");
// Redo forward one, then a fresh keystroke forks the branch.
- assert!(WidgetHost::context_action(&mut tb, crate::widget::ContextAction::Redo));
+ assert!(crate::widget::WidgetHostExt::context_action(&mut tb, crate::widget::ContextAction::Redo));
assert_eq!(tb.edit_buffer, "hello");
type_str(&mut tb, &mut dummy, "!");
assert_eq!(tb.edit_buffer, "hello!");
@@ -2642,7 +2642,7 @@ mod tests {
// A committed value is not the box's to undo.
tb.unfocus();
assert!(!tb.editing);
- assert!(!WidgetHost::context_action(&mut tb, crate::widget::ContextAction::Undo));
+ assert!(!crate::widget::WidgetHostExt::context_action(&mut tb, crate::widget::ContextAction::Undo));
}
#[test]
@@ -2870,7 +2870,7 @@ mod tests {
assert!(opts.contains(&"Clear".to_string()));
// Simulate choosing the "Clear" option
- WidgetHost::context_action(&mut tb, crate::widget::ContextAction::ClearText);
+ crate::widget::WidgetHostExt::context_action(&mut tb, crate::widget::ContextAction::ClearText);
assert_eq!(tb.text, "");
assert_eq!(tb.edit_buffer, "");
}
@@ -2938,7 +2938,7 @@ mod tests {
};
tb.keyboard_input(&ctrl_x, &mut dummy);
assert_eq!(tb.edit_buffer, "hunter2", "Ctrl+X cuts nothing");
- assert!(!WidgetHost::context_action(&mut tb, crate::widget::ContextAction::Cut));
+ assert!(!crate::widget::WidgetHostExt::context_action(&mut tb, crate::widget::ContextAction::Cut));
assert_eq!(tb.edit_buffer, "hunter2", "nor does the menu's Cut");
dummy.hide_context_menu();
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index 7878b6a..02098a9 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -248,11 +248,6 @@ pub trait WidgetHost {
fn stable_target(&mut self) -> Option<(*mut (dyn WidgetHost + 'static), std::sync::Weak<()>)> {
None
}
- /// The widget's natural CONTENT height — the control below its detached label, if
- /// any. What a layout strategy allots; [`WidgetHost::layout`] places that content
- /// box at the origin it is given and hangs the label ([`WidgetHost::label_strip`])
- /// above it. `None` when the widget has no natural height.
- fn preferred_height(&self) -> Option<f32> { None }
/// The height of the detached-label strip above this widget's content: zero for
/// unlabeled widgets and for those whose base label IS their content
@@ -270,20 +265,6 @@ pub trait WidgetHost {
/// as a block (a `Group`'s hull) unions this with the rect.
fn detached_label_rect(&self) -> Option<crate::scene::layout::Rect> { None }
- fn mark_dirty(&mut self, ctx: &mut UiContext) {
- let b = self.base_mut();
- if b.dirty {
- return;
- }
- b.dirty = true;
- if let Some(id) = b.id.get() {
- if let Some(parent_ptr) = ctx.tree.parent_ptr(id) {
- unsafe {
- (*parent_ptr).mark_dirty(ctx);
- }
- }
- }
- }
// Required (the flip): the old defaults manufactured DummyAny stand-ins nothing
// could legitimately use. `impl_widget_base!` provides both. `as_ptr`/`as_ptr_mut`
@@ -294,6 +275,23 @@ pub trait WidgetHost {
fn as_any(&self) -> &dyn std::any::Any;
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
+ /// The widget's own model, as its narrow traits: what [`WidgetHostExt`] reads its
+ /// one-line answers off (`Adapted` hands out its inner widget; a test shim with no
+ /// model gets [`NoModel`]'s defaults).
+ fn layout_model(&self) -> &dyn Layout {
+ &NoModel
+ }
+ fn paint_model(&self) -> &dyn Paint {
+ &NoModel
+ }
+ fn input_model(&self) -> &dyn Input {
+ &NoModel
+ }
+ fn input_model_mut(&mut self) -> &mut dyn Input {
+ // A zero-sized value: leaking it allocates nothing.
+ Box::leak(Box::new(NoModel))
+ }
+
fn handle_event(&mut self, event: &Event, ctx: &mut UiContext) -> bool {
// The default serves test shims only (Adapted overrides this): base hover
// bookkeeping on moves, tick forwarding, everything else inert — the old
@@ -337,9 +335,6 @@ pub trait WidgetHost {
(b.x, b.y, b.w, b.h)
}
- fn label(&self) -> Option<String> {
- self.base().label.clone()
- }
// The value/polling block (`get_value_string`/`set_value_string`/`take_change`/
// `take_click`/`value`/`set_text`/`set_selected`) is GONE from the trait (6bd value
@@ -347,12 +342,6 @@ pub trait WidgetHost {
// (which forward to the narrow `Input` hooks). The last dyn readers went concrete-slot
// (TI's roster drain, cloud's JsonControl, designer's pane-focus sync).
- /// Dispatch a context-menu action on this widget. Returns whether it was applied.
- /// Default inert; the adapter forwards to `Input::context_action` (whose default gives
- /// every widget whole-value Cut/Copy/Paste through the value-string pair).
- fn context_action(&mut self, _action: ContextAction) -> bool {
- false
- }
fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
let b = self.base_mut();
@@ -406,9 +395,6 @@ pub trait WidgetHost {
Some((hx, b.y, hw, b.h, hc))
}
- fn color(&self) -> [f32; 4];
- fn solid_border(&self) -> Option<([f32; 4], f32)> { None }
- fn plate_bevel(&self) -> Option<f32> { None }
// `draggable`/`is_dragging` are GONE from the trait (the ControlPanel endgame
// removed their last stored-child-pointer consumer): the drag queries are concrete
@@ -454,9 +440,7 @@ pub trait WidgetHost {
// `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(depth) = self.plate_bevel() {
- ctx.bevel(rect, radii, &crate::scene::material::Material::from_fill(color), depth);
- } else if let Some((border_color, thickness)) = self.solid_border() {
+ 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();
@@ -482,17 +466,7 @@ pub trait WidgetHost {
// trap). Migrated widgets go through `Adapted::paint_self`, never this default.
}
- /// Whether the paint walk should clip this widget's children to its rect (scroll/root plate
- /// containers). Default: no clipping.
- fn clips_children(&self) -> bool { false }
- /// Whether this widget paints its ENTIRE subtree itself through its (recursive)
- /// `all_rounded_quads` / `all_quads` — a legacy "subtree painter" such as `TreeList`, whose
- /// row backgrounds and separators live in an `all_rounded_quads` override that also recurses
- /// into its children. When true, the paint walk emits those directly and does NOT recurse
- /// (the widget already did). Transitional: such widgets will eventually get a proper
- /// non-recursive `paint_self`. Default: false.
- fn renders_own_subtree(&self) -> bool { false }
fn all_rounded_quads(&self, ctx: &UiContext) -> Vec<(f32, f32, f32, f32, f32, [f32; 4], (bool, bool, bool, bool))> {
if !self.visible() {
@@ -522,7 +496,6 @@ pub trait WidgetHost {
// 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.
- fn widget_font(&self) -> Option<String> { None }
fn type_name(&self) -> &'static str {
let full_name = std::any::type_name::<Self>();
full_name.split("::").last().unwrap_or("Widget")
@@ -544,9 +517,7 @@ pub trait WidgetHost {
fn set_visible(&mut self, _visible: bool) {}
fn visible(&self) -> bool { true }
fn tick(&mut self, _dt: f32, _ctx: &mut UiContext) -> bool { false }
- fn wants_tick(&self) -> bool { false }
fn is_child_visible(&self, _child_id: WidgetId) -> bool { true }
- fn set_modifiers(&mut self, _ctrl: bool, _shift: bool, _alt: bool) {}
// `set_parent`/`add_child` are GONE from the trait (6bd batch 4): linking is a tree
// operation — concrete callers ride the inherent `Adapted` methods, dyn callers go
@@ -557,9 +528,6 @@ pub trait WidgetHost {
// child (the one `Layout::container_children` implementor) reaches the walks through
// the tree link its per-tick `register_embedded_children` maintains.
- fn z_index(&self) -> i32 { 0 }
- fn is_scrollable(&self) -> bool { false }
- fn blocks_root_plate_drag(&self) -> bool { true }
/// Uniform corner radius + per-corner on-flags, in one read (6bd batch 2 — replaced the
/// separate `corner_radius`/`rounded_corners` getters). The radius is meaningful even with
@@ -569,47 +537,132 @@ pub trait WidgetHost {
(12.0, (false, false, false, false))
}
+
+
+
+
+
+
+ /// The parts a screen reader sees as nodes of their own (`Input::a11y_items`).
+ fn a11y_items(&self) -> Vec<crate::a11y::A11yItem> {
+ Vec::new()
+ }
+
+
+}
+
+/// The host surface that does not need a slot of its own in `WidgetHost`: what a widget's
+/// narrow traits answer ([`Input`], [`Paint`], [`Layout`], reached through the host's
+/// [`WidgetHost::input_model`] / [`paint_model`](WidgetHost::paint_model) /
+/// [`layout_model`](WidgetHost::layout_model)) and what is derived from the host's own state.
+/// Implemented for every host, `dyn WidgetHost` included, so `w.focus_role()` reads as it
+/// always did — with this trait in scope (`use cce_ui::widget::WidgetHostExt`). Until
+/// 2026-10-08 each of these was a `WidgetHost` method that `Adapted` overrode with a one-line
+/// forward and `Owned` forwarded again.
+pub trait WidgetHostExt: WidgetHost {
/// This widget's part in keyboard navigation — `Input::focus_role` through
/// the adapter; `FocusRole::None` for anything that is not a plate or a well.
fn focus_role(&self) -> FocusRole {
- FocusRole::None
+ self.input_model().focus_role()
}
/// Whether, focused, it takes Tab itself instead of the Tab walk (`Input::keeps_tab`).
fn keeps_tab(&self) -> bool {
- false
+ self.input_model().keeps_tab()
+ }
+
+ fn blocks_root_plate_drag(&self) -> bool {
+ self.input_model().blocks_root_plate_drag()
+ }
+
+ fn wants_tick(&self) -> bool {
+ self.input_model().wants_tick()
+ }
+
+ fn is_scrollable(&self) -> bool {
+ self.input_model().scrollable()
}
/// An explicit accessibility role, overriding the guess `crate::a11y::role_for` makes
/// from the widget's type and focus role. Default `None`.
fn a11y_role(&self) -> Option<accesskit::Role> {
- None
+ self.input_model().a11y_role()
}
/// The widget's value for assistive technology: a field's text, a slider's number, a
/// check box's "true" / "false". Default `None`.
fn a11y_value(&self) -> Option<String> {
- None
+ self.input_model().value_string()
}
/// The `(min, max, step)` an assistive tool may set the value in (`Input::a11y_range`).
fn a11y_range(&self) -> Option<(f64, f64, f64)> {
- None
+ self.input_model().a11y_range()
}
/// Set the value an assistive tool asked for (`Input::a11y_set_value`).
- fn a11y_set_value(&mut self, _value: f64) -> bool {
- false
+ fn a11y_set_value(&mut self, value: f64) -> bool {
+ self.input_model_mut().a11y_set_value(value)
}
- /// The parts a screen reader sees as nodes of their own (`Input::a11y_items`).
- fn a11y_items(&self) -> Vec<crate::a11y::A11yItem> {
- Vec::new()
+ /// An assistive tool clicked one of them (`Input::a11y_select_item`).
+ fn a11y_select_item(&mut self, idx: usize) -> bool {
+ self.input_model_mut().a11y_select_item(idx)
}
- /// An assistive tool clicked one of them (`Input::a11y_select_item`).
- fn a11y_select_item(&mut self, _idx: usize) -> bool {
- false
+ fn set_modifiers(&mut self, ctrl: bool, shift: bool, alt: bool) {
+ self.input_model_mut().set_modifiers(ctrl, shift, alt)
+ }
+
+ /// Dispatch a context-menu action on this widget. Returns whether it was applied.
+ /// Default inert; the adapter forwards to `Input::context_action` (whose default gives
+ /// every widget whole-value Cut/Copy/Paste through the value-string pair).
+ fn context_action(&mut self, action: ContextAction) -> bool {
+ self.input_model_mut().context_action(action)
+ }
+
+ fn color(&self) -> [f32; 4] {
+ self.paint_model().color()
+ }
+
+ fn solid_border(&self) -> Option<([f32; 4], f32)> {
+ self.paint_model().solid_border()
+ }
+
+ fn widget_font(&self) -> Option<String> {
+ self.paint_model().widget_font()
+ }
+
+ /// Whether the paint walk should clip this widget's children to its rect (scroll/root plate
+ /// containers). Default: no clipping.
+ fn clips_children(&self) -> bool {
+ self.paint_model().clips_children()
+ }
+
+ /// Whether this widget paints its ENTIRE subtree itself through its (recursive)
+ /// `all_rounded_quads` / `all_quads` — a legacy "subtree painter" such as `TreeList`, whose
+ /// row backgrounds and separators live in an `all_rounded_quads` override that also recurses
+ /// into its children. When true, the paint walk emits those directly and does NOT recurse
+ /// (the widget already did). Transitional: such widgets will eventually get a proper
+ /// non-recursive `paint_self`. Default: false.
+ fn renders_own_subtree(&self) -> bool {
+ self.paint_model().paints_own_subtree()
+ }
+
+ fn z_index(&self) -> i32 {
+ self.layout_model().z_order()
+ }
+
+ /// The widget's natural CONTENT height — the control below its detached label, if
+ /// any. What a layout strategy allots; [`WidgetHost::layout`] places that content
+ /// box at the origin it is given and hangs the label ([`WidgetHost::label_strip`])
+ /// above it. `None` when the widget has no natural height.
+ fn preferred_height(&self) -> Option<f32> {
+ self.layout_model().intrinsic_size().map(|s| s.height)
+ }
+
+ fn label(&self) -> Option<String> {
+ self.base().label.clone()
}
fn corner_radii(&self) -> CornerRadii {
@@ -621,8 +674,37 @@ pub trait WidgetHost {
if bl { r } else { 0.0 },
)
}
+
+ fn mark_dirty(&mut self, ctx: &mut UiContext){
+ let b = self.base_mut();
+ if b.dirty {
+ return;
+ }
+ b.dirty = true;
+ if let Some(id) = b.id.get() {
+ if let Some(parent_ptr) = ctx.tree.parent_ptr(id) {
+ unsafe {
+ (*parent_ptr).mark_dirty(ctx);
+ }
+ }
+ }
+ }
}
+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;
+impl Layout for NoModel {}
+impl Paint for NoModel {
+ fn color(&self) -> [f32; 4] {
+ [0.0; 4]
+ }
+}
+impl Input for NoModel {}
+
+
// The `Control` subtrait (set_label + control_label) is DELETED (6bd value shrink):
// zero dyn consumers and zero `control_label()` callers remained; `set_label` lives on as
// the inherent `Adapted<W>` method every call site already resolved to (it shadowed the
diff --git a/src/widget/model.rs b/src/widget/model.rs
index f29b09a..86a77c2 100644
--- a/src/widget/model.rs
+++ b/src/widget/model.rs
@@ -28,9 +28,7 @@
use crate::scene::layout::{Rect, Size};
use crate::scene::paint::{PaintCtx, Prim};
-use crate::widget::{
- WidgetHost, Event, TextLabel, UiContext, Widget, WidgetId,
-};
+use crate::widget::{WidgetHost, Event, TextLabel, UiContext, Widget, WidgetId, WidgetHostExt};
/// Layout inputs for the scene layout engine — the RFC's `Widget` concern, named `Layout` here to
/// avoid the existing [`Widget`] base struct.
@@ -1131,6 +1129,18 @@ impl<W: Layout + Paint + Input + 'static> std::ops::DerefMut for Adapted<W> {
}
impl<W: Layout + Paint + Input + 'static> WidgetHost for Adapted<W> {
+ fn layout_model(&self) -> &dyn Layout {
+ &self.inner
+ }
+ fn paint_model(&self) -> &dyn Paint {
+ &self.inner
+ }
+ fn input_model(&self) -> &dyn Input {
+ &self.inner
+ }
+ fn input_model_mut(&mut self) -> &mut dyn Input {
+ &mut self.inner
+ }
fn base(&self) -> &Widget {
&self.base
}
@@ -1172,13 +1182,7 @@ impl<W: Layout + Paint + Input + 'static> WidgetHost for Adapted<W> {
false
}
- fn z_index(&self) -> i32 {
- Layout::z_order(&self.inner)
- }
- fn set_modifiers(&mut self, ctrl: bool, shift: bool, alt: bool) {
- Input::set_modifiers(&mut self.inner, ctrl, shift, alt)
- }
fn focused(&self, _ctx: &UiContext) -> bool {
Input::is_focused(&self.inner, self.base.focused)
@@ -1252,10 +1256,6 @@ impl<W: Layout + Paint + Input + 'static> WidgetHost for Adapted<W> {
}
}
- /// The intrinsic content height — the control below the label.
- fn preferred_height(&self) -> Option<f32> {
- Layout::intrinsic_size(&self.inner).map(|s| s.height)
- }
fn label_strip(&self) -> f32 {
if Layout::inline_label(&self.inner) { 0.0 } else { self.base.label_offset() }
@@ -1326,62 +1326,21 @@ impl<W: Layout + Paint + Input + 'static> WidgetHost for Adapted<W> {
}
// --- Paint concern -> `Paint` ---
- fn color(&self) -> [f32; 4] {
- Paint::color(&self.inner)
- }
- fn clips_children(&self) -> bool {
- Paint::clips_children(&self.inner)
- }
fn corner_style(&self) -> (f32, (bool, bool, bool, bool)) {
// 12.0 / all-off mirrors the `WidgetHost` default for widgets without a corner style.
Paint::corner_style(&self.inner, self.content_rect())
.unwrap_or((12.0, (false, false, false, false)))
}
- fn focus_role(&self) -> FocusRole {
- Input::focus_role(&self.inner)
- }
- fn keeps_tab(&self) -> bool {
- Input::keeps_tab(&self.inner)
- }
- fn a11y_role(&self) -> Option<accesskit::Role> {
- Input::a11y_role(&self.inner)
- }
- fn a11y_value(&self) -> Option<String> {
- Input::value_string(&self.inner)
- }
- fn a11y_range(&self) -> Option<(f64, f64, f64)> {
- Input::a11y_range(&self.inner)
- }
- fn a11y_set_value(&mut self, value: f64) -> bool {
- Input::a11y_set_value(&mut self.inner, value)
- }
fn a11y_items(&self) -> Vec<crate::a11y::A11yItem> {
Input::a11y_items(&self.inner, self.content_rect())
}
- fn a11y_select_item(&mut self, idx: usize) -> bool {
- Input::a11y_select_item(&mut self.inner, idx)
- }
- fn solid_border(&self) -> Option<([f32; 4], f32)> {
- Paint::solid_border(&self.inner)
- }
- fn widget_font(&self) -> Option<String> {
- Paint::widget_font(&self.inner)
- }
- /// Scene-path emission. Geometry comes from [`Paint::paint`]; its plain `Text` prims are
- /// REPLACED by the same font+bounds view the standard text bridges serve
- /// (`own_labels_with_font_and_bounds`, or the per-label hatch), so a display list built by
- /// the paint walk carries per-widget fonts and clip rects (Phase 6 — text ordering
- /// relative to geometry is immaterial: glyphs always render in the later text pass).
- fn renders_own_subtree(&self) -> bool {
- Paint::paints_own_subtree(&self.inner)
- }
fn paint_self(&self, ui: &UiContext, ctx: &mut PaintCtx) {
let mut tmp = PaintCtx::new();
@@ -1593,12 +1552,6 @@ impl<W: Layout + Paint + Input + 'static> WidgetHost for Adapted<W> {
}
// --- Input concern -> `Input` ---
- fn blocks_root_plate_drag(&self) -> bool {
- Input::blocks_root_plate_drag(&self.inner)
- }
- fn context_action(&mut self, action: crate::widget::ContextAction) -> bool {
- Input::context_action(&mut self.inner, action)
- }
/// Row-rect assignment (row-layout hosts): apply the widget's clamp
/// ([`Layout::adjust_row_rect`] — TextBox's `width`/`max_width`), then the base write the
/// `WidgetHost` default does.
@@ -1626,12 +1579,6 @@ impl<W: Layout + Paint + Input + 'static> WidgetHost for Adapted<W> {
}
changed
}
- fn wants_tick(&self) -> bool {
- Input::wants_tick(&self.inner)
- }
- fn is_scrollable(&self) -> bool {
- Input::scrollable(&self.inner)
- }
/// Focus set/cleared directly (hosts call `w.focus()`/`w.unfocus()`): keep the base flag
/// (unless the widget opts out — [`Input::tracks_base_focus`], TextBox's legacy `focus`
/// never set it) and tell the widget via the same `FocusIn`/`FocusOut` events the router
diff --git a/src/widget/owned.rs b/src/widget/owned.rs
index 551b889..630a09f 100644
--- a/src/widget/owned.rs
+++ b/src/widget/owned.rs
@@ -17,7 +17,7 @@
//! with `mem::swap` — but the allocation always holds a valid `W` while the token lives. So a
//! pointer the registry resolves through an `Owned`'s token always names a live `W`.
//!
-//! `Owned<W>` is itself a [`WidgetHost`] (every method forwards to the boxed widget), so it goes
+//! `Owned<W>` is itself a [`WidgetHost`] (every trait method forwards to the boxed widget), so it goes
//! wherever a widget went: `register_host(&mut self.button)`, `set_focused`, `render_widget`,
//! `link_parent_child`. Each of those registers through `WidgetTree::register`, which asks
//! [`WidgetHost::stable_target`] and, for an `Owned`, stores the BOXED widget and the
@@ -35,7 +35,7 @@ use std::ops::{Deref, DerefMut};
use std::ptr::NonNull;
use super::core::Liveness;
-use super::{ContextAction, CornerRadii, Event, FocusRole, LayoutConstraints, Point, Size, Widget, WidgetHost, WidgetId};
+use super::{Event, LayoutConstraints, Point, Size, Widget, WidgetHost, WidgetId};
use crate::context::UiContext;
/// A widget in a heap allocation of its own, which the registry can point at however the
@@ -137,6 +137,10 @@ impl<W: WidgetHost + std::fmt::Debug + 'static> std::fmt::Debug for Owned<W> {
/// Every method forwards to the boxed widget, so an `Owned` behaves exactly as the widget does;
/// the one addition is [`stable_target`](WidgetHost::stable_target).
impl<W: WidgetHost + 'static> WidgetHost for Owned<W> {
+ fn layout_model(&self) -> &dyn crate::widget::Layout { (**self).layout_model() }
+ fn paint_model(&self) -> &dyn crate::widget::Paint { (**self).paint_model() }
+ fn input_model(&self) -> &dyn crate::widget::Input { (**self).input_model() }
+ fn input_model_mut(&mut self) -> &mut dyn crate::widget::Input { (**self).input_model_mut() }
fn stable_target(&mut self) -> Option<(*mut (dyn WidgetHost + 'static), std::sync::Weak<()>)> {
// The raw root itself, not a pointer taken from a reborrow (see the type's docs).
let ptr: *mut (dyn WidgetHost + 'static) = self.widget.as_ptr();
@@ -145,36 +149,26 @@ impl<W: WidgetHost + 'static> WidgetHost for Owned<W> {
fn base(&self) -> &Widget { (**self).base() }
fn base_mut(&mut self) -> &mut Widget { (**self).base_mut() }
- fn preferred_height(&self) -> Option<f32> { (**self).preferred_height() }
fn label_strip(&self) -> f32 { (**self).label_strip() }
fn detached_label_rect(&self) -> Option<crate::scene::layout::Rect> { (**self).detached_label_rect() }
- fn mark_dirty(&mut self, ctx: &mut UiContext) { (**self).mark_dirty(ctx) }
fn as_any(&self) -> &dyn std::any::Any { (**self).as_any() }
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { (**self).as_any_mut() }
fn handle_event(&mut self, event: &Event, ctx: &mut UiContext) -> bool { (**self).handle_event(event, ctx) }
fn measure(&self, constraints: LayoutConstraints, ctx: &UiContext) -> Size { (**self).measure(constraints, ctx) }
fn layout(&mut self, origin: Point, constraints: LayoutConstraints, ctx: &mut UiContext) { (**self).layout(origin, constraints, ctx) }
fn rect(&self) -> (f32, f32, f32, f32) { (**self).rect() }
- fn label(&self) -> Option<String> { (**self).label() }
- fn context_action(&mut self, action: ContextAction) -> bool { (**self).context_action(action) }
fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { (**self).set_rect(x, y, w, h) }
fn set_row_rect(&mut self, x: f32, w: f32) { (**self).set_row_rect(x, w) }
fn hit_test(&self, px: f32, py: f32, ctx: &UiContext) -> bool { (**self).hit_test(px, py, ctx) }
fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, f32, [f32; 4])> { (**self).highlight_quad(ctx) }
- fn color(&self) -> [f32; 4] { (**self).color() }
- fn solid_border(&self) -> Option<([f32; 4], f32)> { (**self).solid_border() }
- fn plate_bevel(&self) -> Option<f32> { (**self).plate_bevel() }
fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> { (**self).extra_quads() }
fn extra_arcs(&self) -> Vec<(f32, f32, f32, f32, f32, f32, [f32; 4])> { (**self).extra_arcs() }
fn extra_circles(&self) -> Vec<(f32, f32, f32, [f32; 4])> { (**self).extra_circles() }
fn all_quads(&self, ctx: &UiContext) -> Vec<(f32, f32, f32, f32, [f32; 4])> { (**self).all_quads(ctx) }
fn paint_self(&self, ui: &UiContext, ctx: &mut crate::scene::paint::PaintCtx) { (**self).paint_self(ui, ctx) }
- fn clips_children(&self) -> bool { (**self).clips_children() }
- fn renders_own_subtree(&self) -> bool { (**self).renders_own_subtree() }
fn all_rounded_quads(&self, ctx: &UiContext) -> Vec<(f32, f32, f32, f32, f32, [f32; 4], (bool, bool, bool, bool))> {
(**self).all_rounded_quads(ctx)
}
- fn widget_font(&self) -> Option<String> { (**self).widget_font() }
fn type_name(&self) -> &'static str { (**self).type_name() }
fn popover_rect(&self) -> Option<(f32, f32, f32, f32)> { (**self).popover_rect() }
fn render_popover(&self, pc: &mut dyn crate::layout::RenderTarget) { (**self).render_popover(pc) }
@@ -185,22 +179,9 @@ impl<W: WidgetHost + 'static> WidgetHost for Owned<W> {
fn set_visible(&mut self, visible: bool) { (**self).set_visible(visible) }
fn visible(&self) -> bool { (**self).visible() }
fn tick(&mut self, dt: f32, ctx: &mut UiContext) -> bool { (**self).tick(dt, ctx) }
- fn wants_tick(&self) -> bool { (**self).wants_tick() }
fn is_child_visible(&self, child_id: WidgetId) -> bool { (**self).is_child_visible(child_id) }
- fn set_modifiers(&mut self, ctrl: bool, shift: bool, alt: bool) { (**self).set_modifiers(ctrl, shift, alt) }
- fn z_index(&self) -> i32 { (**self).z_index() }
- fn is_scrollable(&self) -> bool { (**self).is_scrollable() }
- fn blocks_root_plate_drag(&self) -> bool { (**self).blocks_root_plate_drag() }
fn corner_style(&self) -> (f32, (bool, bool, bool, bool)) { (**self).corner_style() }
- fn focus_role(&self) -> FocusRole { (**self).focus_role() }
- fn keeps_tab(&self) -> bool { (**self).keeps_tab() }
- fn a11y_role(&self) -> Option<accesskit::Role> { (**self).a11y_role() }
- fn a11y_value(&self) -> Option<String> { (**self).a11y_value() }
- fn a11y_range(&self) -> Option<(f64, f64, f64)> { (**self).a11y_range() }
- fn a11y_set_value(&mut self, value: f64) -> bool { (**self).a11y_set_value(value) }
fn a11y_items(&self) -> Vec<crate::a11y::A11yItem> { (**self).a11y_items() }
- fn a11y_select_item(&mut self, idx: usize) -> bool { (**self).a11y_select_item(idx) }
- fn corner_radii(&self) -> CornerRadii { (**self).corner_radii() }
}
#[cfg(test)]
@@ -215,9 +196,6 @@ mod tests {
}
impl WidgetHost for Tag {
crate::impl_widget_base!(Tag);
- fn color(&self) -> [f32; 4] {
- [0.0; 4]
- }
}
fn tag(n: u32) -> Owned<Tag> {
Owned::new(Tag { base: Widget::new(), n })