git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commit73b057f95658f94969080006b5f2927bb279cd4a
parentfe5f493efa
authorLucas Galante <[email protected]>
date2026-07-15 14:53
fix(context): self-heal a stale drag grab on the next fresh press

If a button release never reaches the app (eaten compositor-side or
lost to a focus change), active_grab stayed armed forever and every
subsequent mouse event was redirected to the stale drag target — the
entire UI stopped responding until restart. A fresh press at a new
position now ends the stale drag (DragEnd to the old target) and drops
the grab before dispatching normally.

Co-Authored-By: Claude Fable 5 <[email protected]>

 src/context.rs | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/src/context.rs b/src/context.rs
index b183a80..952792f 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -194,6 +194,20 @@ impl UiContext {
                         // its first move. drag_start_pos is cleared on release, so an
                         // equal position here means "same press, next root".
                         if self.drag_start_pos != Some((*x, *y)) {
+                            // A fresh press while a grab is still armed means the
+                            // release never arrived (lost to a focus change or eaten
+                            // compositor-side). End the stale drag and drop the grab —
+                            // otherwise active_grab redirects every event to the old
+                            // target forever and the whole UI stops responding.
+                            if self.active_grab.is_some() {
+                                if self.is_dragging {
+                                    if let Some(target_ptr) = self.drag_target.and_then(|id| self.tree.get_ptr(id)) {
+                                        (*target_ptr).handle_event(&Event::DragEnd, self);
+                                        (*target_ptr).mark_dirty(self);
+                                    }
+                                }
+                                self.active_grab = None;
+                            }
                             self.drag_start_pos = Some((*x, *y));
                             self.is_dragging = false;
                             self.drag_target = None;