]> git.mdlowis.com Git - proto/labwc.git/commitdiff
edges: limit edge attraction and resistance...
authorAndrew J. Hesford <ajh@sideband.org>
Fri, 2 Feb 2024 19:43:52 +0000 (14:43 -0500)
committerJohan Malm <johanmalm@users.noreply.github.com>
Mon, 5 Feb 2024 22:05:22 +0000 (22:05 +0000)
...to edges actually encountered by motion during interactive moves and
resizes.

In addition, ignore edge resistance and attraction for minimized views.

include/edges.h
include/view.h
src/edges.c
src/resistance.c
src/snap.c
src/view.c

index fd401c435ace6e1833ce9b372c3306d65f93aada..974893bdbdc4a797c8c489f05ec209a32ca9ca66 100644 (file)
@@ -50,6 +50,15 @@ edge_get_best(int next, int edge, bool decreasing)
        return decreasing ? MAX(next, edge) : MIN(next, edge);
 }
 
+struct edge {
+       /* Position of an edge along the axis perpendicular to it */
+       int offset;
+
+       /* Limits of edge along axis parallel to it */
+       int min;
+       int max;
+};
+
 /*
  * edge_validator_t - edge validator signature
  * @best: pointer to the current "best" edge
@@ -57,16 +66,13 @@ edge_get_best(int next, int edge, bool decreasing)
  * @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).
+ * region and should be considered a snap point.
  *
  * 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
@@ -85,8 +91,8 @@ edge_get_best(int next, int edge, bool decreasing)
  * 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);
+typedef void (*edge_validator_t)(int *best, struct edge current,
+       struct edge target, struct edge oppose, struct edge align);
 
 void edges_initialize(struct border *edges);
 
@@ -107,4 +113,6 @@ void edges_adjust_move_coords(struct view *view, struct border edges,
 void edges_adjust_resize_geom(struct view *view, struct border edges,
        uint32_t resize_edges, struct wlr_box *geom, bool use_pending);
 
+bool edges_traverse_edge(struct edge current, struct edge target, struct edge edge);
+
 #endif /* LABWC_EDGES_H */
index 887a199a507470abc29bcfe3e960cb43939f133c..3d300dd6a74accd23398a684e65f9d337a16e697 100644 (file)
@@ -348,6 +348,15 @@ enum view_wants_focus view_wants_focus(struct view *view);
  */
 bool view_is_focusable_from(struct view *view, struct wlr_surface *prev);
 
+/**
+ * view_edge_invert() - select the opposite of a provided edge
+ *
+ * VIEW_EDGE_CENTER and VIEW_EDGE_INVALID both map to VIEW_EDGE_INVALID.
+ *
+ * @edge: edge to be inverted
+ */
+enum view_edge view_edge_invert(enum view_edge edge);
+
 /**
  * view_is_focusable() - Check whether or not a view can be focused
  * @view: view to be checked
index 66325b1448d6a7b3bc64af22c9385596dede8ddb..43df410a945a72bdd5d57494343f627f12f690c6 100644 (file)
@@ -34,10 +34,45 @@ edges_initialize(struct border *edges)
        edges->left = INT_MIN;
 }
 
+static inline struct edge
+build_edge(struct border region, enum view_edge direction, int pad)
+{
+       struct edge edge = { 0 };
+
+       switch (direction) {
+       case VIEW_EDGE_LEFT:
+               edge.offset = clipped_sub(region.left, pad);
+               edge.min = region.top;
+               edge.max = region.bottom;
+               break;
+       case VIEW_EDGE_RIGHT:
+               edge.offset = clipped_add(region.right, pad);
+               edge.min = region.top;
+               edge.max = region.bottom;
+               break;
+       case VIEW_EDGE_UP:
+               edge.offset = clipped_sub(region.top, pad);
+               edge.min = region.left;
+               edge.max = region.right;
+               break;
+       case VIEW_EDGE_DOWN:
+               edge.offset = clipped_add(region.bottom, pad);
+               edge.min = region.left;
+               edge.max = region.right;
+               break;
+       default:
+               /* Should never be reached */
+               assert(false);
+       }
+
+       return edge;
+}
+
 static void
-validate_edges(struct border *valid_edges,
+validate_single_region_edge(int *valid_edge,
                struct border view, struct border target,
-               struct border region, edge_validator_t validator)
+               struct border region, edge_validator_t validator,
+               enum view_edge direction)
 {
        /*
         * When a view snaps to another while moving to its target, it can do
@@ -55,28 +90,53 @@ validate_edges(struct border *valid_edges,
         * 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),
-       };
+       validator(valid_edge,
+               build_edge(view, direction, 0),
+               build_edge(target, direction, 0),
+               build_edge(region, view_edge_invert(direction), 0),
+               build_edge(region, direction, rc.gap));
+}
 
+static void
+validate_edges(struct border *valid_edges,
+               struct border view, struct border target,
+               struct border region, edge_validator_t validator)
+{
        /* Check for edges encountered during movement of left edge */
-       validator(&valid_edges->left, view.left, target.left,
-               region.right, region_pad.left, /* lesser */ true);
+       validate_single_region_edge(&valid_edges->left,
+               view, target, region, validator, VIEW_EDGE_LEFT);
 
        /* Check for edges encountered during movement of right edge */
-       validator(&valid_edges->right, view.right, target.right,
-               region.left, region_pad.right, /* lesser */ false);
+       validate_single_region_edge(&valid_edges->right,
+               view, target, region, validator, VIEW_EDGE_RIGHT);
 
        /* Check for edges encountered during movement of top edge */
-       validator(&valid_edges->top, view.top, target.top,
-               region.bottom, region_pad.top, /* lesser */ true);
+       validate_single_region_edge(&valid_edges->top,
+               view, target, region, validator, VIEW_EDGE_UP);
 
        /* Check for edges encountered during movement of bottom edge */
-       validator(&valid_edges->bottom, view.bottom, target.bottom,
-               region.top, region_pad.bottom, /* lesser */ false);
+       validate_single_region_edge(&valid_edges->bottom,
+               view, target, region, validator, VIEW_EDGE_DOWN);
+}
+
+static void
+validate_single_output_edge(int *valid_edge,
+               struct border view, struct border target,
+               struct border region, edge_validator_t validator,
+               enum view_edge direction)
+{
+       static struct border unbounded = {
+               .top = INT_MIN,
+               .right = INT_MAX,
+               .bottom = INT_MAX,
+               .left = INT_MIN,
+       };
+
+       validator(valid_edge,
+               build_edge(view, direction, 0),
+               build_edge(target, direction, 0),
+               build_edge(region, direction, 0),
+               build_edge(unbounded, direction, 0));
 }
 
 static void
@@ -108,21 +168,31 @@ validate_output_edges(struct border *valid_edges,
         * only the non-infinite edges.
         */
 
+       struct border output = {
+               .top = usable.y,
+               .right = usable.x + usable.width,
+               .bottom = usable.y + usable.height,
+               .left = usable.x,
+       };
+
        /* Left edge encounters a half-infinite region to the left of the output */
-       validator(&valid_edges->left, view.left, target.left,
-               usable.x, INT_MIN, /* lesser */ true);
+
+       validate_single_output_edge(&valid_edges->left,
+                       view, target, output, validator, VIEW_EDGE_LEFT);
 
        /* Right edge encounters a half-infinite region to the right of the output */
-       validator(&valid_edges->right, view.right, target.right,
-               usable.x + usable.width, INT_MAX, /* lesser */ false);
+
+       validate_single_output_edge(&valid_edges->right,
+                       view, target, output, validator, VIEW_EDGE_RIGHT);
 
        /* Top edge encounters a half-infinite region above the output */
-       validator(&valid_edges->top, view.top, target.top,
-               usable.y, INT_MIN, /* lesser */ true);
+
+       validate_single_output_edge(&valid_edges->top,
+                       view, target, output, validator, VIEW_EDGE_UP);
 
        /* Bottom edge encounters a half-infinite region below the output */
-       validator(&valid_edges->bottom, view.bottom, target.bottom,
-               usable.y + usable.height, INT_MAX, /* lesser */ false);
+       validate_single_output_edge(&valid_edges->bottom,
+                       view, target, output, validator, VIEW_EDGE_DOWN);
 }
 
 void
@@ -145,7 +215,7 @@ edges_find_neighbors(struct border *nearest_edges, struct view *view,
 
        struct view *v;
        for_each_view(v, &view->server->views, LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) {
-               if (v == view || !output_is_usable(v->output)) {
+               if (v == view || v->minimized || !output_is_usable(v->output)) {
                        continue;
                }
 
@@ -317,3 +387,73 @@ edges_adjust_resize_geom(struct view *view, struct border edges,
                }
        }
 }
+
+static double
+linear_interp(int x, int x1, int y1, int x2, int y2)
+{
+       /*
+        * For a line y = mx + b that passes through both (x1, y1) and
+        * (x2, y2), find and return the value y for a given point x.
+        *
+        * The point x does not need to fall in the range [x1, x2].
+        */
+
+       /* No need to interpolate if line is horizontal */
+       int rise = y2 - y1;
+       if (rise == 0) {
+               return y2;
+       }
+
+       /* For degenerate line, just pick a midpoint */
+       int run = x2 - x1;
+       if (run == 0) {
+               return 0.5 * (y1 + y2);
+       }
+
+       /* Othewise, linearly interpolate */
+       int dx = x - x1;
+       return y1 + dx * (rise / (double)run);
+}
+
+bool
+edges_traverse_edge(struct edge current, struct edge target, struct edge obstacle)
+{
+       /*
+        * Each edge structure defines a line segment that can be represented
+        * in a local coordinate system as starting at (offset, min) and
+        * finishing at (offset, max).
+        *
+        * The starting and ending points of the "current" edge trace
+        * respective lines
+        *
+        *   1. (current.offset, current.min) -> (target.offset, target.min)
+        *   2. (current.offset, current.max) -> (target.offset, target.max)
+        *
+        * as the segment transits from its current position to its target.
+        * Hence, motion of the entire edge from current to target will sweep a
+        * quadrilateral bounded by (locally) vertical lines at current.offset
+        * and target.offset as well as the segments (1) and (2) above.
+        *
+        * To test if the motion will encounter the obstacle edge, we need to
+        * test if any of the obstacle edge falls within this quadrilateral.
+        * Thus, we need to find the extent of the quadrilateral at the same
+        * offset as the obstacle: a segment with starting point
+        * (obstacle.offset, lo) and ending point (obstacle.offset, hi).
+        */
+
+       double lo =
+               linear_interp(obstacle.offset,
+                       current.offset, current.min, target.offset, target.min);
+
+       /* Motion misses when obstacle ends above start of quad segment */
+       if (obstacle.max < lo) {
+               return false;
+       }
+
+       double hi =
+               linear_interp(obstacle.offset,
+                       current.offset, current.max, target.offset, target.max);
+
+       /* Motion hits when obstacle starts above the end of quad segment */
+       return obstacle.min <= hi;
+}
index 241441fb6c99b2419106e66f5012174ea1738f03..506fe7820f7c55bb342254f8fd4a0476dd1f681c 100644 (file)
 #include "view.h"
 
 static void
-check_edge(int *next, int current, int target,
-               int oppose, int align, bool lesser, int tolerance)
+check_edge(int *next, struct edge current, struct edge target,
+               struct edge oppose, struct edge align, int tolerance)
 {
+       int cur = current.offset;
+       int tgt = target.offset;
+       int opp = oppose.offset;
+       int aln = align.offset;
+
        /* Ignore non-moving edges */
-       if (current == target) {
+       if (cur == tgt) {
                return;
        }
 
@@ -32,56 +37,56 @@ check_edge(int *next, int current, int target,
         */
 
        /* Direction of motion for the edge */
-       const bool decreasing = target < current;
+       const bool decreasing = tgt < cur;
 
        /* 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;
+               const int lo = clipped_sub(opp, abs(tolerance));
+               const int hi = clipped_sub(opp, MIN(tolerance, 0));
+               valid = tgt >= lo && tgt < 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;
+               const int lo = clipped_add(opp, MIN(tolerance, 0));
+               const int hi = clipped_add(opp, abs(tolerance));
+               valid = tgt > lo && tgt <= hi;
        }
 
-       if (valid) {
-               *next = edge_get_best(*next, oppose, decreasing);
+       if (valid && edges_traverse_edge(current, target, oppose)) {
+               *next = edge_get_best(*next, opp, decreasing);
        }
 
        /* 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;
+               const int lo = clipped_sub(aln, abs(tolerance));
+               const int hi = clipped_sub(aln, MIN(tolerance, 0));
+               valid = tgt >= lo && tgt < hi;
        } else {
-               const int lo = clipped_add(align, MIN(tolerance, 0));
-               const int hi = clipped_add(align, abs(tolerance));
-               valid = target > lo && target <= hi;
+               const int lo = clipped_add(aln, MIN(tolerance, 0));
+               const int hi = clipped_add(aln, abs(tolerance));
+               valid = tgt > lo && tgt <= hi;
        }
 
-       if (valid) {
-               *next = edge_get_best(*next, align, decreasing);
+       if (valid && edges_traverse_edge(current, target, align)) {
+               *next = edge_get_best(*next, aln, decreasing);
        }
 }
 
 static void
-check_edge_output(int *next, int current, int target,
-               int oppose, int align, bool lesser)
+check_edge_output(int *next, struct edge current, struct edge target,
+               struct edge oppose, struct edge align)
 {
        check_edge(next, current, target,
-               oppose, align, lesser, rc.screen_edge_strength);
+               oppose, align, rc.screen_edge_strength);
 }
 
 static void
-check_edge_window(int *next, int current, int target,
-               int oppose, int align, bool lesser)
+check_edge_window(int *next, struct edge current, struct edge target,
+               struct edge oppose, struct edge align)
 {
        check_edge(next, current, target,
-               oppose, align, lesser, rc.window_edge_strength);
+               oppose, align, rc.window_edge_strength);
 }
 
 void
index 5d0e943d8387cd297e383eaab0d14307b04927e1..af402ca16124b683c9bed5016998241231cd75c3 100644 (file)
 #include "view.h"
 
 static void
-check_edge(int *next, int current, int target, int oppose, int align, bool lesser)
+check_edge(int *next, struct edge current, struct edge target,
+               struct edge oppose, struct edge align)
 {
-       if (current == target) {
+       int cur = current.offset;
+       int tgt = target.offset;
+       int opp = oppose.offset;
+       int aln = align.offset;
+
+       if (cur == tgt) {
                return;
        }
 
@@ -32,18 +38,16 @@ check_edge(int *next, int current, int target, int oppose, int align, bool lesse
         */
 
        /* Direction of motion for the edge */
-       const bool decreasing = target < current;
+       const bool decreasing = tgt < cur;
 
        /* Check the opposing edge */
-       if ((target <= oppose && oppose < current) ||
-                       (current < oppose && oppose <= target)) {
-               *next = edge_get_best(*next, oppose, decreasing);
+       if ((tgt <= opp && opp < cur) || (cur < opp && opp <= tgt)) {
+               *next = edge_get_best(*next, opp, decreasing);
        }
 
        /* Check the aligned edge */
-       if ((target <= align && align < current) ||
-                       (current < align && align <= target)) {
-               *next = edge_get_best(*next, align, decreasing);
+       if ((tgt <= aln && aln < cur) || (cur < aln && aln <= tgt)) {
+               *next = edge_get_best(*next, aln, decreasing);
        }
 }
 
index 34117248cbf052edc9f00f1c04934692ff3b40d8..9773e07074ead6532cd8c8838f2423d880d7df12 100644 (file)
@@ -193,7 +193,7 @@ view_is_focusable_from(struct view *view, struct wlr_surface *prev)
  * They may be called repeatably during output layout changes.
  */
 
-static enum view_edge
+enum view_edge
 view_edge_invert(enum view_edge edge)
 {
        switch (edge) {