From cf34e60240b4419ab07c82b7959d9cfb4f969e70 Mon Sep 17 00:00:00 2001 From: "Andrew J. Hesford" Date: Fri, 2 Feb 2024 14:43:52 -0500 Subject: [PATCH] edges: limit edge attraction and resistance... ...to edges actually encountered by motion during interactive moves and resizes. In addition, ignore edge resistance and attraction for minimized views. --- include/edges.h | 20 +++-- include/view.h | 9 +++ src/edges.c | 190 ++++++++++++++++++++++++++++++++++++++++------- src/resistance.c | 57 +++++++------- src/snap.c | 22 +++--- src/view.c | 2 +- 6 files changed, 233 insertions(+), 67 deletions(-) diff --git a/include/edges.h b/include/edges.h index fd401c43..974893bd 100644 --- a/include/edges.h +++ b/include/edges.h @@ -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 */ diff --git a/include/view.h b/include/view.h index 887a199a..3d300dd6 100644 --- a/include/view.h +++ b/include/view.h @@ -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 diff --git a/src/edges.c b/src/edges.c index 66325b14..43df410a 100644 --- a/src/edges.c +++ b/src/edges.c @@ -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; +} diff --git a/src/resistance.c b/src/resistance.c index 241441fb..506fe782 100644 --- a/src/resistance.c +++ b/src/resistance.c @@ -10,11 +10,16 @@ #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 diff --git a/src/snap.c b/src/snap.c index 5d0e943d..af402ca1 100644 --- a/src/snap.c +++ b/src/snap.c @@ -12,9 +12,15 @@ #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); } } diff --git a/src/view.c b/src/view.c index 34117248..9773e070 100644 --- a/src/view.c +++ b/src/view.c @@ -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) {