resistance: add window-edge resistance for interactive moves/resizes

This commit is contained in:
Andrew J. Hesford 2024-01-20 21:59:46 -05:00 committed by Andrew J. Hesford
parent f73e9ded1c
commit 57ea197e6c
6 changed files with 199 additions and 138 deletions

View file

@ -197,9 +197,14 @@ this is for compatibility with Openbox.
## RESISTANCE ## RESISTANCE
*<resistance><screenEdgeStrength>* *<resistance><screenEdgeStrength>*
Screen Edge Strength is how far past the screen's edge your cursor must Screen-edge strength is the distance, in pixels, past the edge of a
move before the window will move with it. Resistance is counted in screen the cursor must move before an interactive move or resize of a
pixels. Default is 20 pixels. window will continue past the edge. Default is 20 pixels.
*<resistance><windowEdgeStrength>*
Window-edge strength is the distance, in pixels, past the edge of any
other window the cursor must move before an interactive move or resize
of a window will continue past the edge. Default is 20 pixels.
## FOCUS ## FOCUS

View file

@ -66,6 +66,7 @@
<!-- edge strength is in pixels --> <!-- edge strength is in pixels -->
<resistance> <resistance>
<screenEdgeStrength>20</screenEdgeStrength> <screenEdgeStrength>20</screenEdgeStrength>
<windowEdgeStrength>20</windowEdgeStrength>
</resistance> </resistance>
<!-- Show a simple resize and move indicator --> <!-- Show a simple resize and move indicator -->

View file

@ -110,6 +110,7 @@ struct rcxml {
/* resistance */ /* resistance */
int screen_edge_strength; int screen_edge_strength;
int window_edge_strength;
/* window snapping */ /* window snapping */
int snap_edge_range; int snap_edge_range;

View file

@ -807,6 +807,8 @@ entry(xmlNode *node, char *nodename, char *content)
rc.kb_layout_per_window = !strcasecmp(content, "window"); rc.kb_layout_per_window = !strcasecmp(content, "window");
} else if (!strcasecmp(nodename, "screenEdgeStrength.resistance")) { } else if (!strcasecmp(nodename, "screenEdgeStrength.resistance")) {
rc.screen_edge_strength = atoi(content); rc.screen_edge_strength = atoi(content);
} else if (!strcasecmp(nodename, "windowEdgeStrength.resistance")) {
rc.window_edge_strength = atoi(content);
} else if (!strcasecmp(nodename, "range.snapping")) { } else if (!strcasecmp(nodename, "range.snapping")) {
rc.snap_edge_range = atoi(content); rc.snap_edge_range = atoi(content);
} else if (!strcasecmp(nodename, "topMaximize.snapping")) { } else if (!strcasecmp(nodename, "topMaximize.snapping")) {
@ -1061,6 +1063,7 @@ rcxml_init(void)
rc.kb_numlock_enable = true; rc.kb_numlock_enable = true;
rc.kb_layout_per_window = false; rc.kb_layout_per_window = false;
rc.screen_edge_strength = 20; rc.screen_edge_strength = 20;
rc.window_edge_strength = 20;
rc.snap_edge_range = 1; rc.snap_edge_range = 1;
rc.snap_top_maximize = true; rc.snap_top_maximize = true;

View file

@ -245,12 +245,16 @@ process_cursor_resize(struct server *server, uint32_t time)
struct wlr_box new_view_geo = view->current; struct wlr_box new_view_geo = view->current;
if (server->resize_edges & WLR_EDGE_TOP) { if (server->resize_edges & WLR_EDGE_TOP) {
/* Shift y to anchor bottom edge when resizing top */
new_view_geo.y = server->grab_box.y + dy;
new_view_geo.height = server->grab_box.height - dy; new_view_geo.height = server->grab_box.height - dy;
} else if (server->resize_edges & WLR_EDGE_BOTTOM) { } else if (server->resize_edges & WLR_EDGE_BOTTOM) {
new_view_geo.height = server->grab_box.height + dy; new_view_geo.height = server->grab_box.height + dy;
} }
if (server->resize_edges & WLR_EDGE_LEFT) { if (server->resize_edges & WLR_EDGE_LEFT) {
/* Shift x to anchor right edge when resizing left */
new_view_geo.x = server->grab_box.x + dx;
new_view_geo.width = server->grab_box.width - dx; new_view_geo.width = server->grab_box.width - dx;
} else if (server->resize_edges & WLR_EDGE_RIGHT) { } else if (server->resize_edges & WLR_EDGE_RIGHT) {
new_view_geo.width = server->grab_box.width + dx; new_view_geo.width = server->grab_box.width + dx;
@ -260,13 +264,13 @@ process_cursor_resize(struct server *server, uint32_t time)
view_adjust_size(view, &new_view_geo.width, &new_view_geo.height); view_adjust_size(view, &new_view_geo.width, &new_view_geo.height);
if (server->resize_edges & WLR_EDGE_TOP) { if (server->resize_edges & WLR_EDGE_TOP) {
/* anchor bottom edge */ /* After size adjustments, make sure to anchor bottom edge */
new_view_geo.y = server->grab_box.y + new_view_geo.y = server->grab_box.y +
server->grab_box.height - new_view_geo.height; server->grab_box.height - new_view_geo.height;
} }
if (server->resize_edges & WLR_EDGE_LEFT) { if (server->resize_edges & WLR_EDGE_LEFT) {
/* anchor right edge */ /* After size adjustments, make sure to anchor bottom right */
new_view_geo.x = server->grab_box.x + new_view_geo.x = server->grab_box.x +
server->grab_box.width - new_view_geo.width; server->grab_box.width - new_view_geo.width;
} }

View file

@ -1,19 +1,16 @@
// SPDX-License-Identifier: GPL-2.0-only // SPDX-License-Identifier: GPL-2.0-only
#include <assert.h>
#include <limits.h>
#include "common/border.h"
#include "common/macros.h"
#include "config/rcxml.h" #include "config/rcxml.h"
#include "labwc.h" #include "labwc.h"
#include "resistance.h" #include "resistance.h"
#include "view.h" #include "view.h"
struct edges {
int left;
int top;
int right;
int bottom;
};
static void static void
is_within_resistance_range(struct edges view, struct edges target, is_within_resistance_range(struct border view, struct border target,
struct edges other, struct edges *flags, int strength) struct border other, struct border *flags, int strength)
{ {
if (view.left >= other.left && target.left < other.left if (view.left >= other.left && target.left < other.left
&& target.left >= other.left - strength) { && target.left >= other.left - strength) {
@ -32,160 +29,210 @@ is_within_resistance_range(struct edges view, struct edges target,
} }
} }
void static void
resistance_move_apply(struct view *view, double *x, double *y) build_view_edges(struct view *view, struct wlr_box new_geom,
struct border *view_edges, struct border *target_edges, bool move)
{ {
struct server *server = view->server;
struct wlr_box mgeom, intersection;
struct wlr_box vgeom = view->current;
struct wlr_box tgeom = {.x = *x, .y = *y, .width = vgeom.width,
.height = vgeom.height};
struct output *output;
struct border border = ssd_get_margin(view->ssd); struct border border = ssd_get_margin(view->ssd);
struct edges view_edges; /* The edges of the current view */
struct edges target_edges; /* The desired edges */
struct edges other_edges; /* The edges of the monitor/other view */
struct edges flags = { 0 };
/* Use the effective height to properly snap shaded views */ /* Use the effective height to properly snap shaded views */
int eff_height = view_effective_height(view, /* use_pending */ false); int eff_height = view_effective_height(view, /* use_pending */ false);
view_edges.left = vgeom.x - border.left + 1; view_edges->left = view->current.x - border.left + (move ? 1 : 0);
view_edges.top = vgeom.y - border.top + 1; view_edges->top = view->current.y - border.top + (move ? 1 : 0);
view_edges.right = vgeom.x + vgeom.width + border.right; view_edges->right = view->current.x + view->current.width + border.right;
view_edges.bottom = vgeom.y + eff_height + border.bottom; view_edges->bottom = view->current.y + eff_height + border.bottom;
target_edges.left = *x - border.left; target_edges->left = new_geom.x - border.left;
target_edges.top = *y - border.top; target_edges->top = new_geom.y - border.top;
target_edges.right = *x + vgeom.width + border.right; target_edges->right = new_geom.x + new_geom.width + border.right;
target_edges.bottom = *y + eff_height + border.bottom; target_edges->bottom = new_geom.y + new_geom.height + border.bottom;
}
if (!rc.screen_edge_strength) { static void
update_nearest_edge(struct border view_edges, struct border target_edges,
struct border region_edges, int strength,
struct border *next_edges)
{
struct border flags = { 0 };
is_within_resistance_range(view_edges,
target_edges, region_edges, &flags, strength);
if (flags.left == 1) {
next_edges->left = MAX(region_edges.left, next_edges->left);
} else if (flags.right == 1) {
next_edges->right = MIN(region_edges.right, next_edges->right);
}
if (flags.top == 1) {
next_edges->top = MAX(region_edges.top, next_edges->top);
} else if (flags.bottom == 1) {
next_edges->bottom = MIN(region_edges.bottom, next_edges->bottom);
}
}
static void
find_neighbor_edges(struct view *view, struct wlr_box new_geom,
struct border *next_edges, bool move)
{
if (rc.window_edge_strength <= 0) {
return; return;
} }
wl_list_for_each(output, &server->outputs, link) { struct border view_edges = { 0 };
struct border target_edges = { 0 };
build_view_edges(view, new_geom, &view_edges, &target_edges, move);
struct view *v;
for_each_view(v, &view->server->views, LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) {
if (v == view || !output_is_usable(v->output)) {
continue;
}
struct border border = ssd_get_margin(v->ssd);
/*
* The significance of window edges here is inverted with
* respect to the usual orientation, because the edges of the
* view v of interest are those that would be encountered by a
* change in geometry in view along the named edge of view.
* Hence, when moving or resizing view *left*, it is the
* *right* edge of v that would be encountered, and vice versa;
* when moving or resizing view *down* ("bottom"), it is the
* *top* edge of v that would be encountered, and vice versa.
*/
struct border win_edges = {
.top = v->current.y + v->current.height + border.bottom,
.right = v->current.x - border.left,
.bottom = v->current.y - border.top,
.left = v->current.x + v->current.width + border.right,
};
update_nearest_edge(view_edges, target_edges,
win_edges, rc.window_edge_strength, next_edges);
}
}
static void
find_screen_edges(struct view *view, struct wlr_box new_geom,
struct border *next_edges, bool move)
{
if (rc.screen_edge_strength <= 0) {
return;
}
struct border view_edges = { 0 };
struct border target_edges = { 0 };
build_view_edges(view, new_geom, &view_edges, &target_edges, move);
struct output *output;
wl_list_for_each(output, &view->server->outputs, link) {
if (!output_is_usable(output)) { if (!output_is_usable(output)) {
continue; continue;
} }
mgeom = output_usable_area_in_layout_coords(output); struct wlr_box mgeom =
output_usable_area_in_layout_coords(output);
if (!wlr_box_intersection(&intersection, &vgeom, &mgeom) struct wlr_box ol;
&& !wlr_box_intersection(&intersection, &tgeom, if (!wlr_box_intersection(&ol, &view->current, &mgeom) &&
&mgeom)) { !wlr_box_intersection(&ol, &new_geom, &mgeom)) {
continue; continue;
} }
other_edges.left = mgeom.x; struct border screen_edges = {
other_edges.top = mgeom.y; .top = mgeom.y,
other_edges.right = mgeom.x + mgeom.width; .right = mgeom.x + mgeom.width,
other_edges.bottom = mgeom.y + mgeom.height; .bottom = mgeom.y + mgeom.height,
.left = mgeom.x,
};
is_within_resistance_range(view_edges, target_edges, update_nearest_edge(view_edges, target_edges,
other_edges, &flags, rc.screen_edge_strength); screen_edges, rc.screen_edge_strength, next_edges);
if (flags.left == 1) {
*x = other_edges.left + border.left;
} else if (flags.right == 1) {
*x = other_edges.right - vgeom.width - border.right;
}
if (flags.top == 1) {
*y = other_edges.top + border.top;
} else if (flags.bottom == 1) {
*y = other_edges.bottom - eff_height - border.bottom;
}
/* reset the flags */
flags.left = 0;
flags.top = 0;
flags.right = 0;
flags.bottom = 0;
} }
} }
void void
resistance_resize_apply(struct view *view, struct wlr_box *new_view_geo) resistance_move_apply(struct view *view, double *x, double *y)
{ {
struct server *server = view->server; assert(view);
struct output *output;
struct wlr_box mgeom, intersection;
struct wlr_box vgeom = view->current;
struct wlr_box tgeom = *new_view_geo;
struct border border = ssd_get_margin(view->ssd); struct border border = ssd_get_margin(view->ssd);
struct edges view_edges; /* The edges of the current view */
struct edges target_edges; /* The desired edges */
struct edges other_edges; /* The edges of the monitor/other view */
struct edges flags = { 0 };
view_edges.left = vgeom.x - border.left; struct border next_edges = {
view_edges.top = vgeom.y - border.top; .top = INT_MIN,
view_edges.right = vgeom.x + vgeom.width + border.right; .right = INT_MAX,
view_edges.bottom = vgeom.y + vgeom.height + border.bottom; .bottom = INT_MAX,
.left = INT_MIN,
};
target_edges.left = new_view_geo->x - border.left; struct wlr_box new_geom = {
target_edges.top = new_view_geo->y - border.top; .x = *x,
target_edges.right = new_view_geo->x + new_view_geo->width .y = *y,
+ border.right; .width = view->current.width,
target_edges.bottom = new_view_geo->y + new_view_geo->height .height = view->current.height,
+ border.bottom; };
if (!rc.screen_edge_strength) { find_screen_edges(view, new_geom, &next_edges, /* move */ true);
return; find_neighbor_edges(view, new_geom, &next_edges, /* move */ true);
if (next_edges.left > INT_MIN) {
*x = next_edges.left + border.left;
} else if (next_edges.right < INT_MAX) {
*x = next_edges.right - view->current.width - border.right;
} }
wl_list_for_each(output, &server->outputs, link) {
if (!output_is_usable(output)) {
continue;
}
mgeom = output_usable_area_in_layout_coords(output); if (next_edges.top > INT_MIN) {
*y = next_edges.top + border.top;
if (!wlr_box_intersection(&intersection, &vgeom, &mgeom) } else if (next_edges.bottom < INT_MAX) {
&& !wlr_box_intersection(&intersection, &tgeom, *y = next_edges.bottom - border.bottom
&mgeom)) { - view_effective_height(view, /* use_pending */ false);
continue; }
} }
other_edges.left = mgeom.x; void
other_edges.top = mgeom.y; resistance_resize_apply(struct view *view, struct wlr_box *new_geom)
other_edges.right = mgeom.x + mgeom.width; {
other_edges.bottom = mgeom.y + mgeom.height; assert(view);
assert(!view->shaded);
is_within_resistance_range(view_edges, target_edges,
other_edges, &flags, rc.screen_edge_strength); struct border border = ssd_get_margin(view->ssd);
if (server->resize_edges & WLR_EDGE_LEFT) { struct border next_edges = {
if (flags.left == 1) { .top = INT_MIN,
new_view_geo->x = other_edges.left .right = INT_MAX,
+ border.left; .bottom = INT_MAX,
new_view_geo->width = vgeom.width; .left = INT_MIN,
} };
} else if (server->resize_edges & WLR_EDGE_RIGHT) {
if (flags.right == 1) { find_screen_edges(view, *new_geom, &next_edges, /* move */ false);
new_view_geo->width = other_edges.right find_neighbor_edges(view, *new_geom, &next_edges, /* move */ false);
- view_edges.left - border.right
- border.left; if (view->server->resize_edges & WLR_EDGE_LEFT) {
} if (next_edges.left > INT_MIN) {
} new_geom->x = next_edges.left + border.left;
new_geom->width = view->current.width
if (server->resize_edges & WLR_EDGE_TOP) { + view->current.x - new_geom->x;
if (flags.top == 1) { }
new_view_geo->y = other_edges.top + border.top; } else if (view->server->resize_edges & WLR_EDGE_RIGHT) {
new_view_geo->height = vgeom.height; if (next_edges.right < INT_MAX) {
} new_geom->width = next_edges.right
} else if (server->resize_edges & WLR_EDGE_BOTTOM) { - view->current.x - border.right;
if (flags.bottom == 1) { }
new_view_geo->height = }
other_edges.bottom - view_edges.top
- border.bottom - border.top; if (view->server->resize_edges & WLR_EDGE_TOP) {
} if (next_edges.top > INT_MIN) {
} new_geom->y = next_edges.top + border.top;
new_geom->height = view->current.height
/* reset the flags */ + view->current.y - new_geom->y;
flags.left = 0; }
flags.top = 0; } else if (view->server->resize_edges & WLR_EDGE_BOTTOM) {
flags.right = 0; if (next_edges.bottom < INT_MAX) {
flags.bottom = 0; new_geom->height = next_edges.bottom
- view->current.y - border.bottom;
}
} }
} }