]> git.mdlowis.com Git - proto/labwc.git/commitdiff
edges, resistance, snap: unified resistance and snapping engine
authorAndrew J. Hesford <ajh@sideband.org>
Tue, 23 Jan 2024 18:44:40 +0000 (13:44 -0500)
committerAndrew J. Hesford <ajh@sideband.org>
Tue, 30 Jan 2024 20:02:17 +0000 (15:02 -0500)
include/common/macros.h
include/edges.h [new file with mode: 0644]
include/snap.h
src/edges.c [new file with mode: 0644]
src/meson.build
src/resistance.c
src/snap.c
src/view.c

index 17ada31ee92b7bdcca3e1d12ab0ffed32a4503a0..38ab45de00d3f1fbb525bb8b053a2e63ba39eb44 100644 (file)
@@ -2,6 +2,8 @@
 #ifndef LABWC_MACROS_H
 #define LABWC_MACROS_H
 
+#include <limits.h>
+
 /**
  * ARRAY_SIZE() - Get the number of elements in array.
  * @arr: array to be sized
 #define MAX(a, b) (((a) > (b)) ? (a) : (b))
 #endif
 
+/**
+ * BOUNDED_INT() - Returns true if an integer is not INT_MAX or INT_MIN
+ *
+ * @param val Value to test (integer)
+ */
+#ifndef BOUNDED_INT
+#define BOUNDED_INT(a) ((a) < INT_MAX && (a) > INT_MIN)
+#endif
+
 #endif /* LABWC_MACROS_H */
diff --git a/include/edges.h b/include/edges.h
new file mode 100644 (file)
index 0000000..fd401c4
--- /dev/null
@@ -0,0 +1,110 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef LABWC_EDGES_H
+#define LABWC_EDGES_H
+
+#include <limits.h>
+#include "common/macros.h"
+
+struct border;
+struct output;
+struct view;
+
+static inline int
+clipped_add(int a, int b)
+{
+       if (b > 0) {
+               return a >= (INT_MAX - b) ? INT_MAX : (a + b);
+       } else if (b < 0) {
+               return a <= (INT_MIN - b) ? INT_MIN : (a + b);
+       }
+
+       return a;
+}
+
+static inline int
+clipped_sub(int a, int b)
+{
+       if (b > 0) {
+               return a <= (INT_MIN + b) ? INT_MIN : (a - b);
+       } else if (b < 0) {
+               return a >= (INT_MAX + b) ? INT_MAX : (a - b);
+       }
+
+       return a;
+}
+
+static inline int
+edge_get_best(int next, int edge, bool decreasing)
+{
+       if (!BOUNDED_INT(next)) {
+               /* Any bounded edge beats an unbounded next */
+               return BOUNDED_INT(edge) ? edge : next;
+       }
+
+       /* No unbounded edge ever beats next */
+       if (!BOUNDED_INT(edge)) {
+               return next;
+       }
+
+       /* Max edge wins for decreasing moves, min edge for increasing */
+       return decreasing ? MAX(next, edge) : MIN(next, edge);
+}
+
+/*
+ * edge_validator_t - edge validator signature
+ * @best: pointer to the current "best" edge
+ * @current: current position of a moving edge
+ * @target: position to which the moving edge will be moved
+ * @oppose: opposing edge of encountered region
+ * @align: aligned edge of encountered region
+ * @lesser: true if moving edge is top or left edge; false otherwise
+ *
+ * This function will be used by edge_find_neighbors and edge_find_outputs to
+ * validate and select the "best" output or neighbor edge against which a
+ * moving edge should be snapped. The moving edge has current position
+ * "current" and desired position "target". The validator should determine
+ * whether motion of the crosses the given opposed and aligned edges of a trial
+ * region and should be considered a snap point. An edge is "lesser" if it
+ * occupies a smaller coordinate than the opposite edge of the view region
+ * (i.e., it is a top or left edge).
+ *
+ * Opposing edges are on the opposite side of the target region from the moving
+ * edge (i.e., left <-> right, top <-> bottom). When the moving edge snaps to
+ * an opposing edge, the view should maintain the configured gap. Aligned edges
+ * are on the same side of the target region from the moving edge (i.e.,
+ * left <-> left, right <-> right, top <-> top, bottom <-> bottom). When the
+ * moving edge snaps to an aligned edge, the view should *not* include a gap.
+ *
+ * If window gaps are configured, all edges will be offset as appropriate to
+ * reflect the desired padding. Thus, the validator should generally compare
+ * the given current or target values directly to the opposing and aligned edge
+ * without regard for rc.gap.
+ *
+ * Any edge may take the values INT_MIN or INT_MAX to indicate that the edge
+ * should be effectively ignored. Should the validator decide that a given
+ * region edge (oppose or align) should be a preferred snap point, it should
+ * update the value of *best accordingly.
+ */
+typedef void (*edge_validator_t)(int *best,
+       int current, int target, int oppose, int align, bool lesser);
+
+void edges_initialize(struct border *edges);
+
+void edges_adjust_geom(struct view *view, struct border edges,
+       uint32_t resize_edges, struct wlr_box *geom);
+
+void edges_find_neighbors(struct border *nearest_edges, struct view *view,
+       struct wlr_box target, struct output *output,
+       edge_validator_t validator, bool use_pending);
+
+void edges_find_outputs(struct border *nearest_edges, struct view *view,
+       struct wlr_box target, struct output *output,
+       edge_validator_t validator, bool use_pending);
+
+void edges_adjust_move_coords(struct view *view, struct border edges,
+       int *x, int *y, bool use_pending);
+
+void edges_adjust_resize_geom(struct view *view, struct border edges,
+       uint32_t resize_edges, struct wlr_box *geom, bool use_pending);
+
+#endif /* LABWC_EDGES_H */
index 20d9b9d6ccce12d65c696ab6b38d8aa034811159..342e56f4efcb970eb2e064fdb244c5f63a877327 100644 (file)
@@ -7,11 +7,13 @@
 
 struct wlr_box;
 
-struct border snap_get_max_distance(struct view *view);
+void snap_move_to_edge(struct view *view,
+       enum view_edge direction, bool snap_to_windows, int *dx, int *dy);
 
-void snap_vector_to_next_edge(struct view *view, enum view_edge direction, int *dx, int *dy);
-int snap_distance_to_next_edge(struct view *view, enum view_edge direction);
-void snap_grow_to_next_edge(struct view *view, enum view_edge direction, struct wlr_box *geo);
-void snap_shrink_to_next_edge(struct view *view, enum view_edge direction, struct wlr_box *geo);
+void snap_grow_to_next_edge(struct view *view,
+       enum view_edge direction, struct wlr_box *geo);
+
+void snap_shrink_to_next_edge(struct view *view,
+       enum view_edge direction, struct wlr_box *geo);
 
 #endif /* LABWC_SNAP_H */
diff --git a/src/edges.c b/src/edges.c
new file mode 100644 (file)
index 0000000..965bf61
--- /dev/null
@@ -0,0 +1,303 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <assert.h>
+#include <limits.h>
+#include <wlr/util/box.h>
+#include "common/border.h"
+#include "common/macros.h"
+#include "config/rcxml.h"
+#include "edges.h"
+#include "labwc.h"
+#include "view.h"
+
+static void
+edges_for_target_geometry(struct border *edges, struct view *view,
+               struct wlr_box target)
+{
+       struct border border = ssd_get_margin(view->ssd);
+
+       /* Use the effective height to properly handle shaded views */
+       int eff_height = view->shaded ? 0 : target.height;
+
+       edges->left = target.x - border.left - rc.gap;
+       edges->top = target.y - border.top - rc.gap;
+       edges->right = target.x + target.width + border.right + rc.gap;
+       edges->bottom = target.y + eff_height + border.bottom + rc.gap;
+}
+
+void
+edges_initialize(struct border *edges)
+{
+       assert(edges);
+       edges->top = INT_MIN;
+       edges->right = INT_MAX;
+       edges->bottom = INT_MAX;
+       edges->left = INT_MIN;
+}
+
+static void
+validate_edges(struct border *valid_edges,
+               struct border view, struct border target,
+               struct border region, edge_validator_t validator)
+{
+       /* When a view snaps to a region while moving to its target, it can do
+        * so in two ways: a view edge can snap to an "opposing" edge of the
+        * region (left <-> right, top <-> bottom) or to an "aligned" edge
+        * (left <-> left, right <-> right, top <-> top, bottom <-> bottom).
+        *
+        * When a view hits the opposing edge of a region, it should be
+        * separated by a gap; when a view hits the aligned edge, it should not
+        * be separated. The view and its target already include necessary
+        * padding to reflect the gap. The region does not. To make sure the
+        * "aligned" edges are properly aligned, add padding to the region
+        * borders for aligned edges only.
+        */
+
+       struct border region_pad = {
+               .top = clipped_sub(region.top, rc.gap),
+               .right = clipped_add(region.right, rc.gap),
+               .bottom = clipped_add(region.bottom, rc.gap),
+               .left = clipped_sub(region.left, rc.gap),
+       };
+
+       /* Check for edges encountered during movement of left edge */
+       validator(&valid_edges->left, view.left, target.left,
+               region.right, region_pad.left, /* lesser */ true);
+
+       /* Check for edges encountered during movement of right edge */
+       validator(&valid_edges->right, view.right, target.right,
+               region.left, region_pad.right, /* lesser */ false);
+
+       /* Check for edges encountered during movement of top edge */
+       validator(&valid_edges->top, view.top, target.top,
+               region.bottom, region_pad.top, /* lesser */ true);
+
+       /* Check for edges encountered during movement of bottom edge */
+       validator(&valid_edges->bottom, view.bottom, target.bottom,
+               region.top, region_pad.bottom, /* lesser */ false);
+}
+
+void
+edges_find_neighbors(struct border *nearest_edges, struct view *view,
+               struct wlr_box target, struct output *output,
+               edge_validator_t validator, bool use_pending)
+{
+       assert(view);
+       assert(validator);
+       assert(nearest_edges);
+
+       struct border view_edges = { 0 };
+       struct border target_edges = { 0 };
+
+       edges_for_target_geometry(&view_edges, view,
+               use_pending ? view->pending : view->current);
+       edges_for_target_geometry(&target_edges, view, target);
+
+       struct view *v;
+       for_each_view(v, &view->server->views, LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) {
+               if (v == view || !output_is_usable(v->output)) {
+                       continue;
+               }
+
+               if (output && v->output != output) {
+                       continue;
+               }
+
+               struct border border = ssd_get_margin(v->ssd);
+
+               struct border win_edges = {
+                       .top = v->current.y - border.top,
+                       .left = v->current.x - border.left,
+                       .bottom = v->current.y + border.bottom
+                               + view_effective_height(v, /* use_pending */ false),
+                       .right = v->current.x + v->current.width + border.right,
+               };
+
+               validate_edges(nearest_edges, view_edges,
+                       target_edges, win_edges, validator);
+       }
+}
+
+void
+edges_find_outputs(struct border *nearest_edges, struct view *view,
+               struct wlr_box target, struct output *output,
+               edge_validator_t validator, bool use_pending)
+{
+       assert(view);
+       assert(validator);
+       assert(nearest_edges);
+
+       struct border view_edges = { 0 };
+       struct border target_edges = { 0 };
+
+       struct wlr_box *view_geom =
+               use_pending ? &view->pending : &view->current;
+
+       edges_for_target_geometry(&view_edges, view, *view_geom);
+       edges_for_target_geometry(&target_edges, view, target);
+
+       struct output *o;
+       wl_list_for_each(o, &view->server->outputs, link) {
+               if (!output_is_usable(o)) {
+                       continue;
+               }
+
+               if (output && o != output) {
+                       continue;
+               }
+
+               struct wlr_box usable =
+                       output_usable_area_in_layout_coords(o);
+
+               struct wlr_box ol;
+               if (!wlr_box_intersection(&ol, view_geom, &usable) &&
+                               !wlr_box_intersection(&ol, &target, &usable)) {
+                       continue;
+               }
+
+               /*
+                * Split a single "leaving output" problem into four "entering
+                * complementary region" problems, treating the view, its
+                * target and the screen boundaries as half planes. This
+                * prevents unexpected snapping behavior like the bottom of a
+                * window snapping above the top of an output, where it would
+                * become invisible.
+                */
+               struct border screen;
+               struct border view_eff;
+               struct border target_eff;
+
+               /* First problem: view toward upper half-plane */
+               edges_initialize(&screen);
+               edges_initialize(&view_eff);
+               edges_initialize(&target_eff);
+
+               screen.bottom = usable.y;
+               view_eff.top = view_edges.top;
+               target_eff.top = target_edges.top;
+               validate_edges(nearest_edges, view_eff, target_eff, screen, validator);
+
+               /* Second problem: view toward lower half-plane */
+               edges_initialize(&screen);
+               edges_initialize(&view_eff);
+               edges_initialize(&target_eff);
+
+               screen.top = usable.y + usable.height;
+               view_eff.bottom = view_edges.bottom;
+               target_eff.bottom = target_edges.bottom;
+               validate_edges(nearest_edges, view_eff, target_eff, screen, validator);
+
+               /* Third problem: view toward left half-plane */
+               edges_initialize(&screen);
+               edges_initialize(&view_eff);
+               edges_initialize(&target_eff);
+
+               screen.right = usable.x;
+               view_eff.left = view_edges.left;
+               target_eff.left = target_edges.left;
+               validate_edges(nearest_edges, view_eff, target_eff, screen, validator);
+
+               /* Fourth problem: view toward right half-plane */
+               edges_initialize(&screen);
+               edges_initialize(&view_eff);
+               edges_initialize(&target_eff);
+
+               screen.left = usable.x + usable.width;
+               view_eff.right = view_edges.right;
+               target_eff.right = target_edges.right;
+               validate_edges(nearest_edges, view_eff, target_eff, screen, validator);
+       }
+}
+
+static void
+adjust_move_coords_1d(int *edge, int lesser, int lesser_offset,
+               int greater, int greater_offset, bool decreasing)
+{
+       /* Default best candidate is not valid */
+       int best = INT_MAX;
+
+       if (BOUNDED_INT(lesser)) {
+               /* A valid lesser edge is the always the first candidate */
+               best = clipped_add(lesser, lesser_offset);
+       }
+
+       if (BOUNDED_INT(greater)) {
+               /* Check if a valid greater edge is a better candidate */
+               best = edge_get_best(best,
+                       clipped_sub(greater, greater_offset), decreasing);
+       }
+
+       if (BOUNDED_INT(best)) {
+               /* Replace the edge if a valid candidate was found */
+               *edge = best;
+       }
+}
+
+void
+edges_adjust_move_coords(struct view *view, struct border edges,
+               int *x, int *y, bool use_pending)
+{
+       assert(view);
+
+       struct border border = ssd_get_margin(view->ssd);
+       struct wlr_box *view_geom =
+               use_pending ? &view->pending : &view->current;
+
+       /* When moving, limit motion to the best valid, intervening edge */
+
+       if (view_geom->x != *x) {
+               int lshift = border.left + rc.gap;
+               int rshift = border.right + rc.gap + view->pending.width;
+
+               adjust_move_coords_1d(x, edges.left, lshift,
+                       edges.right, rshift, *x < view_geom->x);
+       }
+
+       if (view_geom->y != *y) {
+               int tshift = border.top + rc.gap;
+               int bshift = border.bottom + rc.gap
+                       + view_effective_height(view, /* use_pending */ true);
+
+               adjust_move_coords_1d(y, edges.top, tshift,
+                       edges.bottom, bshift, *y < view_geom->y);
+       }
+}
+
+void
+edges_adjust_resize_geom(struct view *view, struct border edges,
+               uint32_t resize_edges, struct wlr_box *geom, bool use_pending)
+{
+       assert(view);
+
+       struct border border = ssd_get_margin(view->ssd);
+       struct wlr_box *view_geom =
+               use_pending ? &view->pending : &view->current;
+
+       /*
+        * When resizing along a given edge, limit the motion of that edge to
+        * any valid nearest edge in the corresponding direction.
+        */
+
+       if (resize_edges & WLR_EDGE_LEFT) {
+               if (BOUNDED_INT(edges.left)) {
+                       geom->x = edges.left + border.left + rc.gap;
+                       geom->width = view_geom->width + view_geom->x - geom->x;
+               }
+       } else if (resize_edges & WLR_EDGE_RIGHT) {
+               if (BOUNDED_INT(edges.right)) {
+                       geom->width = edges.right
+                               - view_geom->x - border.right - rc.gap;
+               }
+       }
+
+       if (resize_edges & WLR_EDGE_TOP) {
+               if (BOUNDED_INT(edges.top)) {
+                       geom->y = edges.top + border.top + rc.gap;
+                       geom->height = view_geom->height + view_geom->y - geom->y;
+               }
+       } else if (resize_edges & WLR_EDGE_BOTTOM) {
+               if (BOUNDED_INT(edges.bottom)) {
+                       geom->height = edges.bottom
+                               - view_geom->y - border.bottom - rc.gap;
+               }
+       }
+}
index baf46cb8c33973e0594cdc4e034d11d45bc337d1..b544e99f74dc3f7b93fc39cecc1a92f7d4a2f142 100644 (file)
@@ -4,6 +4,7 @@ labwc_sources = files(
   'debug.c',
   'desktop.c',
   'dnd.c',
+  'edges.c',
   'foreign.c',
   'idle.c',
   'interactive.c',
index d187c44aea1a959c57e52f2fd89be62e1c607fc3..241441fb6c99b2419106e66f5012174ea1738f03 100644 (file)
 #include "common/border.h"
 #include "common/macros.h"
 #include "config/rcxml.h"
+#include "edges.h"
 #include "labwc.h"
 #include "resistance.h"
 #include "view.h"
 
 static void
-is_within_resistance_range(struct border view, struct border target,
-               struct border other, struct border *flags, int strength)
+check_edge(int *next, int current, int target,
+               int oppose, int align, bool lesser, int tolerance)
 {
-       if (view.left >= other.left) {
-               const int lo = other.left - abs(strength);
-               const int hi = other.left - MIN(strength, 0);
-               flags->left = target.left >= lo && target.left < hi;
-       }
-
-       if (!flags->left && view.right <= other.right) {
-               const int lo = other.right + MIN(strength, 0);
-               const int hi = other.right + abs(strength);
-               flags->right = target.right > lo && target.right <= hi;
+       /* Ignore non-moving edges */
+       if (current == target) {
+               return;
        }
 
-       if (view.top >= other.top) {
-               const int lo = other.top - abs(strength);
-               const int hi = other.top - MIN(strength, 0);
-               flags->top = target.top >= lo && target.top < hi;
+       /*
+        * The edge defined by current and moving to target may encounter two
+        * edges of another region: the opposing edge of the region is that in
+        * the opposite orientation of the moving edge (i.e., left <-> right or
+        * top <-> bottom); the aligned edge of the region is that in the same
+        * orientation as the moving edge (i.e., left <->left, top <-> top,
+        * right <-> right, bottom <-> bottom).
+        *
+        * Any opposing or aligned edge of a region is considered "valid" in
+        * this search if the resist/attract zone (defined by tolerance) of
+        * that edge contains the target position of the moving edge.
+        */
+
+       /* Direction of motion for the edge */
+       const bool decreasing = target < current;
+
+       /* Check the opposing edge */
+       bool valid = false;
+       if (decreasing) {
+               const int lo = clipped_sub(oppose, abs(tolerance));
+               const int hi = clipped_sub(oppose, MIN(tolerance, 0));
+               valid = target >= lo && target < hi;
+       } else {
+               /* Check for increasing movement across opposing edge */
+               const int lo = clipped_add(oppose, MIN(tolerance, 0));
+               const int hi = clipped_add(oppose, abs(tolerance));
+               valid = target > lo && target <= hi;
        }
 
-       if (!flags->top && view.bottom <= other.bottom) {
-               const int lo = other.bottom + MIN(strength, 0);
-               const int hi = other.bottom + abs(strength);
-               flags->bottom = target.bottom > lo && target.bottom <= hi;
+       if (valid) {
+               *next = edge_get_best(*next, oppose, decreasing);
        }
-}
-
-static void
-build_view_edges(struct view *view, struct wlr_box new_geom,
-               struct border *view_edges, struct border *target_edges, bool move)
-{
-       struct border border = ssd_get_margin(view->ssd);
-
-       /* Use the effective height to properly snap shaded views */
-       int eff_height = view_effective_height(view, /* use_pending */ false);
-
-       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 + border.bottom +
-               (view->shaded ? 0 : new_geom.height);
-}
-
-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);
+       /* Check the aligned edge */
+       valid = false;
+       if (decreasing) {
+               const int lo = clipped_sub(align, abs(tolerance));
+               const int hi = clipped_sub(align, MIN(tolerance, 0));
+               valid = target >= lo && target < hi;
+       } else {
+               const int lo = clipped_add(align, MIN(tolerance, 0));
+               const int hi = clipped_add(align, abs(tolerance));
+               valid = target > lo && target <= hi;
        }
 
-       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 (valid) {
+               *next = edge_get_best(*next, align, decreasing);
        }
 }
 
 static void
-find_neighbor_edges(struct view *view, struct wlr_box new_geom,
-               struct border *next_edges, bool move)
+check_edge_output(int *next, int current, int target,
+               int oppose, int align, bool lesser)
 {
-       if (rc.window_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 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 + border.bottom
-                               + view_effective_height(v, /* use_pending */ false),
-                       .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);
-       }
+       check_edge(next, current, target,
+               oppose, align, lesser, rc.screen_edge_strength);
 }
 
 static void
-find_screen_edges(struct view *view, struct wlr_box new_geom,
-               struct border *next_edges, bool move)
+check_edge_window(int *next, int current, int target,
+               int oppose, int align, bool lesser)
 {
-       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;
-               }
-
-               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;
-               }
-
-               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);
-       }
+       check_edge(next, current, target,
+               oppose, align, lesser, rc.window_edge_strength);
 }
 
 void
@@ -169,37 +89,34 @@ resistance_move_apply(struct view *view, double *x, double *y)
 {
        assert(view);
 
-       struct border border = ssd_get_margin(view->ssd);
+       struct border next_edges;
+       edges_initialize(&next_edges);
 
-       struct border next_edges = {
-               .top = INT_MIN,
-               .right = INT_MAX,
-               .bottom = INT_MAX,
-               .left = INT_MIN,
-       };
-
-       struct wlr_box new_geom = {
+       struct wlr_box target = {
                .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;
+       if (rc.screen_edge_strength != 0) {
+               /* Find any relevant output edges encountered by this move */
+               edges_find_outputs(&next_edges, view, target, NULL,
+                       check_edge_output, /* use_pending */ false);
        }
 
-       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 (rc.window_edge_strength != 0) {
+               /* Find any relevant window edges encountered by this move */
+               edges_find_neighbors(&next_edges, view, target, NULL,
+                       check_edge_window, /* use_pending */ false);
        }
+
+       /* If any "best" edges were encountered during this move, snap motion */
+       edges_adjust_move_coords(view, next_edges,
+               &target.x, &target.y, /* use_pending */ false);
+
+       *x = target.x;
+       *y = target.y;
 }
 
 void
@@ -208,41 +125,22 @@ 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,
-       };
+       struct border next_edges;
+       edges_initialize(&next_edges);
 
-       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 (rc.screen_edge_strength != 0) {
+               /* Find any relevant output edges encountered by this move */
+               edges_find_outputs(&next_edges, view, *new_geom, NULL,
+                       check_edge_output, /* use_pending */ false);
        }
 
-       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;
-               }
+       if (rc.window_edge_strength != 0) {
+               /* Find any relevant window edges encountered by this move */
+               edges_find_neighbors(&next_edges, view, *new_geom, NULL,
+                       check_edge_window, /* use_pending */ false);
        }
+
+       /* If any "best" edges were encountered during this move, snap motion */
+       edges_adjust_resize_geom(view, next_edges,
+               view->server->resize_edges, new_geom, /* use_pending */ false);
 }
index fd60cad0040a54272f7725666a2c77b490d435bf..5d0e943d8387cd297e383eaab0d14307b04927e1 100644 (file)
 // SPDX-License-Identifier: GPL-2.0-only
-#include <strings.h>
+#include <assert.h>
+#include <limits.h>
 #include <wlr/util/box.h>
+#include "common/border.h"
+#include "common/macros.h"
+#include "config/rcxml.h"
+#include "edges.h"
 #include "labwc.h"
+#include "resistance.h"
 #include "snap.h"
 #include "view.h"
-#include "workspaces.h"
 
-/* We cannot use MIN/MAX macros, as they may call functions twice, and
- * can be overridden by previous #define.
- */
-static inline int
-min(int a, int b) {
-       return a < b ? a : b;
-}
+static void
+check_edge(int *next, int current, int target, int oppose, int align, bool lesser)
+{
+       if (current == target) {
+               return;
+       }
 
-static inline int
-max(int a, int b) {
-       return a > b ? a : b;
-}
+       /*
+        * The edge defined by current and moving to target may encounter two
+        * edges of another region: the opposing edge of the region is that in
+        * the opposite orientation of the moving edge (i.e., left <-> right or
+        * top <-> bottom); the aligned edge of the region is that in the same
+        * orientation as the moving edge (i.e., left <-> left, top <-> top,
+        * right <-> right, bottom <-> bottom).
+        *
+        * Any opposing or aligned edge of a region is considered "valid" in
+        * this search if the edge sits between the current and target
+        * positions of the moving edge (including the target position itself).
+        */
 
-static inline int
-min3(int a, int b, int c) {
-       return min(min(a, b), c);
-}
+       /* Direction of motion for the edge */
+       const bool decreasing = target < current;
 
-enum snap_mode {
-       SNAP_MODE_MOVE = 0,
-       SNAP_MODE_GROW,
-       SNAP_MODE_SHRINK,
-};
+       /* Check the opposing edge */
+       if ((target <= oppose && oppose < current) ||
+                       (current < oppose && oppose <= target)) {
+               *next = edge_get_best(*next, oppose, decreasing);
+       }
 
-static struct border
-snap_get_view_edge(struct view *view)
-{
-       struct border margin = ssd_get_margin(view->ssd);
-       struct border edge = {
-               .left   = view->pending.x - margin.left,
-               .top    = view->pending.y - margin.top,
-               .right  = view->pending.x + margin.right + view->pending.width,
-               .bottom = view->pending.y + margin.bottom
-                       + view_effective_height(view, /* use_pending */ true)
-       };
-       return edge;
+       /* Check the aligned edge */
+       if ((target <= align && align < current) ||
+                       (current < align && align <= target)) {
+               *next = edge_get_best(*next, align, decreasing);
+       }
 }
 
-struct border
-snap_get_max_distance(struct view *view)
+void
+snap_move_to_edge(struct view *view, enum view_edge direction,
+               bool snap_to_windows, int *dx, int *dy)
 {
-       struct output *output = view->output;
-       struct border margin = ssd_get_margin(view->ssd);
-       struct wlr_box usable = output_usable_area_scaled(output);
-       struct border distance = {
-               .left   = usable.x + margin.left + rc.gap - view->pending.x,
-               .top    = usable.y + margin.top  + rc.gap - view->pending.y,
-               .right  = usable.x + usable.width - view->pending.width
-                       - margin.right  - rc.gap - view->pending.x,
-               .bottom = usable.y + usable.height
-                       - view_effective_height(view, /* use_pending */ true)
-                       - margin.bottom - rc.gap - view->pending.y
-       };
-       return distance;
-}
-
-struct snap_search {
-       const int search_dir; /* -1: left/up, 1: right/down */
+       assert(view);
 
-       const int add_view_x;
-       const int add_view_y;
-       const int add_view_width;
-       const int add_view_height;
+       *dx = 0;
+       *dy = 0;
 
-       const int add_margin_left;
-       const int add_margin_top;
-       const int add_margin_right;
-       const int add_margin_bottom;
-};
+       struct output *output = view->output;
+       if (!output_is_usable(output)) {
+               wlr_log(WLR_ERROR, "view has no output, not snapping to edge");
+               return;
+       }
 
-/* near/far is the left, right, top or bottom border of a window,
- * depending on the search direction:
- *  - near_right: search to the right, snap to left  (near) border of a window.
- *  - far_right:  search to the right, snap to right (far)  border of a window.
- *  - near_left:  search to the left,  snap to right (near) border of a window.
- *  - far_left:   search to the left,  snap to left  (far)  border of a window.
- *
- * structs below define what coordinates and margins to take into
- * account depending near/far, and direction.
- */
-static const struct snap_search near_left  = { -1,   1, 0, 1, 0,    0,  0,  1,  0 };
-static const struct snap_search near_up    = { -1,   0, 1, 0, 1,    0,  0,  0,  1 };
-static const struct snap_search near_right = {  1,   1, 0, 0, 0,   -1,  0,  0,  0 };
-static const struct snap_search near_down  = {  1,   0, 1, 0, 0,    0, -1,  0,  0 };
-static const struct snap_search far_left   = { -1,   1, 0, 0, 0,   -1,  0,  0,  0 };
-static const struct snap_search far_up     = { -1,   0, 1, 0, 0,    0, -1,  0,  0 };
-static const struct snap_search far_right  = {  1,   1, 0, 1, 0,    0,  0,  1,  0 };
-static const struct snap_search far_down   = {  1,   0, 1, 0, 1,    0,  0,  1,  0 };
+       struct wlr_box target = view->pending;
+       struct border ssd = ssd_thickness(view);
+       struct wlr_box usable = output_usable_area_in_layout_coords(output);
 
-static inline int
-_snap_next_edge(struct view *view, int start_pos, const struct snap_search def, int max, int gap)
-{
-       struct output *output = view->output;
-       struct server *server = output->server;
-       struct view *v;
-       int p = max;
-       for_each_view(v, &server->views, LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) {
-               if (v == view || v->output != output || v->minimized
-                               || v->maximized == VIEW_AXIS_BOTH) {
-                       continue;
+       /*
+        * First try to move the view to the relevant edge of its output. If
+        * the view is off-screen, such a move might actually run contrary to
+        * the commanded direction (e.g., a view off the screen to the left,
+        * when moved to the left edge, will actually move rightward). This is
+        * counter-intuitive, so abandon any such movements.
+        *
+        * In addition, any view that is already at the desired screen edge
+        * needs no further consideration.
+        */
+       switch (direction) {
+       case VIEW_EDGE_LEFT:
+               target.x = usable.x + ssd.left + rc.gap;
+               if (target.x >= view->pending.x) {
+                       return;
                }
+               break;
+       case VIEW_EDGE_RIGHT:
+               target.x = usable.x + usable.width
+                       - rc.gap - target.width - ssd.right;
+               if (target.x <= view->pending.x) {
+                       return;
+               }
+               break;
+       case VIEW_EDGE_UP:
+               target.y = usable.y + ssd.top + rc.gap;
+               if (target.y >= view->pending.y) {
+                       return;
+               }
+               break;
+       case VIEW_EDGE_DOWN:
+               target.y = usable.y + usable.height - rc.gap - ssd.bottom
+                       - view_effective_height(view, /* use_pending */ true);
+               if (target.y <= view->pending.y) {
+                       return;
+               }
+               break;
+       default:
+               return;
+       }
 
-               struct border margin = ssd_get_margin(v->ssd);
-               int vp = -start_pos;
-               vp += def.add_margin_left   * margin.left;
-               vp += def.add_margin_top    * margin.top;
-               vp += def.add_margin_right  * margin.right;
-               vp += def.add_margin_bottom * margin.bottom;
-               vp += def.add_view_x        * v->pending.x;
-               vp += def.add_view_y        * v->pending.y;
-               vp += def.add_view_width    * v->pending.width;
-               vp += def.add_view_height
-                       * view_effective_height(v, /* use_pending */ true);
-               vp += gap;
+       /*
+        * Because the target has been updated to put the view at the edge of
+        * an output, there is no need to check snapping to output edges. If
+        * snapping to view is desired, check for snapping against any view on
+        * the same output.
+        */
+       if (snap_to_windows) {
+               struct border next_edges;
+               edges_initialize(&next_edges);
 
-               if (def.search_dir * vp > 0 && def.search_dir * (vp - p) < 0) {
-                       p = vp;
-               }
+               edges_find_neighbors(&next_edges, view, target,
+                       output, check_edge, /* use_pending */ true);
+
+               /* If any "best" edges were encountered, limit motion */
+               edges_adjust_move_coords(view, next_edges,
+                       &target.x, &target.y, /* use_pending */ true);
        }
-       return p;
+
+       *dx = target.x - view->pending.x;
+       *dy = target.y - view->pending.y;
 }
 
-static void
-_snap_move_resize_to_edge(struct view *view, enum view_edge direction, enum snap_mode mode,
-               struct wlr_box *delta)
+void
+snap_grow_to_next_edge(struct view *view, enum view_edge direction,
+               struct wlr_box *geo)
 {
-       struct border edge = snap_get_view_edge(view);
-       struct border dmax;
+       assert(view);
+       assert(!view->shaded);
+
+       *geo = view->pending;
 
-       if (mode == SNAP_MODE_SHRINK) {
-               /* limit to half of current size */
-               int eff_height =
-                       view_effective_height(view, /* use_pending */ true);
-               int width_max_dx = max(view->pending.width - LAB_MIN_VIEW_WIDTH, 0);
-               int height_max_dy = max(eff_height - LAB_MIN_VIEW_HEIGHT, 0);
-               dmax.right  = min(width_max_dx,  view->pending.width  / 2);
-               dmax.bottom = min(height_max_dy, eff_height / 2);
-               dmax.left   = -dmax.right;
-               dmax.top    = -dmax.bottom;
-       } else {
-               dmax = snap_get_max_distance(view);
+       struct output *output = view->output;
+       if (!output_is_usable(output)) {
+               wlr_log(WLR_ERROR, "view has no output, not growing to edge");
+               return;
        }
 
+       struct border ssd = ssd_thickness(view);
+       struct wlr_box usable = output_usable_area_in_layout_coords(output);
+       uint32_t resize_edges;
+
+       /* First try to grow the view to the relevant edge of its output. */
        switch (direction) {
        case VIEW_EDGE_LEFT:
-               if (mode == SNAP_MODE_MOVE) {
-                       delta->x += max(
-                               /* left edge to left/right edges */
-                               _snap_next_edge(view, edge.left, near_left, dmax.left, rc.gap),
-                               _snap_next_edge(view, edge.left, far_left,  dmax.left, 0)
-                       );
-               } else if (mode == SNAP_MODE_GROW) {
-                       int dx = max(
-                               /* left edge to left/right edges */
-                               _snap_next_edge(view, edge.left, near_left, dmax.left, rc.gap),
-                               _snap_next_edge(view, edge.left, far_left,  dmax.left, 0)
-                       );
-                       delta->x     +=  dx;
-                       delta->width += -dx;
-               } else if (mode == SNAP_MODE_SHRINK) {
-                       delta->width += max(
-                               /* right edge to left/right edges */
-                               _snap_next_edge(view, edge.right, near_left, dmax.left, 0),
-                               _snap_next_edge(view, edge.right, far_left,  dmax.left, -rc.gap)
-                       );
-               }
-               break;
-       case VIEW_EDGE_UP:
-               if (mode == SNAP_MODE_MOVE) {
-                       delta->y += max(
-                               /* top edge to top/bottom edges */
-                               _snap_next_edge(view, edge.top, near_up, dmax.top, rc.gap),
-                               _snap_next_edge(view, edge.top, far_up,  dmax.top, 0)
-                       );
-               } else if (mode == SNAP_MODE_GROW) {
-                       int dy = max(
-                               /* top edge to top/bottom edges */
-                               _snap_next_edge(view, edge.top, near_up, dmax.top, rc.gap),
-                               _snap_next_edge(view, edge.top, far_up,  dmax.top, 0)
-                       );
-                       delta->y      +=  dy;
-                       delta->height += -dy;
-               } else if (mode == SNAP_MODE_SHRINK) {
-                       delta->height += max(
-                               /* bottom edge to top/bottom edges */
-                               _snap_next_edge(view, edge.bottom, near_up, dmax.top, 0),
-                               _snap_next_edge(view, edge.bottom, far_up,  dmax.top, -rc.gap)
-                       );
-               }
+               geo->x = usable.x + ssd.left + rc.gap;
+               geo->width = view->pending.x + view->pending.width - geo->x;
+               resize_edges = WLR_EDGE_LEFT;
                break;
        case VIEW_EDGE_RIGHT:
-               if (mode == SNAP_MODE_MOVE) {
-                       delta->x += min3(
-                               /* left edge to left/right edges */
-                               _snap_next_edge(view, edge.left, near_right, dmax.right, 0),
-                               _snap_next_edge(view, edge.left, far_right,  dmax.right, rc.gap),
-                               /* right edge to left edge */
-                               _snap_next_edge(view, edge.right, near_right, dmax.right, -rc.gap)
-                       );
-               } else if (mode == SNAP_MODE_GROW) {
-                       delta->width += min(
-                               /* right edge to left/right edges */
-                               _snap_next_edge(view, edge.right, near_right, dmax.right, -rc.gap),
-                               _snap_next_edge(view, edge.right, far_right,  dmax.right, 0)
-                       );
-               } else if (mode == SNAP_MODE_SHRINK) {
-                       delta->x += min(
-                               /* left edge to left/right edges */
-                               _snap_next_edge(view, edge.left, near_right, dmax.right, 0),
-                               _snap_next_edge(view, edge.left, far_right,  dmax.right, rc.gap)
-                       );
-                       delta->width += -(delta->x);
-               }
+               geo->width = usable.x + usable.width
+                       - rc.gap - ssd.right - view->pending.x;
+               resize_edges = WLR_EDGE_RIGHT;
+               break;
+       case VIEW_EDGE_UP:
+               geo->y = usable.y + ssd.top + rc.gap;
+               geo->height = view->pending.y + view->pending.height - geo->y;
+               resize_edges = WLR_EDGE_TOP;
                break;
        case VIEW_EDGE_DOWN:
-               if (mode == SNAP_MODE_MOVE) {
-                       delta->y += min3(
-                               /* top edge to top/bottom edges */
-                               _snap_next_edge(view, edge.top, near_down, dmax.bottom, 0),
-                               _snap_next_edge(view, edge.top, far_down,  dmax.bottom, rc.gap),
-                               /* bottom edge to top edge */
-                               _snap_next_edge(view, edge.bottom, near_down, dmax.bottom, -rc.gap)
-                       );
-               } else if (mode == SNAP_MODE_GROW) {
-                       delta->height += min(
-                               /* bottom edge to top/bottom edges */
-                               _snap_next_edge(view, edge.bottom, near_down, dmax.bottom, -rc.gap),
-                               _snap_next_edge(view, edge.bottom, far_down,  dmax.bottom, 0)
-                       );
-               } else if (mode == SNAP_MODE_SHRINK) {
-                       delta->y += min(
-                               /* top edge to top/bottom edges */
-                               _snap_next_edge(view, edge.top, near_down, dmax.bottom, 0),
-                               _snap_next_edge(view, edge.top, far_down,  dmax.bottom, rc.gap)
-                       );
-                       delta->height += -(delta->y);
-               }
+               geo->height = usable.y + usable.height
+                       - rc.gap - ssd.bottom - view->pending.y;
+               resize_edges = WLR_EDGE_BOTTOM;
                break;
        default:
                return;
        }
+
+       /* No grow operation should ever shrink the view */
+       if (geo->width < view->pending.width ||
+                       geo->height < view->pending.height) {
+               *geo = view->pending;
+               return;
+       }
+
+       /* If the view doesn't change size, there is no need for snap checks */
+       if (geo->width == view->pending.width &&
+                       geo->height == view->pending.height) {
+               *geo = view->pending;
+               return;
+       }
+
+       struct border next_edges;
+       edges_initialize(&next_edges);
+
+       /* Limit motion to any intervening edge of other views on this output */
+       edges_find_neighbors(&next_edges, view, *geo,
+               output, check_edge, /* use_pending */ true);
+       edges_adjust_resize_geom(view, next_edges,
+               resize_edges, geo, /* use_pending */ true);
 }
 
 void
-snap_vector_to_next_edge(struct view *view, enum view_edge direction, int *dx, int *dy)
+snap_shrink_to_next_edge(struct view *view, enum view_edge direction,
+               struct wlr_box *geo)
 {
-       struct wlr_box delta = {0};
-       _snap_move_resize_to_edge(view, direction, SNAP_MODE_MOVE, &delta);
-       *dx = delta.x;
-       *dy = delta.y;
-}
+       assert(view);
+       assert(!view->shaded);
 
-int
-snap_distance_to_next_edge(struct view *view, enum view_edge direction)
-{
-       struct wlr_box delta = {0};
-       _snap_move_resize_to_edge(view, direction, SNAP_MODE_MOVE, &delta);
+       *geo = view->pending;
+       uint32_t resize_edges;
+
+       /*
+        * First shrink the view along the relevant edge. The maximum shrink
+        * allowed is half the current size, but the window must also meet
+        * minimum size requirements.
+        */
        switch (direction) {
-       case VIEW_EDGE_LEFT:  return -delta.x;
-       case VIEW_EDGE_UP:    return -delta.y;
-       case VIEW_EDGE_RIGHT: return  delta.x;
-       case VIEW_EDGE_DOWN:  return  delta.y;
-       default: return 0;
+       case VIEW_EDGE_RIGHT:
+               geo->width = MAX(geo->width / 2, LAB_MIN_VIEW_WIDTH);
+               geo->x = view->pending.x + view->pending.width - geo->width;
+               resize_edges = WLR_EDGE_LEFT;
+               break;
+       case VIEW_EDGE_LEFT:
+               geo->width = MAX(geo->width / 2, LAB_MIN_VIEW_WIDTH);
+               resize_edges = WLR_EDGE_RIGHT;
+               break;
+       case VIEW_EDGE_DOWN:
+               geo->height = MAX(geo->height / 2, LAB_MIN_VIEW_HEIGHT);
+               geo->y = view->pending.y + view->pending.height - geo->height;
+               resize_edges = WLR_EDGE_TOP;
+               break;
+       case VIEW_EDGE_UP:
+               geo->height = MAX(geo->height / 2, LAB_MIN_VIEW_HEIGHT);
+               resize_edges = WLR_EDGE_BOTTOM;
+               break;
+       default:
+               return;
        }
-}
 
-void
-snap_grow_to_next_edge(struct view *view, enum view_edge direction, struct wlr_box *geo)
-{
-       _snap_move_resize_to_edge(view, direction, SNAP_MODE_GROW, geo);
-}
+       /* If the view doesn't change size, abandon the shrink */
+       if (geo->width == view->pending.width &&
+                       geo->height == view->pending.height) {
+               *geo = view->pending;
+               return;
+       }
 
-void
-snap_shrink_to_next_edge(struct view *view, enum view_edge direction, struct wlr_box *geo)
-{
-       _snap_move_resize_to_edge(view, direction, SNAP_MODE_SHRINK, geo);
+       struct border next_edges;
+       edges_initialize(&next_edges);
+
+       /* Snap to output edges if the moving edge started off-screen */
+       edges_find_outputs(&next_edges, view, *geo,
+               view->output, check_edge, /* use_pending */ true);
+
+       /* Limit motion to any intervening edge of ther views on this output */
+       edges_find_neighbors(&next_edges, view, *geo,
+               view->output, check_edge, /* use_pending */ true);
+
+       edges_adjust_resize_geom(view, next_edges,
+               resize_edges, geo, /* use_pending */ true);
 }
index 180ae189d342ba2f17bbddcae6ba3b53211f1bf1..424e921ed2c8b09af6b6ee6e223e43dc885173ad 100644 (file)
@@ -1550,27 +1550,7 @@ view_move_to_edge(struct view *view, enum view_edge direction, bool snap_to_wind
        }
 
        int dx = 0, dy = 0;
-       if (snap_to_windows) {
-               snap_vector_to_next_edge(view, direction, &dx, &dy);
-       } else {
-               struct border distance = snap_get_max_distance(view);
-               switch (direction) {
-               case VIEW_EDGE_LEFT:
-                       dx = distance.left;
-                       break;
-               case VIEW_EDGE_UP:
-                       dy = distance.top;
-                       break;
-               case VIEW_EDGE_RIGHT:
-                       dx = distance.right;
-                       break;
-               case VIEW_EDGE_DOWN:
-                       dy = distance.bottom;
-                       break;
-               default:
-                       return;
-               }
-       }
+       snap_move_to_edge(view, direction, snap_to_windows, &dx, &dy);
 
        if (dx != 0 || dy != 0) {
                /* Move the window if a change was discovered */
@@ -1665,7 +1645,7 @@ view_grow_to_edge(struct view *view, enum view_edge direction)
 
        view_set_shade(view, false);
 
-       struct wlr_box geo = view->pending;
+       struct wlr_box geo;
        snap_grow_to_next_edge(view, direction, &geo);
        view_move_resize(view, geo);
 }