wayland: fix dialog/menu hit-testing and parenting

Several related fixes for WINGs dialogs and menus that depend on
correct scene graph parenting and hit-testing:

1. fake_leader_create: now creates a wlr_scene_tree (not just a
   scene_buf). When the fake_leader is reparented into a WMaker frame,
   any content inside it (e.g. WINGs panel) is reparented as part of
   the tree, so it follows the WMaker frame's position when moved.

2. wl_client_reparent (frame_buf path): if the source frame_buf has
   a tree, reparent the tree (with all its children) instead of the
   bare scene_buf.

3. frame_buf_update_children_abs: new recursive helper to walk the
   entire descendant tree updating fb->x/y absolute tracking. Used
   by wl_window_move, wl_frame_configure, and wl_client_reparent.
   Without this, grandchildren (e.g. WINGs panel inside a fake_leader
   inside a WMaker frame) had stale fb->x/y after the WMaker frame
   was moved, causing click coordinate translation to be wrong and
   buttons to stop responding after dragging the dialog.

4. frame_buf_button_at: now walks up the scene tree checking ancestor
   enabled state. A frame_buf's scene_buf may be flagged enabled even
   when an ancestor tree is disabled (e.g. menu unmapped). Without this
   check, clicks could be incorrectly routed to ghost frame_bufs from
   recently-unmapped menus, causing root-menu reopen to require
   multiple clicks.
This commit is contained in:
Window Maker 2026-05-23 21:27:19 +02:00
parent bc25aa4b8a
commit c7e2b7ba88
1 changed files with 70 additions and 46 deletions

View File

@ -3981,6 +3981,34 @@ frame_buf_find(WNativeWindow id)
return NULL;
}
/* Recursively update fb->x, fb->y for all descendants of `parent`, used
* to keep absolute-position tracking in sync after the parent moves.
* Hit-testing (frame_buf_at, frame_buf_button_at) and click coordinate
* translation depend on these fields being current. */
static void
frame_buf_update_children_abs(WNativeWindow parent_id)
{
struct wl_frame_buf *parent = frame_buf_find(parent_id);
if (!parent) return;
struct wl_frame_buf *child;
wl_list_for_each(child, &wl_state.frame_list, link) {
if (child->parent_id == parent_id) {
child->x = parent->x + child->rel_x;
child->y = parent->y + child->rel_y;
/* Recurse into grandchildren */
frame_buf_update_children_abs(child->id);
}
}
/* Also update toplevel views parented to this frame */
struct wl_toplevel_view *cv;
wl_list_for_each(cv, &wl_state.toplevel_list, link) {
if (cv->frame_id == parent_id) {
cv->x = parent->x + cv->frame_rel_x;
cv->y = parent->y + cv->frame_rel_y;
}
}
}
/* Hit-test: find the frame_buf under (px, py) that should receive the event.
*
* Returns the most-specific (innermost) frame_buf whose rendered pixels
@ -4077,6 +4105,17 @@ frame_buf_button_at(int px, int py)
if (!fb->parent_id) continue; /* top-level root */
if (!fb->scene_buf) continue; /* no scene node */
if (!fb->scene_buf->node.enabled) continue; /* hidden */
/* Walk up the scene tree — if any ancestor is disabled, this
* frame_buf is not actually visible (just its node enabled flag
* is set, but a parent tree was disabled e.g. by window_unmap). */
struct wlr_scene_node *n = fb->scene_buf->node.parent
? &fb->scene_buf->node.parent->node : NULL;
int ancestor_disabled = 0;
while (n) {
if (!n->enabled) { ancestor_disabled = 1; break; }
n = n->parent ? &n->parent->node : NULL;
}
if (ancestor_disabled) continue;
/* Buttons are small children of the titlebar with enabled scene_bufs.
* Skip large nodes (titlebars, resizebars) by size threshold. */
if (px < fb->x || px >= fb->x + fb->width) continue;
@ -4278,22 +4317,8 @@ wl_frame_configure(WNativeWindow win, int x, int y, int width, int height)
wlr_scene_buffer_set_dest_size(fb->scene_buf, width, height);
}
/* Update absolute position tracking for children (used by hit-testing) */
struct wl_toplevel_view *cv;
wl_list_for_each(cv, &wl_state.toplevel_list, link) {
if (cv->frame_id == win) {
cv->x = fb->x + cv->frame_rel_x;
cv->y = fb->y + cv->frame_rel_y;
break;
}
}
struct wl_frame_buf *child;
wl_list_for_each(child, &wl_state.frame_list, link) {
if (child->parent_id == win) {
child->x = fb->x + child->rel_x;
child->y = fb->y + child->rel_y;
}
}
/* Update absolute position tracking — recursively for all descendants. */
frame_buf_update_children_abs(win);
}
static void wl_frame_paint_button(WCoreWindow *button, WTexture *texture,
@ -6551,22 +6576,10 @@ static void wl_window_move(WNativeWindow win, int x, int y)
if (fb->tree)
wlr_scene_node_set_position(&fb->tree->node, x, y);
}
/* Update absolute position tracking for hit-testing */
struct wl_toplevel_view *cv;
wl_list_for_each(cv, &wl_state.toplevel_list, link) {
if (cv->frame_id == win) {
cv->x = fb->x + cv->frame_rel_x;
cv->y = fb->y + cv->frame_rel_y;
break;
}
}
struct wl_frame_buf *child;
wl_list_for_each(child, &wl_state.frame_list, link) {
if (child->parent_id == win) {
child->x = fb->x + child->rel_x;
child->y = fb->y + child->rel_y;
}
}
/* Update absolute position tracking for hit-testing — recursively
* for all descendants so grandchildren (e.g. WINGs panel inside
* a fake_leader inside a WMaker frame) stay in sync. */
frame_buf_update_children_abs(win);
return;
}
}
@ -6948,20 +6961,20 @@ wl_client_reparent(WNativeWindow client, WNativeWindow new_parent, int x, int y)
fb->rel_y = y;
fb->x = parent_fb->x + x;
fb->y = parent_fb->y + y;
if (fb->scene_buf) {
/* Reparent under parent's tree and use relative position */
if (fb->tree) {
/* This frame_buf has a tree (e.g. fake_leader): reparent
* the whole tree so all children move with it. */
if (parent_fb->tree)
wlr_scene_node_reparent(&fb->tree->node, parent_fb->tree);
wlr_scene_node_set_position(&fb->tree->node, x, y);
} else if (fb->scene_buf) {
/* Plain frame_buf (no tree): reparent scene_buf directly. */
if (parent_fb->tree)
wlr_scene_node_reparent(&fb->scene_buf->node, parent_fb->tree);
wlr_scene_node_set_position(&fb->scene_buf->node, x, y);
}
/* Update absolute tracking for children */
struct wl_frame_buf *child;
wl_list_for_each(child, &wl_state.frame_list, link) {
if (child->parent_id == client) {
child->x = fb->x + child->rel_x;
child->y = fb->y + child->rel_y;
}
}
/* Update absolute tracking recursively for all descendants */
frame_buf_update_children_abs(client);
}
}
}
@ -7140,7 +7153,11 @@ wl_icccm_free_command(char **argv)
static WNativeWindow wl_fake_leader_create(WScreen *scr)
{
(void)scr;
/* Create a frame_buf to act as the dialog's content container */
/* Create a frame_buf with a tree to act as the dialog's content
* container. The tree is essential — content reparented into the
* fake_leader (e.g. WINGs panel views) becomes a child of the tree
* and follows it when the fake_leader itself is reparented into
* a WMaker frame. */
struct wl_frame_buf *fb = wmalloc(sizeof(*fb));
memset(fb, 0, sizeof(*fb));
@ -7154,9 +7171,16 @@ static WNativeWindow wl_fake_leader_create(WScreen *scr)
if (wl_state.scene) {
struct wlr_scene_tree *_pp = wl_state.level_trees[WL_LAYER_NORMAL]
? wl_state.level_trees[WL_LAYER_NORMAL] : &wl_state.scene->tree;
fb->scene_buf = wlr_scene_buffer_create(_pp, &fb->base);
if (fb->scene_buf)
wlr_scene_node_set_enabled(&fb->scene_buf->node, false);
fb->tree = wlr_scene_tree_create(_pp);
if (fb->tree) {
wlr_scene_node_set_position(&fb->tree->node, 0, 0);
fb->scene_buf = wlr_scene_buffer_create(fb->tree, &fb->base);
if (fb->scene_buf) {
wlr_scene_node_set_position(&fb->scene_buf->node, 0, 0);
wlr_scene_node_set_enabled(&fb->scene_buf->node, false);
}
wlr_scene_node_set_enabled(&fb->tree->node, true);
}
}
wl_list_insert(&wl_state.frame_list, &fb->link);