]> git.mdlowis.com Git - proto/labwc.git/commitdiff
resistance: add window-edge resistance for interactive moves/resizes
authorAndrew J. Hesford <ajh@sideband.org>
Sun, 21 Jan 2024 02:59:46 +0000 (21:59 -0500)
committerAndrew J. Hesford <48421688+ahesford@users.noreply.github.com>
Sun, 21 Jan 2024 21:10:50 +0000 (16:10 -0500)
docs/labwc-config.5.scd
docs/rc.xml.all
include/config/rcxml.h
src/config/rcxml.c
src/input/cursor.c
src/resistance.c

index f6345d4e4a19f19a14713f9d285e74081af8f738..a685542569113b830596084a4dc999ed1e78fec7 100644 (file)
@@ -197,9 +197,14 @@ this is for compatibility with Openbox.
 ## RESISTANCE
 
 *<resistance><screenEdgeStrength>*
-       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.
+
+*<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
 
index 350a5be16967e647319b571266852e9b5bcf8aca..5591a88079f368c091ad3cc610cb95cbe76f0a6a 100644 (file)
@@ -66,6 +66,7 @@
   <!-- edge strength is in pixels -->
   <resistance>
     <screenEdgeStrength>20</screenEdgeStrength>
+    <windowEdgeStrength>20</windowEdgeStrength>
   </resistance>
 
   <!-- Show a simple resize and move indicator -->
index 48600d50ebb23e85cc1648948f0410b6c93f284b..b9294d5e77834b5992e9441cf9ffd971c212adc5 100644 (file)
@@ -110,6 +110,7 @@ struct rcxml {
 
        /* resistance */
        int screen_edge_strength;
+       int window_edge_strength;
 
        /* window snapping */
        int snap_edge_range;
index 3f0e4ce994aa81ae5bd3d0ba23e22713d63ac89b..c8c4b4464cec528443cf4475c5807f4ada1646ce 100644 (file)
@@ -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;
index 38bf250c622e4033c90b8323f64b0c3d071564e0..c1cfdd75bc34c0fc68a9384a9a3c2ff0cb6403be 100644 (file)
@@ -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;
        }
index a03080a9e323fc9d71270045e1e5ab461d0c10d0..7fe3f3b59ea1a3e3ac767c576034a8857422eb8c 100644 (file)
@@ -1,19 +1,16 @@
 // 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 "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 = 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;
+}
 
-       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;
+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);
+       }
+}
 
-       if (!rc.screen_edge_strength) {
+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) {
-               if (!output_is_usable(output)) {
-                       continue;
-               }
+       struct border view_edges = { 0 };
+       struct border target_edges = { 0 };
 
-               mgeom = output_usable_area_in_layout_coords(output);
+       build_view_edges(view, new_geom, &view_edges, &target_edges, move);
 
-               if (!wlr_box_intersection(&intersection, &vgeom, &mgeom)
-                               && !wlr_box_intersection(&intersection, &tgeom,
-                               &mgeom)) {
+       struct view *v;
+       for_each_view(v, &view->server->views, LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) {
+               if (v == view || !output_is_usable(v->output)) {
                        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 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 };
 
-               is_within_resistance_range(view_edges, target_edges,
-                       other_edges, &flags, rc.screen_edge_strength);
+       build_view_edges(view, new_geom, &view_edges, &target_edges, move);
 
-               if (flags.left == 1) {
-                       *x = other_edges.left + border.left;
-               } else if (flags.right == 1) {
-                       *x = other_edges.right - vgeom.width - border.right;
+       struct output *output;
+       wl_list_for_each(output, &view->server->outputs, link) {
+               if (!output_is_usable(output)) {
+                       continue;
                }
 
-               if (flags.top == 1) {
-                       *y = other_edges.top + border.top;
-               } else if (flags.bottom == 1) {
-                       *y = other_edges.bottom - eff_height - border.bottom;
+               struct wlr_box mgeom =
+                       output_usable_area_in_layout_coords(output);
+
+               struct wlr_box ol;
+               if (!wlr_box_intersection(&ol, &view->current, &mgeom) &&
+                               !wlr_box_intersection(&ol, &new_geom, &mgeom)) {
+                       continue;
                }
 
-               /* reset the flags */
-               flags.left = 0;
-               flags.top = 0;
-               flags.right = 0;
-               flags.bottom = 0;
+               struct border screen_edges = {
+                       .top = mgeom.y,
+                       .right = mgeom.x + mgeom.width,
+                       .bottom = mgeom.y + mgeom.height,
+                       .left = mgeom.x,
+               };
+
+               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;
-
-       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;
-
-       if (!rc.screen_edge_strength) {
-               return;
+
+       struct border next_edges = {
+               .top = INT_MIN,
+               .right = INT_MAX,
+               .bottom = INT_MAX,
+               .left = INT_MIN,
+       };
+
+       struct wlr_box new_geom = {
+               .x = *x,
+               .y = *y,
+               .width = view->current.width,
+               .height = view->current.height,
+       };
+
+       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 (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);
+       }
+}
 
-               if (!wlr_box_intersection(&intersection, &vgeom, &mgeom)
-                               && !wlr_box_intersection(&intersection, &tgeom,
-                               &mgeom)) {
-                       continue;
-               }
+void
+resistance_resize_apply(struct view *view, struct wlr_box *new_geom)
+{
+       assert(view);
+       assert(!view->shaded);
 
-               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;
-                       }
-               }
+       struct border border = ssd_get_margin(view->ssd);
 
-               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;
-                       }
+       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;
                }
+       }
 
-               /* reset the flags */
-               flags.left = 0;
-               flags.top = 0;
-               flags.right = 0;
-               flags.bottom = 0;
+       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;
+               }
        }
 }