labwc/src/snap.c
Axel Burri 1d72a08916 Add snap to window edge framework
Adds functions for calculation of distances between window edges, as
well as for window growing and shrinking.

All calculations are based on the "pending" geometry.

Ignored from snapping:

 - views that do not share the same output
 - minimized views
 - maximized views
 - views that are neither:
   - part of the current workspace
   - part of the always-on-top tree
2023-10-19 19:09:42 +01:00

279 lines
8.8 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
#include <assert.h>
#include <strings.h>
#include <wlr/util/box.h>
#include "labwc.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 + view->pending.width + margin.right,
.bottom = view->pending.y + view->pending.height + margin.bottom
};
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->pending.height
- 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) {
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 * v->pending.height;
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)
{
struct border edge = snap_get_view_edge(view);
struct border dmax;
if (mode == SNAP_MODE_SHRINK) {
/* limit to half of current size */
int width_max_dx = max(view->pending.width - LAB_MIN_VIEW_WIDTH, 0);
int height_max_dy = max(view->pending.height - LAB_MIN_VIEW_HEIGHT, 0);
dmax.right = min(width_max_dx, view->pending.width / 2);
dmax.bottom = min(height_max_dy, view->pending.height / 2);
dmax.left = -dmax.right;
dmax.top = -dmax.bottom;
} else {
dmax = snap_get_max_distance(view);
}
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)
);
}
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);
}
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);
}
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;
}
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;
}
}
void
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);
}
void
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);
}