git.lucas.co / cce-designer
graphic design tool
git clone https://git.lucas.co/cce-designer.git

commit02bd5d6164a602b4e6197479db03308d47531190
parent0a3a1378bb
authorLucas Galante <[email protected]>
date2026-07-12 09:44
refactor: designer's four raw impls onto the narrow traits (Phase 6az part 5)

PassivePlate, Canvas, NodePalette, and Viewport3D convert to
Layout/Paint/Input in Adapted; cce-designer is raw-impl-free. Constructors
return the wrapper, so the roster's Box::new(X::new()) pushes coerce
unchanged and every as_any downcast still reaches the model. PassivePlate:
the full 6as-part-6 getter surface rides Paint (color rule, corner_style,
solid_border) and the circular hit becomes Input::hit (rect fallback keeps
the legacy exclusive right/bottom edges); its visible/base fields move to
the adapter (hosts toggle panes via Element::set_visible on the box).
Canvas: a unit struct — hit-through via Input::hit false, hover rides the
adapter base. NodePalette: model-owned visibility (set_palette_state writes
the downcast model), z_order 200, extra_quads + own_labels become paint
gated on it. Viewport3D: wheel rotate/zoom moves to on_event (the adapter's
wheel gate replaces the old leading self hit test — it also covers the
unconditional direct dispatch site), inertia tick to Input::tick,
set_modifiers to the Input hook.

A/B: static diff = the recorded 2px status-race sliver even at a 4%
threshold; cross-build captures after an identical circular-pane toggle
(HTTP API) + viewport wheel are structurally identical (empty 8% mask).
Not headlessly drivable, user spot-check: the Add Node palette overlay
(keyboard-opened; hidden state byte-identical in A/B).

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018u7qTwzX95dd5ysAkaSCLk

 src/app.rs         | 151 +++++++++++++++++++++--------------------------------
 src/viewport_3d.rs |  37 +++++++------
 2 files changed, 78 insertions(+), 110 deletions(-)

diff --git a/src/app.rs b/src/app.rs
index 038a5d0..715cc0b 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -503,8 +503,8 @@ pub struct NodePalette {
 }
 
 impl NodePalette {
-    fn new() -> Self {
-        Self { x: 0.0, y: 0.0, w: 0.0, h: 0.0, visible: false, query: String::new(), items: Vec::new(), selected: 0 }
+    fn new() -> cce_ui::widget::Adapted<NodePalette> {
+        cce_ui::widget::Adapted::new(Self { x: 0.0, y: 0.0, w: 0.0, h: 0.0, visible: false, query: String::new(), items: Vec::new(), selected: 0 })
     }
 
     fn panel_rect(&self) -> (f32, f32, f32, f32) {
@@ -526,8 +526,6 @@ impl NodePalette {
 /// at `plate_opacity` times the network fade, alpha negated when blur is on (the
 /// scenefx blur marker) — with no children and no events.
 pub struct PassivePlate {
-    base: cce_ui::widget::Widget,
-    visible: bool,
     color: [f32; 4],
     blur: bool,
     pub network_opacity: f32,
@@ -536,15 +534,13 @@ pub struct PassivePlate {
 }
 
 impl PassivePlate {
-    pub fn new(color: [f32; 4], blur: bool) -> Self {
-        Self {
-            base: cce_ui::widget::Widget::new(),
-            visible: true,
+    pub fn new(color: [f32; 4], blur: bool) -> cce_ui::widget::Adapted<PassivePlate> {
+        cce_ui::widget::Adapted::new(Self {
             color,
             blur,
             network_opacity: 1.0,
             curved_circle: None,
-        }
+        })
     }
 
     pub fn set_network_opacity(&mut self, opacity: f32) {
@@ -556,17 +552,9 @@ impl PassivePlate {
     }
 }
 
-impl cce_ui::widget::Element for PassivePlate {
-    cce_ui::impl_widget_base!(PassivePlate);
-
-    fn visible(&self) -> bool {
-        self.visible
-    }
-
-    fn set_visible(&mut self, visible: bool) {
-        self.visible = visible;
-    }
+impl cce_ui::widget::Layout for PassivePlate {}
 
+impl cce_ui::widget::Paint for PassivePlate {
     fn color(&self) -> [f32; 4] {
         let mut c = self.color;
         c[3] *= cce_ui::layout::plate_opacity();
@@ -577,17 +565,10 @@ impl cce_ui::widget::Element for PassivePlate {
         c
     }
 
-    fn rounded_corners(&self) -> (bool, bool, bool, bool) {
+    fn corner_style(&self, _rect: cce_ui::scene::layout::Rect) -> Option<(f32, (bool, bool, bool, bool))> {
         let r = cce_ui::layout::plate_corner_radius();
-        if r > 0.0 {
-            (true, true, true, true)
-        } else {
-            (false, false, false, false)
-        }
-    }
-
-    fn corner_radius(&self) -> f32 {
-        cce_ui::layout::plate_corner_radius()
+        let on = r > 0.0;
+        Some((r, (on, on, on, on)))
     }
 
     fn solid_border(&self) -> Option<([f32; 4], f32)> {
@@ -597,87 +578,68 @@ impl cce_ui::widget::Element for PassivePlate {
             None
         }
     }
+}
 
-    fn hit_test(&self, px: f32, py: f32, ctx: &cce_ui::context::UiContext) -> bool {
-        if ctx.is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
-            return false;
-        }
+impl cce_ui::widget::Input for PassivePlate {
+    fn hit(&self, rect: cce_ui::scene::layout::Rect, x: f32, y: f32) -> bool {
         if let Some((cx, cy, r)) = self.curved_circle {
-            let dx = px - cx;
-            let dy = py - cy;
+            let dx = x - cx;
+            let dy = y - cy;
             return dx * dx + dy * dy <= r * r;
         }
-        let (x, y, w, h) = self.rect();
-        px >= x && px < x + w && py >= y && py < y + h
+        // Exclusive right/bottom edges, like the legacy Plate hit test.
+        x >= rect.x && x < rect.x + rect.width && y >= rect.y && y < rect.y + rect.height
     }
 }
 
 
 /// App-owned copy of the dissolved cce-ui `Canvas` (Phase 6ay part 2): the transparent
 /// hit-through pane behind the network area. Verbatim; dies with the machinery retype.
-pub struct Canvas {
-    x: f32, y: f32, w: f32, h: f32,
-    hovered: bool,
-}
+pub struct Canvas;
 
 impl Canvas {
-    pub fn new() -> Self { Self { x: 0.0, y: 0.0, w: 0.0, h: 0.0, hovered: false } }
+    pub fn new() -> cce_ui::widget::Adapted<Canvas> { cce_ui::widget::Adapted::new(Canvas) }
 }
 
-impl Element for Canvas {
-    fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
+impl cce_ui::widget::Layout for Canvas {}
+
+impl cce_ui::widget::Paint for Canvas {
     fn color(&self) -> [f32; 4] { [0.0, 0.0, 0.0, 0.0] }
-    fn as_ptr(&self) -> *mut (dyn Element + 'static) {
-        self as *const Self as *mut Self as *mut (dyn Element + 'static)
-    }
-    fn as_ptr_mut(&mut self) -> *mut (dyn Element + 'static) {
-        self as *mut Self as *mut (dyn Element + 'static)
-    }
-    fn set_hovered(&mut self, v: bool) { self.hovered = v; }
-    fn hovered(&self) -> bool { self.hovered }
-    fn hit_test(&self, _px: f32, _py: f32, _ctx: &UiContext) -> bool { false }
 }
 
-impl Element for NodePalette {
-    // Leaf legacy widget: own labels via paint_self (cce-ui's default no longer drains
-    // the text getters; the render loop's walk text reads it).
-    fn paint_self(&self, ui: &cce_ui::context::UiContext, ctx: &mut cce_ui::scene::paint::PaintCtx) {
-        cce_ui::scene::painter::paint_legacy_leaf(
-            self, ui, ctx,
-            cce_ui::scene::painter::fonted_leaf_labels(self, ui, self.own_labels()),
-        );
-    }
+impl cce_ui::widget::Input for Canvas {
+    // Hit-through: the pane never claims the pointer (the graph decides its own hits).
+    fn hit(&self, _rect: cce_ui::scene::layout::Rect, _x: f32, _y: f32) -> bool { false }
+}
 
-    fn as_any(&self) -> &dyn std::any::Any { self }
-    fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
-    fn as_ptr(&self) -> *mut (dyn Element + 'static) {
-        self as *const Self as *mut Self as *mut (dyn Element + 'static)
+impl cce_ui::widget::Layout for NodePalette {
+    // The model mirrors the landed rect (panel_rect/labels read it between events).
+    fn rect_assigned(&mut self, rect: cce_ui::scene::layout::Rect) {
+        self.x = rect.x;
+        self.y = rect.y;
+        self.w = rect.width;
+        self.h = rect.height;
     }
-    fn as_ptr_mut(&mut self) -> *mut (dyn Element + 'static) {
-        self as *mut Self as *mut (dyn Element + 'static)
-    }
-    fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
-    fn color(&self) -> [f32; 4] { [0.0, 0.0, 0.0, 0.0] }
-    fn hit_test(&self, px: f32, py: f32, ctx: &cce_ui::context::UiContext) -> bool {
-        if ctx.is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
-            return false;
-        }
-        self.visible && px >= self.x && px <= self.x + self.w && py >= self.y && py <= self.y + self.h
+
+    fn z_order(&self) -> i32 {
+        200
     }
-    fn set_visible(&mut self, visible: bool) { self.visible = visible; }
-    fn visible(&self) -> bool { self.visible }
-    fn take_click(&mut self) -> bool { self.visible }
+}
 
+impl cce_ui::widget::Paint for NodePalette {
+    fn color(&self) -> [f32; 4] { [0.0, 0.0, 0.0, 0.0] }
 
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        if !self.visible { return Vec::new(); }
-        let mut quads = Vec::new();
-        quads.push((self.x, self.y, self.w, self.h, [0.0, 0.0, 0.0, 0.45]));
+    fn paint(&self, _rect: cce_ui::scene::layout::Rect, pc: &mut cce_ui::scene::paint::PaintCtx) {
+        use cce_ui::scene::layout::Rect;
+        // Visibility is model-owned (set through set_palette_state on the downcast model,
+        // not Element::set_visible on the wrapper), so the paint gate lives here.
+        if !self.visible {
+            return;
+        }
+        pc.quad(Rect { x: self.x, y: self.y, width: self.w, height: self.h }, [0.0, 0.0, 0.0, 0.45]);
         let (px, py, pw, ph) = self.panel_rect();
-        quads.push((px, py, pw, ph, colors::popover_bg_color()));
-        quads.push((px + 16.0, py + 48.0, pw - 32.0, 32.0, [0.10, 0.10, 0.14, 1.0]));
+        pc.quad(Rect { x: px, y: py, width: pw, height: ph }, colors::popover_bg_color());
+        pc.quad(Rect { x: px + 16.0, y: py + 48.0, width: pw - 32.0, height: 32.0 }, [0.10, 0.10, 0.14, 1.0]);
         let list_y = py + 92.0;
         let row_h = 24.0;
         let visible_rows = ((ph - 120.0) / row_h).floor().max(0.0) as usize;
@@ -685,14 +647,21 @@ impl Element for NodePalette {
         for (row, item_idx) in (start..self.items.len().min(start + visible_rows)).enumerate() {
             let y = list_y + row as f32 * row_h;
             let bg = if item_idx == self.selected { [0.24, 0.33, 0.55, 0.85] } else { [0.14, 0.14, 0.19, 0.55] };
-            quads.push((px + 16.0, y, pw - 32.0, row_h - 2.0, bg));
+            pc.quad(Rect { x: px + 16.0, y, width: pw - 32.0, height: row_h - 2.0 }, bg);
+        }
+        for l in self.own_labels() {
+            pc.text(l.text, l.x, l.y, l.font_size, l.color);
         }
-        quads
     }
+}
 
+impl cce_ui::widget::Input for NodePalette {
+    fn hit(&self, rect: cce_ui::scene::layout::Rect, x: f32, y: f32) -> bool {
+        self.visible && x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height
+    }
 
-    fn z_index(&self) -> i32 {
-        200
+    fn take_click(&mut self) -> bool {
+        self.visible
     }
 }
 
diff --git a/src/viewport_3d.rs b/src/viewport_3d.rs
index 657a8ce..e4646ae 100644
--- a/src/viewport_3d.rs
+++ b/src/viewport_3d.rs
@@ -1,6 +1,7 @@
 //! App-owned copy of the dissolved cce-ui `Viewport3D` (Phase 6ay part 2): the designer
-//! is the only consumer — the 3D preview pane of the roster. Verbatim from cce-ui; the
-//! `Element` impl dies with the machinery retype.
+//! is the only consumer — the 3D preview pane of the roster, on the narrow traits
+//! wrapped in `Adapted<Viewport3D>` (Phase 6az). The roster keeps it as
+//! `Box<dyn Element>`; `as_any` downcasts reach this model.
 
 use cce_ui::colors;
 use cce_ui::widget::*;
@@ -8,7 +9,6 @@ use glam::{Mat4, Vec3};
 
 #[derive(Debug, Clone)]
 pub struct Viewport3D {
-    base: Widget,
     pub rotation_x: f32,
     pub rotation_y: f32,
     pub zoom: f32,
@@ -47,10 +47,8 @@ pub struct Viewport3D {
 }
 
 impl Viewport3D {
-    pub fn new() -> Self {
-        let base = Widget::new();
-        Self {
-            base,
+    pub fn new() -> Adapted<Viewport3D> {
+        Adapted::new(Self {
             rotation_x: 0.0,
             rotation_y: 0.0,
             zoom: 1.0,
@@ -80,7 +78,7 @@ impl Viewport3D {
             scroll_speed: 1.0,
             inertial_scroll: true,
             scroll_friction: 0.90,
-        }
+        })
     }
 
     pub fn with_scroll_speed(mut self, speed: f32) -> Self {
@@ -135,28 +133,26 @@ impl Viewport3D {
     }
 }
 
-impl Element for Viewport3D {
-    cce_ui::impl_widget_base!(Viewport3D);
+impl cce_ui::widget::Layout for Viewport3D {}
 
+impl cce_ui::widget::Paint for Viewport3D {
     fn color(&self) -> [f32; 4] {
         colors::VIEWPORT_BG
     }
+}
 
+impl cce_ui::widget::Input for Viewport3D {
     fn set_modifiers(&mut self, ctrl: bool, shift: bool, alt: bool) {
         self.ctrl_pressed = ctrl;
         self.shift_pressed = shift;
         self.alt_pressed = alt;
     }
 
-    fn hit_test(&self, px: f32, py: f32, _ctx: &UiContext) -> bool {
-        let (x, y, w, h) = self.rect();
-        px >= x && px <= x + w && py >= y && py <= y + h
-    }
+    fn on_event(&mut self, event: &Event, _ectx: &mut cce_ui::widget::EventCtx) -> bool {
+        // Wheel arrives hit-gated to the pane rect (the adapter's gate replaces the old
+        // leading self.hit_test); the rotate/zoom handling is the legacy body verbatim.
+        let Event::MouseWheel { delta, .. } = event else { return false };
 
-    fn mouse_wheel(&mut self, delta: &MouseScrollDelta, px: f32, py: f32, ctx: &mut UiContext) -> bool {
-        if !self.hit_test(px, py, ctx) {
-            return false;
-        }
         let scale = cce_ui::scale::scale_factor();
         if self.ctrl_pressed {
             match delta {
@@ -237,9 +233,11 @@ impl Element for Viewport3D {
                 }
             }
         }
+    
     }
 
-    fn tick(&mut self, dt: f32, _ctx: &mut UiContext) -> bool {
+    fn tick(&mut self, dt: f32, _rect: cce_ui::scene::layout::Rect) -> bool {
+
         let now = std::time::Instant::now();
         let mut changed = false;
 
@@ -309,5 +307,6 @@ impl Element for Viewport3D {
         }
 
         changed
+    
     }
 }