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
* @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
* 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);
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 */
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
* 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
* 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
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;
}
}
}
}
+
+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;
+}
#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;
}
*/
/* 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