From 57ea197e6cac05b2950c362511ebb4f60be401c5 Mon Sep 17 00:00:00 2001 From: "Andrew J. Hesford" Date: Sat, 20 Jan 2024 21:59:46 -0500 Subject: [PATCH] resistance: add window-edge resistance for interactive moves/resizes --- docs/labwc-config.5.scd | 11 +- docs/rc.xml.all | 1 + include/config/rcxml.h | 1 + src/config/rcxml.c | 3 + src/input/cursor.c | 8 +- src/resistance.c | 313 +++++++++++++++++++++++----------------- 6 files changed, 199 insertions(+), 138 deletions(-) diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index f6345d4e..a6855425 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -197,9 +197,14 @@ this is for compatibility with Openbox. ## RESISTANCE ** - Screen Edge Strength is how far past the screen's edge your cursor must - move before the window will move with it. Resistance is counted in - pixels. Default is 20 pixels. + Screen-edge strength is the distance, in pixels, past the edge of a + screen the cursor must move before an interactive move or resize of a + window will continue past the edge. Default is 20 pixels. + +** + 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 diff --git a/docs/rc.xml.all b/docs/rc.xml.all index 350a5be1..5591a880 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -66,6 +66,7 @@ 20 + 20 diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 48600d50..b9294d5e 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -110,6 +110,7 @@ struct rcxml { /* resistance */ int screen_edge_strength; + int window_edge_strength; /* window snapping */ int snap_edge_range; diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 3f0e4ce9..c8c4b446 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -807,6 +807,8 @@ entry(xmlNode *node, char *nodename, char *content) rc.kb_layout_per_window = !strcasecmp(content, "window"); } else if (!strcasecmp(nodename, "screenEdgeStrength.resistance")) { rc.screen_edge_strength = atoi(content); + } else if (!strcasecmp(nodename, "windowEdgeStrength.resistance")) { + rc.window_edge_strength = atoi(content); } else if (!strcasecmp(nodename, "range.snapping")) { rc.snap_edge_range = atoi(content); } else if (!strcasecmp(nodename, "topMaximize.snapping")) { @@ -1061,6 +1063,7 @@ rcxml_init(void) rc.kb_numlock_enable = true; rc.kb_layout_per_window = false; rc.screen_edge_strength = 20; + rc.window_edge_strength = 20; rc.snap_edge_range = 1; rc.snap_top_maximize = true; diff --git a/src/input/cursor.c b/src/input/cursor.c index 38bf250c..c1cfdd75 100644 --- a/src/input/cursor.c +++ b/src/input/cursor.c @@ -245,12 +245,16 @@ process_cursor_resize(struct server *server, uint32_t time) struct wlr_box new_view_geo = view->current; 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; } else if (server->resize_edges & WLR_EDGE_BOTTOM) { new_view_geo.height = server->grab_box.height + dy; } 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; } else if (server->resize_edges & WLR_EDGE_RIGHT) { 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); 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 + server->grab_box.height - new_view_geo.height; } 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 + server->grab_box.width - new_view_geo.width; } diff --git a/src/resistance.c b/src/resistance.c index a03080a9..7fe3f3b5 100644 --- a/src/resistance.c +++ b/src/resistance.c @@ -1,19 +1,16 @@ // SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include "common/border.h" +#include "common/macros.h" #include "config/rcxml.h" #include "labwc.h" #include "resistance.h" #include "view.h" -struct edges { - int left; - int top; - int right; - int bottom; -}; - static void -is_within_resistance_range(struct edges view, struct edges target, - struct edges other, struct edges *flags, int strength) +is_within_resistance_range(struct border view, struct border target, + struct border other, struct border *flags, int strength) { if (view.left >= other.left && target.left < other.left && target.left >= other.left - strength) { @@ -32,160 +29,210 @@ is_within_resistance_range(struct edges view, struct edges target, } } -void -resistance_move_apply(struct view *view, double *x, double *y) +static void +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 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 */ int eff_height = view_effective_height(view, /* use_pending */ false); - view_edges.left = vgeom.x - border.left + 1; - view_edges.top = vgeom.y - border.top + 1; - view_edges.right = vgeom.x + vgeom.width + border.right; - view_edges.bottom = vgeom.y + eff_height + border.bottom; + view_edges->left = view->current.x - border.left + (move ? 1 : 0); + view_edges->top = view->current.y - border.top + (move ? 1 : 0); + view_edges->right = view->current.x + view->current.width + border.right; + view_edges->bottom = view->current.y + eff_height + border.bottom; - target_edges.left = *x - border.left; - target_edges.top = *y - border.top; - target_edges.right = *x + vgeom.width + border.right; - target_edges.bottom = *y + eff_height + border.bottom; + target_edges->left = new_geom.x - border.left; + target_edges->top = new_geom.y - border.top; + target_edges->right = new_geom.x + new_geom.width + border.right; + 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; } - 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)) { 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) - && !wlr_box_intersection(&intersection, &tgeom, - &mgeom)) { + struct wlr_box ol; + if (!wlr_box_intersection(&ol, &view->current, &mgeom) && + !wlr_box_intersection(&ol, &new_geom, &mgeom)) { continue; } - other_edges.left = mgeom.x; - other_edges.top = mgeom.y; - other_edges.right = mgeom.x + mgeom.width; - other_edges.bottom = mgeom.y + mgeom.height; + struct border screen_edges = { + .top = mgeom.y, + .right = mgeom.x + mgeom.width, + .bottom = mgeom.y + mgeom.height, + .left = mgeom.x, + }; - is_within_resistance_range(view_edges, target_edges, - other_edges, &flags, rc.screen_edge_strength); - - 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; + update_nearest_edge(view_edges, target_edges, + screen_edges, rc.screen_edge_strength, next_edges); } } 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; - struct output *output; - struct wlr_box mgeom, intersection; - struct wlr_box vgeom = view->current; - struct wlr_box tgeom = *new_view_geo; + assert(view); + 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; - view_edges.top = vgeom.y - border.top; - view_edges.right = vgeom.x + vgeom.width + border.right; - view_edges.bottom = vgeom.y + vgeom.height + border.bottom; + struct border next_edges = { + .top = INT_MIN, + .right = INT_MAX, + .bottom = INT_MAX, + .left = INT_MIN, + }; - target_edges.left = new_view_geo->x - border.left; - target_edges.top = new_view_geo->y - border.top; - target_edges.right = new_view_geo->x + new_view_geo->width - + border.right; - target_edges.bottom = new_view_geo->y + new_view_geo->height - + border.bottom; + struct wlr_box new_geom = { + .x = *x, + .y = *y, + .width = view->current.width, + .height = view->current.height, + }; - if (!rc.screen_edge_strength) { - return; + find_screen_edges(view, new_geom, &next_edges, /* move */ true); + 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 (!wlr_box_intersection(&intersection, &vgeom, &mgeom) - && !wlr_box_intersection(&intersection, &tgeom, - &mgeom)) { - continue; - } - - other_edges.left = mgeom.x; - other_edges.top = mgeom.y; - other_edges.right = mgeom.x + mgeom.width; - other_edges.bottom = mgeom.y + mgeom.height; - - is_within_resistance_range(view_edges, target_edges, - other_edges, &flags, rc.screen_edge_strength); - - if (server->resize_edges & WLR_EDGE_LEFT) { - if (flags.left == 1) { - new_view_geo->x = other_edges.left - + border.left; - new_view_geo->width = vgeom.width; - } - } else if (server->resize_edges & WLR_EDGE_RIGHT) { - if (flags.right == 1) { - new_view_geo->width = other_edges.right - - view_edges.left - border.right - - border.left; - } - } - - if (server->resize_edges & WLR_EDGE_TOP) { - if (flags.top == 1) { - new_view_geo->y = other_edges.top + border.top; - new_view_geo->height = vgeom.height; - } - } else if (server->resize_edges & WLR_EDGE_BOTTOM) { - if (flags.bottom == 1) { - new_view_geo->height = - other_edges.bottom - view_edges.top - - border.bottom - border.top; - } - } - - /* reset the flags */ - flags.left = 0; - flags.top = 0; - flags.right = 0; - flags.bottom = 0; + if (next_edges.top > INT_MIN) { + *y = next_edges.top + border.top; + } else if (next_edges.bottom < INT_MAX) { + *y = next_edges.bottom - border.bottom + - view_effective_height(view, /* use_pending */ false); + } +} + +void +resistance_resize_apply(struct view *view, struct wlr_box *new_geom) +{ + assert(view); + assert(!view->shaded); + + struct border border = ssd_get_margin(view->ssd); + + struct border next_edges = { + .top = INT_MIN, + .right = INT_MAX, + .bottom = INT_MAX, + .left = INT_MIN, + }; + + find_screen_edges(view, *new_geom, &next_edges, /* move */ false); + find_neighbor_edges(view, *new_geom, &next_edges, /* move */ false); + + 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 + + view->current.x - new_geom->x; + } + } else if (view->server->resize_edges & WLR_EDGE_RIGHT) { + if (next_edges.right < INT_MAX) { + new_geom->width = next_edges.right + - view->current.x - border.right; + } + } + + 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 + + view->current.y - new_geom->y; + } + } else if (view->server->resize_edges & WLR_EDGE_BOTTOM) { + if (next_edges.bottom < INT_MAX) { + new_geom->height = next_edges.bottom + - view->current.y - border.bottom; + } } }