diff --git a/examples/layer-shell.c b/examples/layer-shell.c index ac6e8a9e..56fa28f7 100644 --- a/examples/layer-shell.c +++ b/examples/layer-shell.c @@ -145,7 +145,8 @@ static const struct xdg_surface_listener xdg_surface_listener = { static void xdg_popup_configure(void *data, struct xdg_popup *xdg_popup, int32_t x, int32_t y, int32_t width, int32_t height) { - // Meh. + wlr_log(L_DEBUG, "Popup configured %dx%d@%d,%d", + width, height, x, y); } static void xdg_popup_done(void *data, struct xdg_popup *xdg_popup) { @@ -176,8 +177,6 @@ static void create_popup() { xdg_positioner_set_anchor_rect(xdg_positioner, cur_x, cur_y, 1, 1); xdg_positioner_set_anchor(xdg_positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT); xdg_positioner_set_gravity(xdg_positioner, XDG_POSITIONER_GRAVITY_TOP_LEFT); - xdg_positioner_set_constraint_adjustment(xdg_positioner, - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE); popup = xdg_surface_get_popup(xdg_surface, NULL, xdg_positioner); assert(popup); diff --git a/include/wlr/types/wlr_xdg_shell.h b/include/wlr/types/wlr_xdg_shell.h index 809d6390..ead4613a 100644 --- a/include/wlr/types/wlr_xdg_shell.h +++ b/include/wlr/types/wlr_xdg_shell.h @@ -258,6 +258,46 @@ void wlr_xdg_surface_send_close(struct wlr_xdg_surface *surface); void wlr_xdg_surface_popup_get_position(struct wlr_xdg_surface *surface, double *popup_sx, double *popup_sy); +/** + * Get the geometry for this positioner based on the anchor rect, gravity, and + * size of this positioner. + */ +struct wlr_box wlr_xdg_positioner_get_geometry( + struct wlr_xdg_positioner *positioner); + +/** + * Get the anchor point for this popup in the toplevel parent's coordinate system. + */ +void wlr_xdg_popup_get_anchor_point(struct wlr_xdg_popup *popup, + int *toplevel_sx, int *toplevel_sy); + +/** + * Convert the given coordinates in the popup coordinate system to the toplevel + * surface coordinate system. + */ +void wlr_xdg_popup_get_toplevel_coords(struct wlr_xdg_popup *popup, + int popup_sx, int popup_sy, int *toplevel_sx, int *toplevel_sy); + +/** + * Set the geometry of this popup to unconstrain it according to its + * xdg-positioner rules. The box should be in the popup's root toplevel parent + * surface coordinate system. + */ +void wlr_xdg_popup_unconstrain_from_box(struct wlr_xdg_popup *popup, + struct wlr_box *toplevel_sx_box); + +/** + Invert the right/left anchor and gravity for this positioner. This can be + used to "flip" the positioner around the anchor rect in the x direction. + */ +void wlr_positioner_invert_x(struct wlr_xdg_positioner *positioner); + +/** + Invert the top/bottom anchor and gravity for this positioner. This can be + used to "flip" the positioner around the anchor rect in the y direction. + */ +void wlr_positioner_invert_y(struct wlr_xdg_positioner *positioner); + /** * Find a surface within this xdg-surface tree at the given surface-local * coordinates. Returns the surface and coordinates in the leaf surface diff --git a/rootston/xdg_shell.c b/rootston/xdg_shell.c index 927bd018..bd670a87 100644 --- a/rootston/xdg_shell.c +++ b/rootston/xdg_shell.c @@ -48,6 +48,59 @@ static void popup_handle_new_popup(struct wl_listener *listener, void *data) { popup_create(popup->view_child.view, wlr_popup); } +static void popup_unconstrain(struct roots_xdg_popup *popup) { + // get the output of the popup's positioner anchor point and convert it to + // the toplevel parent's coordinate system and then pass it to + // wlr_xdg_popup_v6_unconstrain_from_box + + // TODO: unconstrain popups for rotated windows + if (popup->view_child.view->rotation != 0.0) { + return; + } + + struct roots_view *view = popup->view_child.view; + struct wlr_output_layout *layout = view->desktop->layout; + struct wlr_xdg_popup *wlr_popup = popup->wlr_popup; + + int anchor_lx, anchor_ly; + wlr_xdg_popup_get_anchor_point(wlr_popup, &anchor_lx, &anchor_ly); + + int popup_lx, popup_ly; + wlr_xdg_popup_get_toplevel_coords(wlr_popup, wlr_popup->geometry.x, + wlr_popup->geometry.y, &popup_lx, &popup_ly); + popup_lx += view->x; + popup_ly += view->y; + + anchor_lx += popup_lx; + anchor_ly += popup_ly; + + double dest_x = 0, dest_y = 0; + wlr_output_layout_closest_point(layout, NULL, anchor_lx, anchor_ly, + &dest_x, &dest_y); + + struct wlr_output *output = + wlr_output_layout_output_at(layout, dest_x, dest_y); + + if (output == NULL) { + return; + } + + int width = 0, height = 0; + wlr_output_effective_resolution(output, &width, &height); + + // the output box expressed in the coordinate system of the toplevel parent + // of the popup + struct wlr_box output_toplevel_sx_box = { + .x = output->lx - view->x, + .y = output->ly - view->y, + .width = width, + .height = height + }; + + wlr_xdg_popup_unconstrain_from_box( + popup->wlr_popup, &output_toplevel_sx_box); +} + static struct roots_xdg_popup *popup_create(struct roots_view *view, struct wlr_xdg_popup *wlr_popup) { struct roots_xdg_popup *popup = @@ -66,6 +119,9 @@ static struct roots_xdg_popup *popup_create(struct roots_view *view, wl_signal_add(&wlr_popup->base->events.unmap, &popup->unmap); popup->new_popup.notify = popup_handle_new_popup; wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup); + + popup_unconstrain(popup); + return popup; } diff --git a/types/wlr_xdg_shell.c b/types/wlr_xdg_shell.c index 22d404a7..78b18563 100644 --- a/types/wlr_xdg_shell.c +++ b/types/wlr_xdg_shell.c @@ -1608,6 +1608,268 @@ struct wlr_surface *wlr_xdg_surface_surface_at( return wlr_surface_surface_at(surface->surface, sx, sy, sub_x, sub_y); } +void wlr_xdg_popup_get_anchor_point(struct wlr_xdg_popup *popup, + int *root_sx, int *root_sy) { + struct wlr_box rect = popup->positioner.anchor_rect; + enum xdg_positioner_anchor anchor = popup->positioner.anchor; + int sx = 0, sy = 0; + + if (anchor == XDG_POSITIONER_ANCHOR_NONE) { + sx = (rect.x + rect.width) / 2; + sy = (rect.y + rect.height) / 2; + } else if (anchor == XDG_POSITIONER_ANCHOR_TOP) { + sx = (rect.x + rect.width) / 2; + sy = rect.y; + } else if (anchor == XDG_POSITIONER_ANCHOR_BOTTOM) { + sx = (rect.x + rect.width) / 2; + sy = rect.y + rect.height; + } else if (anchor == XDG_POSITIONER_ANCHOR_LEFT) { + sx = rect.x; + sy = (rect.y + rect.height) / 2; + } else if (anchor == XDG_POSITIONER_ANCHOR_RIGHT) { + sx = rect.x + rect.width; + sy = (rect.y + rect.height) / 2; + } else if (anchor == (XDG_POSITIONER_ANCHOR_TOP | + XDG_POSITIONER_ANCHOR_LEFT)) { + sx = rect.x; + sy = rect.y; + } else if (anchor == (XDG_POSITIONER_ANCHOR_TOP | + XDG_POSITIONER_ANCHOR_RIGHT)) { + sx = rect.x + rect.width; + sy = rect.y; + } else if (anchor == (XDG_POSITIONER_ANCHOR_BOTTOM | + XDG_POSITIONER_ANCHOR_LEFT)) { + sx = rect.x; + sy = rect.y + rect.height; + } else if (anchor == (XDG_POSITIONER_ANCHOR_BOTTOM | + XDG_POSITIONER_ANCHOR_RIGHT)) { + sx = rect.x + rect.width; + sy = rect.y + rect.height; + } + + *root_sx = sx; + *root_sy = sy; +} + +void wlr_xdg_popup_get_toplevel_coords(struct wlr_xdg_popup *popup, + int popup_sx, int popup_sy, int *toplevel_sx, int *toplevel_sy) { + assert(strcmp(popup->parent->role, wlr_desktop_xdg_toplevel_role) == 0 + || strcmp(popup->parent->role, wlr_desktop_xdg_popup_role) == 0); + struct wlr_xdg_surface *parent = popup->parent->role_data; + while (parent != NULL && parent->role == WLR_XDG_SURFACE_ROLE_POPUP) { + popup_sx += parent->popup->geometry.x; + popup_sy += parent->popup->geometry.y; + parent = parent->popup->parent->role_data; + } + + assert(parent); + + *toplevel_sx = popup_sx + parent->geometry.x; + *toplevel_sy = popup_sy + parent->geometry.y; + +} + +static void wlr_xdg_popup_box_constraints(struct wlr_xdg_popup *popup, + struct wlr_box *toplevel_sx_box, int *offset_x, int *offset_y) { + int popup_width = popup->geometry.width; + int popup_height = popup->geometry.height; + int anchor_sx = 0, anchor_sy = 0; + wlr_xdg_popup_get_anchor_point(popup, &anchor_sx, &anchor_sy); + int popup_sx = 0, popup_sy = 0; + wlr_xdg_popup_get_toplevel_coords(popup, popup->geometry.x, + popup->geometry.y, &popup_sx, &popup_sy); + *offset_x = 0, *offset_y = 0; + + if (popup_sx < toplevel_sx_box->x) { + *offset_x = toplevel_sx_box->x - popup_sx; + } else if (popup_sx + popup_width > + toplevel_sx_box->x + toplevel_sx_box->width) { + *offset_x = toplevel_sx_box->x + toplevel_sx_box->width - + (popup_sx + popup_width); + } + + if (popup_sy < toplevel_sx_box->y) { + *offset_y = toplevel_sx_box->y - popup_sy; + } else if (popup_sy + popup_height > + toplevel_sx_box->y + toplevel_sx_box->height) { + *offset_y = toplevel_sx_box->y + toplevel_sx_box->height - + (popup_sy + popup_height); + } +} + +static bool wlr_xdg_popup_unconstrain_flip(struct wlr_xdg_popup *popup, + struct wlr_box *toplevel_sx_box) { + int offset_x = 0, offset_y = 0; + wlr_xdg_popup_box_constraints(popup, toplevel_sx_box, + &offset_x, &offset_y); + + if (!offset_x && !offset_y) { + return true; + } + + bool flip_x = offset_x && + (popup->positioner.constraint_adjustment & + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X); + + bool flip_y = offset_y && + (popup->positioner.constraint_adjustment & + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y); + + if (flip_x) { + wlr_positioner_invert_x(&popup->positioner); + } + if (flip_y) { + wlr_positioner_invert_y(&popup->positioner); + } + + popup->geometry = + wlr_xdg_positioner_get_geometry(&popup->positioner); + + wlr_xdg_popup_box_constraints(popup, toplevel_sx_box, + &offset_x, &offset_y); + + if (!offset_x && !offset_y) { + // no longer constrained + return true; + } + + // revert the positioner back if it didn't fix it and go to the next part + if (flip_x) { + wlr_positioner_invert_x(&popup->positioner); + } + if (flip_y) { + wlr_positioner_invert_y(&popup->positioner); + } + + popup->geometry = + wlr_xdg_positioner_get_geometry(&popup->positioner); + + return false; +} + +static bool wlr_xdg_popup_unconstrain_slide(struct wlr_xdg_popup *popup, + struct wlr_box *toplevel_sx_box) { + int offset_x = 0, offset_y = 0; + wlr_xdg_popup_box_constraints(popup, toplevel_sx_box, + &offset_x, &offset_y); + + if (!offset_x && !offset_y) { + return true; + } + + bool slide_x = offset_x && + (popup->positioner.constraint_adjustment & + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X); + + bool slide_y = offset_x && + (popup->positioner.constraint_adjustment & + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y); + + if (slide_x) { + popup->geometry.x += offset_x; + } + + if (slide_y) { + popup->geometry.y += offset_y; + } + + int toplevel_x = 0, toplevel_y = 0; + wlr_xdg_popup_get_toplevel_coords(popup, popup->geometry.x, + popup->geometry.y, &toplevel_x, &toplevel_y); + + if (slide_x && toplevel_x < toplevel_sx_box->x) { + popup->geometry.x += toplevel_sx_box->x - toplevel_x; + } + if (slide_y && toplevel_y < toplevel_sx_box->y) { + popup->geometry.y += toplevel_sx_box->y - toplevel_y; + } + + wlr_xdg_popup_box_constraints(popup, toplevel_sx_box, + &offset_x, &offset_y); + + return !offset_x && !offset_y; +} + +static bool wlr_xdg_popup_unconstrain_resize(struct wlr_xdg_popup *popup, + struct wlr_box *toplevel_sx_box) { + int offset_x, offset_y; + wlr_xdg_popup_box_constraints(popup, toplevel_sx_box, + &offset_x, &offset_y); + + if (!offset_x && !offset_y) { + return true; + } + + bool resize_x = offset_x && + (popup->positioner.constraint_adjustment & + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X); + + bool resize_y = offset_x && + (popup->positioner.constraint_adjustment & + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y); + + if (resize_x) { + popup->geometry.width -= offset_x; + } + if (resize_y) { + popup->geometry.height -= offset_y; + } + + wlr_xdg_popup_box_constraints(popup, toplevel_sx_box, + &offset_y, &offset_y); + + return !offset_x && !offset_y; +} + +void wlr_xdg_popup_unconstrain_from_box(struct wlr_xdg_popup *popup, + struct wlr_box *toplevel_sx_box) { + if (wlr_xdg_popup_unconstrain_flip(popup, toplevel_sx_box)) { + return; + } + if (wlr_xdg_popup_unconstrain_slide(popup, toplevel_sx_box)) { + return; + } + if (wlr_xdg_popup_unconstrain_resize(popup, toplevel_sx_box)) { + return; + } +} + +void wlr_positioner_invert_x(struct wlr_xdg_positioner *positioner) { + if (positioner->anchor & XDG_POSITIONER_ANCHOR_LEFT) { + positioner->anchor &= ~XDG_POSITIONER_ANCHOR_LEFT; + positioner->anchor |= XDG_POSITIONER_ANCHOR_RIGHT; + } else if (positioner->anchor & XDG_POSITIONER_ANCHOR_RIGHT) { + positioner->anchor &= ~XDG_POSITIONER_ANCHOR_RIGHT; + positioner->anchor |= XDG_POSITIONER_ANCHOR_LEFT; + } + + if (positioner->gravity & XDG_POSITIONER_GRAVITY_RIGHT) { + positioner->gravity &= ~XDG_POSITIONER_GRAVITY_RIGHT; + positioner->gravity |= XDG_POSITIONER_GRAVITY_LEFT; + } else if (positioner->gravity & XDG_POSITIONER_GRAVITY_LEFT) { + positioner->gravity &= ~XDG_POSITIONER_GRAVITY_LEFT; + positioner->gravity |= XDG_POSITIONER_GRAVITY_RIGHT; + } +} + +void wlr_positioner_invert_y( + struct wlr_xdg_positioner *positioner) { + if (positioner->anchor & XDG_POSITIONER_ANCHOR_TOP) { + positioner->anchor &= ~XDG_POSITIONER_ANCHOR_TOP; + positioner->anchor |= XDG_POSITIONER_ANCHOR_BOTTOM; + } else if (positioner->anchor & XDG_POSITIONER_ANCHOR_BOTTOM) { + positioner->anchor &= ~XDG_POSITIONER_ANCHOR_BOTTOM; + positioner->anchor |= XDG_POSITIONER_ANCHOR_TOP; + } + + if (positioner->gravity & XDG_POSITIONER_GRAVITY_TOP) { + positioner->gravity &= ~XDG_POSITIONER_GRAVITY_TOP; + positioner->gravity |= XDG_POSITIONER_GRAVITY_BOTTOM; + } else if (positioner->gravity & XDG_POSITIONER_GRAVITY_BOTTOM) { + positioner->gravity &= ~XDG_POSITIONER_GRAVITY_BOTTOM; + positioner->gravity |= XDG_POSITIONER_GRAVITY_TOP; + } +} struct xdg_surface_iterator_data { wlr_surface_iterator_func_t user_iterator;