git.lucas.co / cce-gallery
widget gallery and compositor test bench

commitcba99cc594c7e6904c13c6de46f8a86acc76211d
parent7a42a04614
authorLucas Galante <[email protected]>
date2026-07-12 20:30
refactor: cce-test-interface on routed events (6bd shrink)

The gallery/child roster dispatch routes one Event per visible root through
ui_context.propagate_event: the app-held drag_widget index is deleted (the
router owns the drag lifecycle; the legacy also-arm-on-unhandled-press-if-
draggable rule is preserved by recording ui_context.drag_target explicitly
in that fallback), the rev-order press scan + app focus policy stay, the
release reaches every visible root (commit contract), and the keyboard path
keeps its focused-first-then-broadcast shape. take_click drains stay.

ControlPanel's internal scroll-frame forwarding (ti_widgets.rs) is
container-owned — it translates child coords by its scroll offset and dies
with CP's dissolution (the recorded endgame item), not with this sweep.

Verified live A/B (identical pointer sequences): button click, toggle,
Layout-dropdown popover, and 3x wheel-on-slider — all empty 8% masks, with
real state deltas within each build confirming the interactions fired.

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

 src/main.rs | 64 ++++++++++++++++++++++++++++++++++---------------------------
 1 file changed, 36 insertions(+), 28 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 6cd04f4..261fde4 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -332,7 +332,6 @@ struct State {
 
     status_text: String,
 
-    drag_widget: Option<usize>,
     focused_widget: Option<usize>,
 
     cursor_x: f32,
@@ -859,7 +858,6 @@ cascades in cce."
             roster,
             positions,
             status_text,
-            drag_widget: None,
             focused_widget: None,
             cursor_x: 0.0,
             cursor_y: 0.0,
@@ -1458,12 +1456,11 @@ cascades in cce."
         self.cursor_y = ly;
 
         let mut changed = false;
-        if let Some(idx) = self.drag_widget {
-            if self.roster.get_dyn_mut(idx).drag_update(lx, ly) {
-                changed = true;
-            }
-        }
-        if self.drag_widget.is_none() {
+        // Routed dispatch (6bd shrink): the router owns the drag lifecycle — one
+        // PointerMove per visible root forwards DragUpdate to a live drag target and
+        // runs hover bookkeeping otherwise.
+        let mv = cce_ui::widget::Event::PointerMove { x: lx, y: ly, local_x: lx, local_y: ly };
+        {
             let is_child = self.is_child;
             let use_menubar = self.use_menubar;
             let use_statusbar = self.use_statusbar;
@@ -1487,17 +1484,20 @@ cascades in cce."
                 }
             };
             for i in 0..self.roster.len() {
-            let w = self.roster.get_dyn_mut(i);
                 if !is_visible(i) {
                     continue;
                 }
                 if is_control_panel_child(i) {
                     continue;
                 }
-                if w.cursor_moved(lx, ly, &mut self.ui_context) {
+                let ptr = self.roster.get_dyn_mut(i).as_ptr_mut();
+                if self.ui_context.propagate_event(&mv, ptr) {
                     changed = true;
                 }
             }
+            if self.ui_context.is_dragging {
+                changed = true;
+            }
         }
         if changed {
             *needs_rebuild = true;
@@ -1556,12 +1556,19 @@ cascades in cce."
                 }
             }
             if let Some(i) = clicked_idx {
-                if self.roster.get_dyn_mut(i).mouse_input(button, state, lx, ly, &mut self.ui_context) {
+                // Routed press: the router records the drag target on a handled press
+                // and synthesizes DragStart past its threshold (the old immediate
+                // drag_begin). Legacy also armed drags whose press handler returned
+                // false — preserve that by recording the target explicitly.
+                let ev = cce_ui::widget::Event::MouseButton { button, state, x: lx, y: ly, local_x: lx, local_y: ly };
+                let ptr = self.roster.get_dyn_mut(i).as_ptr_mut();
+                let press_handled = self.ui_context.propagate_event(&ev, ptr);
+                if press_handled {
                     changed = true;
                 }
-                if button == MouseButton::Left && self.roster.get_dyn_mut(i).draggable() {
-                    self.roster.get_dyn_mut(i).drag_begin(lx, ly);
-                    self.drag_widget = Some(i);
+                if button == MouseButton::Left && !press_handled && self.roster.get_dyn(i).draggable() {
+                    let id = self.roster.get_dyn(i).base().id();
+                    self.ui_context.drag_target = Some(id);
                 }
                 if button == MouseButton::Left {
                     self.roster.get_dyn_mut(i).focus();
@@ -1569,23 +1576,21 @@ cascades in cce."
                 }
             }
         } else {
-            if button == MouseButton::Left {
-                if let Some(idx) = self.drag_widget {
-                    self.roster.get_dyn_mut(idx).drag_end();
-                    self.drag_widget = None;
-                    changed = true;
-                }
+            // The router delivers DragEnd to the drag target on the first propagate call
+            // of a release; every visible root then sees the release (commit contract).
+            if self.ui_context.is_dragging {
+                changed = true;
             }
-
+            let ev = cce_ui::widget::Event::MouseButton { button, state, x: lx, y: ly, local_x: lx, local_y: ly };
             for i in 0..self.roster.len() {
-            let w = self.roster.get_dyn_mut(i);
                 if !is_visible(i) {
                     continue;
                 }
                 if is_control_panel_child(i) {
                     continue;
                 }
-                if w.mouse_input(button, state, lx, ly, &mut self.ui_context) {
+                let ptr = self.roster.get_dyn_mut(i).as_ptr_mut();
+                if self.ui_context.propagate_event(&ev, ptr) {
                     changed = true;
                 }
             }
@@ -1865,15 +1870,16 @@ full screen background.",
                 }
             }
         };
+        let ev = cce_ui::widget::Event::MouseWheel { delta: *delta, x: lx, y: ly, local_x: lx, local_y: ly };
         for i in 0..self.roster.len() {
-            let w = self.roster.get_dyn_mut(i);
             if !is_visible(i) {
                 continue;
             }
             if is_control_panel_child(i) {
                 continue;
             }
-            if w.mouse_wheel(delta, lx, ly, &mut self.ui_context) {
+            let ptr = self.roster.get_dyn_mut(i).as_ptr_mut();
+            if self.ui_context.propagate_event(&ev, ptr) {
                 changed = true;
             }
         }
@@ -1907,8 +1913,10 @@ full screen background.",
             }
         };
         let mut handled = false;
+        let key_ev = cce_ui::widget::Event::KeyInput(event.clone());
         if let Some(focused) = self.focused_widget {
-            if self.roster.get_dyn_mut(focused).keyboard_input(event, &mut self.ui_context) {
+            let ptr = self.roster.get_dyn_mut(focused).as_ptr_mut();
+            if self.ui_context.propagate_event(&key_ev, ptr) {
                 changed = true;
                 handled = true;
             }
@@ -1916,7 +1924,6 @@ full screen background.",
 
         if !handled {
             for i in 0..self.roster.len() {
-            let w = self.roster.get_dyn_mut(i);
                 if Some(i) == self.focused_widget {
                     continue;
                 }
@@ -1926,7 +1933,8 @@ full screen background.",
                 if is_control_panel_child(i) {
                     continue;
                 }
-                if w.keyboard_input(event, &mut self.ui_context) {
+                let ptr = self.roster.get_dyn_mut(i).as_ptr_mut();
+                if self.ui_context.propagate_event(&key_ev, ptr) {
                     changed = true;
                 }
             }