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.
This commit is contained in:
Andrew J. Hesford 2024-02-02 14:43:52 -05:00 committed by Johan Malm
parent 14f5733584
commit cf34e60240
6 changed files with 233 additions and 67 deletions

View 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 */

View 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

View 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;
}

View file

@ -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

View file

@ -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);
}
}

View 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) {