ssd: fix resizing on border corners and add <resize><cornerRange>

Eliminate corner extents and instead use cursor position to map SSD
borders and extents to corner contexts, with a size configurable by the
<resize><cornerRange> parameter. This simplifies extent handling,
eliminates bugs in the detection of corner context, and allows users to
expand corner targets if they wish.

Co-authored-by: Andrew J. Hesford <ajh@sideband.org>
This commit is contained in:
tokyo4j 2025-02-01 14:51:47 +09:00 committed by Andrew J. Hesford
parent 9ad6e3c68c
commit 950337b895
9 changed files with 102 additions and 60 deletions

View file

@ -576,6 +576,13 @@ extending outward from the snapped edge.
outlined rectangle is shown to indicate the geometry of resized window. outlined rectangle is shown to indicate the geometry of resized window.
Default is yes. Default is yes.
*<resize><cornerRange>*
The size of corner regions to which the 'TLCorner', 'TRCorner',
'BLCorner' and 'RLCorner' mousebind contexts apply, as well as the size
of the border region for which mouse resizing will apply both
horizontally and vertically rather than one or the other. Default is
half the titlebar height.
## KEYBOARD ## KEYBOARD
*<keyboard><numlock>* [on|off] *<keyboard><numlock>* [on|off]

View file

@ -131,6 +131,7 @@
<popupShow>Never</popupShow> <popupShow>Never</popupShow>
<!-- Let client redraw its contents while resizing --> <!-- Let client redraw its contents while resizing -->
<drawContents>yes</drawContents> <drawContents>yes</drawContents>
<cornerRange>8</cornerRange>
</resize> </resize>
<focus> <focus>

View file

@ -151,6 +151,7 @@ struct rcxml {
enum resize_indicator_mode resize_indicator; enum resize_indicator_mode resize_indicator;
bool resize_draw_contents; bool resize_draw_contents;
int resize_corner_range;
struct { struct {
int popuptime; int popuptime;

View file

@ -5,6 +5,8 @@
#include <wayland-server-core.h> #include <wayland-server-core.h>
#include "common/border.h" #include "common/border.h"
struct wlr_cursor;
#define SSD_EXTENDED_AREA 8 #define SSD_EXTENDED_AREA 8
/* /*
@ -107,10 +109,8 @@ enum ssd_part_type ssd_button_get_type(const struct ssd_button *button);
struct view *ssd_button_get_view(const struct ssd_button *button); struct view *ssd_button_get_view(const struct ssd_button *button);
/* Public SSD helpers */ /* Public SSD helpers */
enum ssd_part_type ssd_at(const struct ssd *ssd,
struct wlr_scene *scene, double lx, double ly);
enum ssd_part_type ssd_get_part_type(const struct ssd *ssd, enum ssd_part_type ssd_get_part_type(const struct ssd *ssd,
struct wlr_scene_node *node); struct wlr_scene_node *node, struct wlr_cursor *cursor);
uint32_t ssd_resize_edges(enum ssd_part_type type); uint32_t ssd_resize_edges(enum ssd_part_type type);
bool ssd_part_contains(enum ssd_part_type whole, enum ssd_part_type candidate); bool ssd_part_contains(enum ssd_part_type whole, enum ssd_part_type candidate);
enum ssd_mode ssd_mode_parse(const char *mode); enum ssd_mode ssd_mode_parse(const char *mode);

View file

@ -1223,6 +1223,8 @@ entry(xmlNode *node, char *nodename, char *content, struct parser_state *state)
} }
} else if (!strcasecmp(nodename, "drawContents.resize")) { } else if (!strcasecmp(nodename, "drawContents.resize")) {
set_bool(content, &rc.resize_draw_contents); set_bool(content, &rc.resize_draw_contents);
} else if (!strcasecmp(nodename, "cornerRange.resize")) {
rc.resize_corner_range = atoi(content);
} else if (!strcasecmp(nodename, "mouseEmulation.tablet")) { } else if (!strcasecmp(nodename, "mouseEmulation.tablet")) {
set_bool(content, &rc.tablet.force_mouse_emulation); set_bool(content, &rc.tablet.force_mouse_emulation);
} else if (!strcasecmp(nodename, "mapToOutput.tablet")) { } else if (!strcasecmp(nodename, "mapToOutput.tablet")) {
@ -1498,6 +1500,7 @@ rcxml_init(void)
rc.resize_indicator = LAB_RESIZE_INDICATOR_NEVER; rc.resize_indicator = LAB_RESIZE_INDICATOR_NEVER;
rc.resize_draw_contents = true; rc.resize_draw_contents = true;
rc.resize_corner_range = -1;
rc.workspace_config.popuptime = INT_MIN; rc.workspace_config.popuptime = INT_MIN;
rc.workspace_config.min_nr_workspaces = 1; rc.workspace_config.min_nr_workspaces = 1;

View file

@ -278,7 +278,8 @@ get_cursor_context(struct server *server)
case LAB_NODE_DESC_VIEW: case LAB_NODE_DESC_VIEW:
case LAB_NODE_DESC_XDG_POPUP: case LAB_NODE_DESC_XDG_POPUP:
ret.view = desc->data; ret.view = desc->data;
ret.type = ssd_get_part_type(ret.view->ssd, ret.node); ret.type = ssd_get_part_type(
ret.view->ssd, ret.node, cursor);
if (ret.type == LAB_SSD_CLIENT) { if (ret.type == LAB_SSD_CLIENT) {
ret.surface = lab_wlr_surface_from_node(ret.node); ret.surface = lab_wlr_surface_from_node(ret.node);
} }

View file

@ -37,17 +37,10 @@ ssd_extents_create(struct ssd *ssd)
-(theme->border_width + extended_area), -(theme->border_width + extended_area),
-(ssd->titlebar.height + theme->border_width + extended_area)); -(ssd->titlebar.height + theme->border_width + extended_area));
/* Top */
add_extent(part_list, LAB_SSD_PART_CORNER_TOP_LEFT, parent);
add_extent(part_list, LAB_SSD_PART_TOP, parent); add_extent(part_list, LAB_SSD_PART_TOP, parent);
add_extent(part_list, LAB_SSD_PART_CORNER_TOP_RIGHT, parent);
/* Sides */
add_extent(part_list, LAB_SSD_PART_LEFT, parent); add_extent(part_list, LAB_SSD_PART_LEFT, parent);
add_extent(part_list, LAB_SSD_PART_RIGHT, parent); add_extent(part_list, LAB_SSD_PART_RIGHT, parent);
/* Bottom */
add_extent(part_list, LAB_SSD_PART_CORNER_BOTTOM_LEFT, parent);
add_extent(part_list, LAB_SSD_PART_BOTTOM, parent); add_extent(part_list, LAB_SSD_PART_BOTTOM, parent);
add_extent(part_list, LAB_SSD_PART_CORNER_BOTTOM_RIGHT, parent);
/* Initial manual update to keep X11 applications happy */ /* Initial manual update to keep X11 applications happy */
ssd_extents_update(ssd); ssd_extents_update(ssd);
@ -76,11 +69,6 @@ ssd_extents_update(struct ssd *ssd)
int full_height = height + theme->border_width * 2 + ssd->titlebar.height; int full_height = height + theme->border_width * 2 + ssd->titlebar.height;
int full_width = width + 2 * theme->border_width; int full_width = width + 2 * theme->border_width;
int extended_area = SSD_EXTENDED_AREA; int extended_area = SSD_EXTENDED_AREA;
int corner_width = ssd_get_corner_width();
int corner_size = extended_area + theme->border_width +
MIN(corner_width, width) / 2;
int side_width = full_width + extended_area * 2 - corner_size * 2;
int side_height = full_height + extended_area * 2 - corner_size * 2;
struct wlr_box part_box; struct wlr_box part_box;
struct wlr_box result_box; struct wlr_box result_box;
@ -120,54 +108,30 @@ ssd_extents_update(struct ssd *ssd)
wl_list_for_each(part, &ssd->extents.parts, link) { wl_list_for_each(part, &ssd->extents.parts, link) {
rect = wlr_scene_rect_from_node(part->node); rect = wlr_scene_rect_from_node(part->node);
switch (part->type) { switch (part->type) {
case LAB_SSD_PART_CORNER_TOP_LEFT: case LAB_SSD_PART_TOP:
target.x = 0; target.x = 0;
target.y = 0; target.y = 0;
target.width = corner_size; target.width = full_width + extended_area * 2;
target.height = corner_size;
break;
case LAB_SSD_PART_TOP:
target.x = corner_size;
target.y = 0;
target.width = side_width;
target.height = extended_area; target.height = extended_area;
break; break;
case LAB_SSD_PART_CORNER_TOP_RIGHT:
target.x = corner_size + side_width;
target.y = 0;
target.width = corner_size;
target.height = corner_size;
break;
case LAB_SSD_PART_LEFT: case LAB_SSD_PART_LEFT:
target.x = 0; target.x = 0;
target.y = corner_size; target.y = extended_area;
target.width = extended_area; target.width = extended_area;
target.height = side_height; target.height = full_height;
break; break;
case LAB_SSD_PART_RIGHT: case LAB_SSD_PART_RIGHT:
target.x = extended_area + full_width; target.x = extended_area + full_width;
target.y = corner_size; target.y = extended_area;
target.width = extended_area; target.width = extended_area;
target.height = side_height; target.height = full_height;
break;
case LAB_SSD_PART_CORNER_BOTTOM_LEFT:
target.x = 0;
target.y = corner_size + side_height;
target.width = corner_size;
target.height = corner_size;
break; break;
case LAB_SSD_PART_BOTTOM: case LAB_SSD_PART_BOTTOM:
target.x = corner_size; target.x = 0;
target.y = extended_area + full_height; target.y = extended_area + full_height;
target.width = side_width; target.width = full_width + extended_area * 2;
target.height = extended_area; target.height = extended_area;
break; break;
case LAB_SSD_PART_CORNER_BOTTOM_RIGHT:
target.x = corner_size + side_width;
target.y = corner_size + side_height;
target.width = corner_size;
target.height = corner_size;
break;
default: default:
/* not reached */ /* not reached */
assert(false); assert(false);

View file

@ -74,8 +74,69 @@ ssd_max_extents(struct view *view)
}; };
} }
/*
* Resizing and mouse contexts like 'Left', 'TLCorner', etc. in the vicinity of
* SSD borders, titlebars and extents can have effective "corner regions" that
* behave differently from single-edge contexts.
*
* Corner regions are active whenever the cursor is within a prescribed size
* (generally rc.resize_corner_range, but clipped to view size) of the view
* bounds, so check the cursor against the view here.
*/
static enum ssd_part_type
get_resizing_type(const struct ssd *ssd, struct wlr_cursor *cursor)
{
struct view *view = ssd ? ssd->view : NULL;
if (!view || !cursor || !view->ssd_enabled || view->fullscreen) {
return LAB_SSD_NONE;
}
struct wlr_box view_box = view->current;
view_box.height = view_effective_height(view, /* use_pending */ false);
if (!view->ssd_titlebar_hidden) {
/* If the titlebar is visible, consider it part of the view */
int titlebar_height = view->server->theme->titlebar_height;
view_box.y -= titlebar_height;
view_box.height += titlebar_height;
}
if (wlr_box_contains_point(&view_box, cursor->x, cursor->y)) {
/* A cursor in bounds of the view is never in an SSD context */
return LAB_SSD_NONE;
}
int corner_height = MAX(0, MIN(rc.resize_corner_range, view_box.height / 2));
int corner_width = MAX(0, MIN(rc.resize_corner_range, view_box.width / 2));
bool left = cursor->x < view_box.x + corner_width;
bool right = cursor->x > view_box.x + view_box.width - corner_width;
bool top = cursor->y < view_box.y + corner_height;
bool bottom = cursor->y > view_box.y + view_box.height - corner_height;
if (top && left) {
return LAB_SSD_PART_CORNER_TOP_LEFT;
} else if (top && right) {
return LAB_SSD_PART_CORNER_TOP_RIGHT;
} else if (bottom && left) {
return LAB_SSD_PART_CORNER_BOTTOM_LEFT;
} else if (bottom && right) {
return LAB_SSD_PART_CORNER_BOTTOM_RIGHT;
} else if (top) {
return LAB_SSD_PART_TOP;
} else if (bottom) {
return LAB_SSD_PART_BOTTOM;
} else if (left) {
return LAB_SSD_PART_LEFT;
} else if (right) {
return LAB_SSD_PART_RIGHT;
}
return LAB_SSD_NONE;
}
enum ssd_part_type enum ssd_part_type
ssd_get_part_type(const struct ssd *ssd, struct wlr_scene_node *node) ssd_get_part_type(const struct ssd *ssd, struct wlr_scene_node *node,
struct wlr_cursor *cursor)
{ {
if (!node) { if (!node) {
return LAB_SSD_NONE; return LAB_SSD_NONE;
@ -121,25 +182,25 @@ ssd_get_part_type(const struct ssd *ssd, struct wlr_scene_node *node)
part_list = &ssd->border.inactive.parts; part_list = &ssd->border.inactive.parts;
} }
enum ssd_part_type part_type = LAB_SSD_NONE;
if (part_list) { if (part_list) {
struct ssd_part *part; struct ssd_part *part;
wl_list_for_each(part, part_list, link) { wl_list_for_each(part, part_list, link) {
if (node == part->node) { if (node == part->node) {
return part->type; part_type = part->type;
break;
} }
} }
} }
return LAB_SSD_NONE;
}
enum ssd_part_type if (part_type == LAB_SSD_NONE) {
ssd_at(const struct ssd *ssd, struct wlr_scene *scene, double lx, double ly) return part_type;
{ }
assert(scene);
double sx, sy; /* Perform cursor-based context checks */
struct wlr_scene_node *node = wlr_scene_node_at( enum ssd_part_type resizing_type = get_resizing_type(ssd, cursor);
&scene->tree.node, lx, ly, &sx, &sy); return resizing_type != LAB_SSD_NONE ? resizing_type : part_type;
return ssd_get_part_type(ssd, node);
} }
uint32_t uint32_t

View file

@ -1388,6 +1388,10 @@ post_processing(struct theme *theme)
rc.corner_radius = theme->titlebar_height - 1; rc.corner_radius = theme->titlebar_height - 1;
} }
if (rc.resize_corner_range < 0) {
rc.resize_corner_range = theme->titlebar_height / 2;
}
int min_button_hover_radius = int min_button_hover_radius =
MIN(theme->window_button_width, theme->window_button_height) / 2; MIN(theme->window_button_width, theme->window_button_height) / 2;
if (theme->window_button_hover_bg_corner_radius > min_button_hover_radius) { if (theme->window_button_hover_bg_corner_radius > min_button_hover_radius) {