GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
refactor(widget)!: second Element shrink batch — 4 methods off (6bd)
Element 73 -> 69, census-driven toward the WidgetHost blueprint:
- `hovered`/`set_hovered` deleted: the state is the base `Widget::hovered`
flag, now read/written directly by the `cursor_moved`/`on_cursor_moved`/
`highlight_quad` defaults and `serialize.rs`. Button/Checkbox keep their
inherent accessors for immediate-mode hosts (cloud json_layout resolves
to those already).
- `corner_radius` + `rounded_corners` replaced by ONE `corner_style()
-> (f32, (bool,bool,bool,bool))` (mirrors the narrow `Paint::corner_style`).
NOT folded into `corner_radii`: the radius is meaningful with all corners
off — Menu/StatusBar report their parent's radius to children through the
flags-off channel, and breadcrumb can be flags-true/radius-0.
- `clear_children` moved to an inherent `Adapted<W>` method (every caller
is a concrete Adapted field in cce-files).
Drive-by: two pre-existing unused-mut warnings in slider.rs closures.
Verified: 163 tests pass; workspace builds; settings audio render stream
byte-identical; cce-files A/B AE=0; TI gallery empty 8% amplitude mask.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018u7qTwzX95dd5ysAkaSCLk
src/layout.rs | 8 ++----
src/scene/painter.rs | 7 ++---
src/widget/container/menu.rs | 2 +-
src/widget/display/serialize.rs | 2 +-
src/widget/display/status_bar.rs | 4 +--
src/widget/input/slider.rs | 4 +--
src/widget/input/text_box.rs | 2 +-
src/widget/mod.rs | 56 +++++++++++++++++-----------------------
src/widget/model.rs | 26 +++++++++----------
9 files changed, 47 insertions(+), 64 deletions(-)
diff --git a/src/layout.rs b/src/layout.rs
index 1944aa4..30be521 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -3147,12 +3147,8 @@ pub fn render_widget<T: Element + 'static>(pc: &mut dyn RenderTarget, w: &mut T,
ctx.register_widget(w_id, w.as_ptr_mut());
}
w.layout(crate::widget::Point { x, y }, crate::widget::LayoutConstraints::new(ww, ww, wh, wh), ctx);
- let corners = w.rounded_corners();
- let r = if corners != (false, false, false, false) {
- w.corner_radius()
- } else {
- 0.0
- };
+ let (style_r, corners) = w.corner_style();
+ 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 = crate::widget::label_offset(w);
wy += top_room;
diff --git a/src/scene/painter.rs b/src/scene/painter.rs
index ced2d4a..be4e5ad 100644
--- a/src/scene/painter.rs
+++ b/src/scene/painter.rs
@@ -337,11 +337,8 @@ mod tests {
fn color(&self) -> [f32; 4] {
[0.2, 0.4, 0.6, 1.0]
}
- fn rounded_corners(&self) -> (bool, bool, bool, bool) {
- (true, true, true, true)
- }
- fn corner_radius(&self) -> f32 {
- 4.0
+ fn corner_style(&self) -> (f32, (bool, bool, bool, bool)) {
+ (4.0, (true, true, true, true))
}
}
let mut ctx = UiContext::new();
diff --git a/src/widget/container/menu.rs b/src/widget/container/menu.rs
index 3104fd6..e22589b 100644
--- a/src/widget/container/menu.rs
+++ b/src/widget/container/menu.rs
@@ -464,7 +464,7 @@ impl Paint for MenuBar {
// Corners never round (the backplate-adjacency source is gone); the radius is still
// reported for children that read it through the parent pointer.
let radius = match self.parent {
- Some(p_ptr) => unsafe { (*p_ptr).corner_radius() },
+ Some(p_ptr) => unsafe { (*p_ptr).corner_style().0 },
None => 0.0,
};
Some((radius, (false, false, false, false)))
diff --git a/src/widget/display/serialize.rs b/src/widget/display/serialize.rs
index 3ed1ceb..a12ee5c 100644
--- a/src/widget/display/serialize.rs
+++ b/src/widget/display/serialize.rs
@@ -4,7 +4,7 @@ fn serialize_single_widget(w: &dyn Element, json: &mut String) {
let (x, y, width, height) = w.rect();
let label = w.label().or_else(|| w.base().and_then(|b| b.label.clone())).unwrap_or_default();
let focused = w.base().map_or(false, |b| b.focused);
- let hovered = w.hovered();
+ let hovered = w.base().map_or(false, |b| b.hovered);
let value = w.value();
let type_name = w.type_name();
diff --git a/src/widget/display/status_bar.rs b/src/widget/display/status_bar.rs
index 62f8fc7..037a8ce 100644
--- a/src/widget/display/status_bar.rs
+++ b/src/widget/display/status_bar.rs
@@ -113,7 +113,7 @@ impl Paint for StatusBar {
// Corners never round (the backplate-adjacency source is gone); the radius is still
// reported for children that read it through the parent pointer.
let radius = match self.parent {
- Some(p_ptr) => unsafe { (*p_ptr).corner_radius() },
+ Some(p_ptr) => unsafe { (*p_ptr).corner_style().0 },
None => 0.0,
};
Some((radius, (false, false, false, false)))
@@ -196,7 +196,7 @@ mod tests {
// Parentless: cornerless plain bg through the plain-quad bridge, at STATUS_BG.
let extra = Element::extra_quads(&bar);
assert_eq!(extra.len(), 1, "cornerless bg quad");
- assert_eq!(Element::rounded_corners(&bar), (false, false, false, false));
+ assert_eq!(Element::corner_style(&bar).1, (false, false, false, false));
assert!(!Element::blocks_backplate_drag(&bar));
}
}
diff --git a/src/widget/input/slider.rs b/src/widget/input/slider.rs
index 85af3a2..e3bbdc7 100644
--- a/src/widget/input/slider.rs
+++ b/src/widget/input/slider.rs
@@ -216,7 +216,7 @@ impl Paint for Slider {
let radius = crate::layout::slider_corner_radius();
let rounded = radius > 0.0;
let rc = (rounded, rounded, rounded, rounded);
- let mut rrect = |r: Rect, rad: f32, corners: (bool, bool, bool, bool), c: [f32; 4], ctx: &mut PaintCtx| {
+ let rrect = |r: Rect, rad: f32, corners: (bool, bool, bool, bool), c: [f32; 4], ctx: &mut PaintCtx| {
if rounded {
ctx.rounded_rect(r, rad, corners, c);
} else {
@@ -534,7 +534,7 @@ impl Paint for RangeSlider {
colors::rangeslider_thumb()
};
- let mut rrect = |r: Rect, rad: f32, corners: (bool, bool, bool, bool), c: [f32; 4], ctx: &mut PaintCtx| {
+ let rrect = |r: Rect, rad: f32, corners: (bool, bool, bool, bool), c: [f32; 4], ctx: &mut PaintCtx| {
if rounded {
ctx.rounded_rect(r, rad, corners, c);
} else {
diff --git a/src/widget/input/text_box.rs b/src/widget/input/text_box.rs
index 145ed06..f06f4a2 100644
--- a/src/widget/input/text_box.rs
+++ b/src/widget/input/text_box.rs
@@ -1646,7 +1646,7 @@ mod tests {
tb.select_anchor = Some(7); // starts at "Line 2"
tb.cursor_idx = 13; // ends at end of "Line 2"
- let has_rounded = tb.rounded_corners() != (false, false, false, false);
+ let has_rounded = Element::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);
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index 11dcf84..ad60b09 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -338,9 +338,11 @@ pub trait Element {
fn cursor_moved(&mut self, px: f32, py: f32, ctx: &mut UiContext) -> bool {
ctx.set_cursor_pos(px, py);
if ctx.is_coordinate_covered(self.base().map(|b| b.id()).unwrap_or(WidgetId(0)), px, py) {
- let was = self.hovered();
+ let was = self.base().map_or(false, |b| b.hovered);
if was {
- self.set_hovered(false);
+ if let Some(b) = self.base_mut() {
+ b.hovered = false;
+ }
self.handle_event(&Event::MouseLeave, ctx);
}
return was;
@@ -350,9 +352,11 @@ pub trait Element {
fn on_cursor_moved(&mut self, px: f32, py: f32, ctx: &mut UiContext) -> bool {
if self.base().is_some() {
- let was = self.hovered();
+ let was = self.base().map_or(false, |b| b.hovered);
let is_hit = self.hit_test(px, py, ctx);
- self.set_hovered(is_hit);
+ if let Some(b) = self.base_mut() {
+ b.hovered = is_hit;
+ }
if was != is_hit {
if is_hit {
self.handle_event(&Event::MouseEnter, ctx);
@@ -371,26 +375,16 @@ pub trait Element {
fn mouse_input(&mut self, _button: MouseButton, _state: ElementState, _px: f32, _py: f32, _ctx: &mut UiContext) -> bool { false }
fn mouse_wheel(&mut self, _delta: &MouseScrollDelta, _px: f32, _py: f32, _ctx: &mut UiContext) -> bool { false }
- fn set_hovered(&mut self, hovered: bool) {
- if let Some(b) = self.base_mut() {
- b.hovered = hovered;
- }
- }
-
- fn hovered(&self) -> bool {
- if let Some(b) = self.base() {
- b.hovered
- } else {
- false
- }
- }
+ // `hovered`/`set_hovered` are GONE from the trait (6bd batch 2): the state is the base
+ // `Widget::hovered` flag, read/written directly by the defaults above; Button/Checkbox
+ // keep inherent accessors for immediate-mode hosts.
fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, f32, [f32; 4])> {
// Focus/hover highlight color, folded from the zero-override `highlight_color` (6bd).
let is_focused = self.base().map(|b| ctx.is_focused_id(b.id())).unwrap_or(false);
let hc = if is_focused {
colors::highlight_primary_color()
- } else if self.hovered() {
+ } else if self.base().map_or(false, |b| b.hovered) {
colors::HIGHLIGHT_SECONDARY
} else {
return None;
@@ -473,9 +467,9 @@ pub trait Element {
} else 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 (r1, r2, r3, r4) = self.rounded_corners();
+ let (radius, (r1, r2, r3, r4)) = self.corner_style();
if r1 || r2 || r3 || r4 {
- ctx.rounded_rect(rect, self.corner_radius(), (r1, r2, r3, r4), color);
+ ctx.rounded_rect(rect, radius, (r1, r2, r3, r4), color);
}
}
}
@@ -513,10 +507,9 @@ pub trait Element {
return Vec::new();
}
let mut quads = Vec::new();
- let (r1, r2, r3, r4) = self.rounded_corners();
+ let (radius, (r1, r2, r3, r4)) = self.corner_style();
if r1 || r2 || r3 || r4 {
let (x, y, w, h) = self.rect();
- let radius = self.corner_radius();
let c = self.color();
if c[3].abs() > 0.001 {
quads.push((x, y, w, h, radius, c, (r1, r2, r3, r4)));
@@ -618,23 +611,20 @@ pub trait Element {
}
}
- fn clear_children(&mut self, ctx: &mut UiContext) {
- if let Some(base) = self.base() {
- let id = base.id();
- ctx.clear_children_ids(id);
- }
- }
-
fn z_index(&self) -> i32 { 0 }
fn is_scrollable(&self) -> bool { false }
fn blocks_backplate_drag(&self) -> bool { true }
- fn rounded_corners(&self) -> (bool, bool, bool, bool) { (false, false, false, false) }
- fn corner_radius(&self) -> f32 { 12.0 }
+ /// 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
+ /// every corner off: Menu/StatusBar report their parent's radius to children this way, so
+ /// the flags-off channel can't be folded into `corner_radii`.
+ fn corner_style(&self) -> (f32, (bool, bool, bool, bool)) {
+ (12.0, (false, false, false, false))
+ }
fn corner_radii(&self) -> CornerRadii {
- let r = self.corner_radius();
- let (tl, tr, br, bl) = self.rounded_corners();
+ let (r, (tl, tr, br, bl)) = self.corner_style();
CornerRadii::new(
if tl { r } else { 0.0 },
if tr { r } else { 0.0 },
diff --git a/src/widget/model.rs b/src/widget/model.rs
index 30fc566..6804914 100644
--- a/src/widget/model.rs
+++ b/src/widget/model.rs
@@ -696,6 +696,12 @@ impl<W: Layout + Paint + Input + 'static> Adapted<W> {
Input::set_drag_bounds(&mut self.inner, bx, by, bw, bh)
}
+ /// Unlink all tree children (off `Element` in 6bd batch 2 — every caller is a concrete
+ /// `Adapted` field).
+ pub fn clear_children(&mut self, ctx: &mut UiContext) {
+ ctx.clear_children_ids(self.base.id());
+ }
+
/// The model's intrinsic content size (off the `Element` trait since 6bd — the concrete
/// callers are fonts'/graph's hand-laid button/dropdown sizing).
pub fn intrinsic_size(&self) -> Option<Size> {
@@ -901,10 +907,6 @@ impl<W: Layout + Paint + Input + 'static> Element for Adapted<W> {
}
}
- fn clear_children(&mut self, ctx: &mut UiContext) {
- ctx.clear_children_ids(self.base.id());
- }
-
fn set_parent(&mut self, parent: Option<*mut (dyn Element + 'static)>, ctx: &mut UiContext) {
Layout::parent_changed(&mut self.inner, parent);
// Replica of the Element default: symmetric tree link.
@@ -1094,12 +1096,10 @@ impl<W: Layout + Paint + Input + 'static> Element for Adapted<W> {
fn clips_children(&self) -> bool {
Paint::clips_children(&self.inner)
}
- fn corner_radius(&self) -> f32 {
- // 12.0 mirrors the `Element` default for widgets without a corner style.
- Paint::corner_style(&self.inner, self.content_rect()).map_or(12.0, |(r, _)| r)
- }
- fn rounded_corners(&self) -> (bool, bool, bool, bool) {
- Paint::corner_style(&self.inner, self.content_rect()).map_or((false, false, false, false), |(_, c)| c)
+ fn corner_style(&self) -> (f32, (bool, bool, bool, bool)) {
+ // 12.0 / all-off mirrors the `Element` default for widgets without a corner style.
+ Paint::corner_style(&self.inner, self.content_rect())
+ .unwrap_or((12.0, (false, false, false, false)))
}
fn solid_border(&self) -> Option<([f32; 4], f32)> {
Paint::solid_border(&self.inner)
@@ -1249,7 +1249,7 @@ impl<W: Layout + Paint + Input + 'static> Element for Adapted<W> {
for child in self.visible_children() {
let widget = unsafe { &*child };
let (wx, wy, ww, wh) = widget.rect();
- let has_rounded = widget.rounded_corners() != (false, false, false, false);
+ 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
@@ -1708,10 +1708,10 @@ mod tests {
// preserves) and sets the base hover flag; moving away synthesizes MouseLeave.
ctx.propagate_event(&Event::PointerMove { x: 20.0, y: 15.0, local_x: 20.0, local_y: 15.0 }, ptr);
assert_eq!(w.inner().entered, 1, "MouseEnter reached on_event");
- assert!(unsafe { (*ptr).hovered() }, "base hover flag set through the adapter");
+ assert!(unsafe { (*ptr).base().map_or(false, |b| b.hovered) }, "base hover flag set through the adapter");
ctx.propagate_event(&Event::PointerMove { x: 200.0, y: 200.0, local_x: 200.0, local_y: 200.0 }, ptr);
assert_eq!(w.inner().left, 1, "MouseLeave reached on_event");
- assert!(!unsafe { (*ptr).hovered() }, "base hover flag cleared");
+ assert!(!unsafe { (*ptr).base().map_or(false, |b| b.hovered) }, "base hover flag cleared");
}
/// A narrow widget that is also a controller: the controller trait is reached through the