mirror of
https://github.com/labwc/labwc.git
synced 2025-11-01 22:58:47 -04:00
edges, resistance, snap: unified resistance and snapping engine
This commit is contained in:
parent
1b0f1a4c4e
commit
e7e6d29237
8 changed files with 748 additions and 469 deletions
474
src/snap.c
474
src/snap.c
|
|
@ -1,285 +1,259 @@
|
|||
// 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 inline int
|
||||
max(int a, int b) {
|
||||
return a > b ? a : b;
|
||||
}
|
||||
|
||||
static inline int
|
||||
min3(int a, int b, int c) {
|
||||
return min(min(a, b), c);
|
||||
}
|
||||
|
||||
enum snap_mode {
|
||||
SNAP_MODE_MOVE = 0,
|
||||
SNAP_MODE_GROW,
|
||||
SNAP_MODE_SHRINK,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
struct border
|
||||
snap_get_max_distance(struct view *view)
|
||||
{
|
||||
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 */
|
||||
|
||||
const int add_view_x;
|
||||
const int add_view_y;
|
||||
const int add_view_width;
|
||||
const int add_view_height;
|
||||
|
||||
const int add_margin_left;
|
||||
const int add_margin_top;
|
||||
const int add_margin_right;
|
||||
const int add_margin_bottom;
|
||||
};
|
||||
|
||||
/* 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 };
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (def.search_dir * vp > 0 && def.search_dir * (vp - p) < 0) {
|
||||
p = vp;
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static void
|
||||
_snap_move_resize_to_edge(struct view *view, enum view_edge direction, enum snap_mode mode,
|
||||
struct wlr_box *delta)
|
||||
check_edge(int *next, int current, int target, int oppose, int align, bool lesser)
|
||||
{
|
||||
struct border edge = snap_get_view_edge(view);
|
||||
struct border dmax;
|
||||
|
||||
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);
|
||||
if (current == target) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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).
|
||||
*/
|
||||
|
||||
/* Direction of motion for the edge */
|
||||
const bool decreasing = target < current;
|
||||
|
||||
/* Check the opposing edge */
|
||||
if ((target <= oppose && oppose < current) ||
|
||||
(current < oppose && oppose <= target)) {
|
||||
*next = edge_get_best(*next, oppose, decreasing);
|
||||
}
|
||||
|
||||
/* Check the aligned edge */
|
||||
if ((target <= align && align < current) ||
|
||||
(current < align && align <= target)) {
|
||||
*next = edge_get_best(*next, align, decreasing);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
snap_move_to_edge(struct view *view, enum view_edge direction,
|
||||
bool snap_to_windows, int *dx, int *dy)
|
||||
{
|
||||
assert(view);
|
||||
|
||||
*dx = 0;
|
||||
*dy = 0;
|
||||
|
||||
struct output *output = view->output;
|
||||
if (!output_is_usable(output)) {
|
||||
wlr_log(WLR_ERROR, "view has no output, not snapping to edge");
|
||||
return;
|
||||
}
|
||||
|
||||
struct wlr_box target = view->pending;
|
||||
struct border ssd = ssd_thickness(view);
|
||||
struct wlr_box usable = output_usable_area_in_layout_coords(output);
|
||||
|
||||
/*
|
||||
* 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:
|
||||
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)
|
||||
);
|
||||
target.x = usable.x + ssd.left + rc.gap;
|
||||
if (target.x >= view->pending.x) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
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:
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
snap_vector_to_next_edge(struct view *view, enum view_edge direction, int *dx, int *dy)
|
||||
{
|
||||
struct wlr_box delta = {0};
|
||||
_snap_move_resize_to_edge(view, direction, SNAP_MODE_MOVE, &delta);
|
||||
*dx = delta.x;
|
||||
*dy = delta.y;
|
||||
}
|
||||
/*
|
||||
* 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);
|
||||
|
||||
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);
|
||||
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;
|
||||
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);
|
||||
}
|
||||
|
||||
*dx = target.x - view->pending.x;
|
||||
*dy = target.y - view->pending.y;
|
||||
}
|
||||
|
||||
void
|
||||
snap_grow_to_next_edge(struct view *view, enum view_edge direction, struct wlr_box *geo)
|
||||
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);
|
||||
assert(view);
|
||||
assert(!view->shaded);
|
||||
|
||||
*geo = view->pending;
|
||||
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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_shrink_to_next_edge(struct view *view, enum view_edge direction, struct wlr_box *geo)
|
||||
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);
|
||||
assert(view);
|
||||
assert(!view->shaded);
|
||||
|
||||
*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_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;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue