interactive: allow snapping to corner edges

In addition to <snapping><range>, <snapping><cornerRange> configures the
distance from the screen corner to trigger quater window snapping.

Also, new values "up-left", "up-right", "down-left" and "down-right" are
allowed for <action name="(Toggle)SnapToEdge" direction="[value]"> and
<query tiled="[value]">.
This commit is contained in:
tokyo4j 2025-08-02 21:35:51 +09:00 committed by Johan Malm
parent b0ff2911b6
commit 2f183cdcb6
14 changed files with 147 additions and 89 deletions

View file

@ -4,22 +4,24 @@
#include <wlr/types/wlr_output_layout.h>
#include "view.h"
enum wlr_direction
direction_from_view_edge(enum view_edge edge)
bool
direction_from_view_edge(enum view_edge edge, enum wlr_direction *direction)
{
switch (edge) {
case VIEW_EDGE_LEFT:
return WLR_DIRECTION_LEFT;
*direction = WLR_DIRECTION_LEFT;
return true;
case VIEW_EDGE_RIGHT:
return WLR_DIRECTION_RIGHT;
*direction = WLR_DIRECTION_RIGHT;
return true;
case VIEW_EDGE_UP:
return WLR_DIRECTION_UP;
*direction = WLR_DIRECTION_UP;
return true;
case VIEW_EDGE_DOWN:
return WLR_DIRECTION_DOWN;
case VIEW_EDGE_CENTER:
case VIEW_EDGE_INVALID:
*direction = WLR_DIRECTION_DOWN;
return true;
default:
return WLR_DIRECTION_UP;
return false;
}
}

View file

@ -1175,6 +1175,8 @@ entry(xmlNode *node, char *nodename, char *content)
rc.unmaximize_threshold = atoi(content);
} else if (!strcasecmp(nodename, "range.snapping")) {
rc.snap_edge_range = atoi(content);
} else if (!strcasecmp(nodename, "cornerRange.snapping")) {
rc.snap_edge_corner_range = atoi(content);
} else if (!strcasecmp(nodename, "enabled.overlay.snapping")) {
set_bool(content, &rc.snap_overlay_enabled);
} else if (!strcasecmp(nodename, "inner.delay.overlay.snapping")) {
@ -1411,6 +1413,7 @@ rcxml_init(void)
rc.unmaximize_threshold = 150;
rc.snap_edge_range = 10;
rc.snap_edge_corner_range = 50;
rc.snap_overlay_enabled = true;
rc.snap_overlay_delay_inner = 500;
rc.snap_overlay_delay_outer = 500;

View file

@ -164,22 +164,26 @@ interactive_begin(struct view *view, enum input_mode mode, uint32_t edges)
}
}
enum view_edge
edge_from_cursor(struct seat *seat, struct output **dest_output)
bool
edge_from_cursor(struct seat *seat, struct output **dest_output,
enum view_edge *edge1, enum view_edge *edge2)
{
*dest_output = NULL;
*edge1 = VIEW_EDGE_INVALID;
*edge2 = VIEW_EDGE_INVALID;
if (!view_is_floating(seat->server->grabbed_view)) {
return VIEW_EDGE_INVALID;
return false;
}
int snap_range = rc.snap_edge_range;
if (!snap_range) {
return VIEW_EDGE_INVALID;
if (rc.snap_edge_range == 0) {
return false;
}
struct output *output = output_nearest_to_cursor(seat->server);
if (!output_is_usable(output)) {
wlr_log(WLR_ERROR, "output at cursor is unusable");
return VIEW_EDGE_INVALID;
return false;
}
*dest_output = output;
@ -190,18 +194,39 @@ edge_from_cursor(struct seat *seat, struct output **dest_output)
output->wlr_output, &cursor_x, &cursor_y);
struct wlr_box *area = &output->usable_area;
if (cursor_x <= area->x + snap_range) {
return VIEW_EDGE_LEFT;
} else if (cursor_x >= area->x + area->width - snap_range) {
return VIEW_EDGE_RIGHT;
} else if (cursor_y <= area->y + snap_range) {
return VIEW_EDGE_UP;
} else if (cursor_y >= area->y + area->height - snap_range) {
return VIEW_EDGE_DOWN;
int top = cursor_y - area->y;
int bottom = area->y + area->height - cursor_y;
int left = cursor_x - area->x;
int right = area->x + area->width - cursor_x;
if (top < rc.snap_edge_range) {
*edge1 = VIEW_EDGE_UP;
} else if (bottom < rc.snap_edge_range) {
*edge1 = VIEW_EDGE_DOWN;
} else if (left < rc.snap_edge_range) {
*edge1 = VIEW_EDGE_LEFT;
} else if (right < rc.snap_edge_range) {
*edge1 = VIEW_EDGE_RIGHT;
} else {
/* Not close to any edge */
return VIEW_EDGE_INVALID;
return false;
}
if (*edge1 == VIEW_EDGE_UP || *edge1 == VIEW_EDGE_DOWN) {
if (left < rc.snap_edge_corner_range) {
*edge2 = VIEW_EDGE_LEFT;
} else if (right < rc.snap_edge_corner_range) {
*edge2 = VIEW_EDGE_RIGHT;
}
} else if (*edge1 == VIEW_EDGE_LEFT || *edge1 == VIEW_EDGE_RIGHT) {
if (top < rc.snap_edge_corner_range) {
*edge2 = VIEW_EDGE_UP;
} else if (bottom < rc.snap_edge_corner_range) {
*edge2 = VIEW_EDGE_DOWN;
}
}
return true;
}
/* Returns true if view was snapped to any edge */
@ -209,10 +234,11 @@ static bool
snap_to_edge(struct view *view)
{
struct output *output;
enum view_edge edge = edge_from_cursor(&view->server->seat, &output);
if (edge == VIEW_EDGE_INVALID) {
enum view_edge edge1, edge2;
if (!edge_from_cursor(&view->server->seat, &output, &edge1, &edge2)) {
return false;
}
enum view_edge edge = edge1 | edge2;
view_set_output(view, output);
/*

View file

@ -979,6 +979,11 @@ output_get_adjacent(struct output *output, enum view_edge edge, bool wrap)
return NULL;
}
enum wlr_direction direction;
if (!direction_from_view_edge(edge, &direction)) {
return NULL;
}
struct wlr_box box = output_usable_area_in_layout_coords(output);
int lx = box.x + box.width / 2;
int ly = box.y + box.height / 2;
@ -987,7 +992,6 @@ output_get_adjacent(struct output *output, enum view_edge edge, bool wrap)
struct wlr_output *new_output = NULL;
struct wlr_output *current_output = output->wlr_output;
struct wlr_output_layout *layout = output->server->output_layout;
enum wlr_direction direction = direction_from_view_edge(edge);
new_output = wlr_output_layout_adjacent_output(layout, direction,
current_output, lx, ly);

View file

@ -2,6 +2,7 @@
#include "overlay.h"
#include <assert.h>
#include <wlr/types/wlr_scene.h>
#include "common/direction.h"
#include "common/lab-scene-rect.h"
#include "labwc.h"
#include "output.h"
@ -137,42 +138,27 @@ handle_edge_overlay_timeout(void *data)
return 0;
}
static enum wlr_direction
get_wlr_direction(enum view_edge edge)
{
switch (edge) {
case VIEW_EDGE_LEFT:
return WLR_DIRECTION_LEFT;
case VIEW_EDGE_RIGHT:
return WLR_DIRECTION_RIGHT;
case VIEW_EDGE_UP:
case VIEW_EDGE_CENTER:
return WLR_DIRECTION_UP;
case VIEW_EDGE_DOWN:
return WLR_DIRECTION_DOWN;
default:
/* not reached */
assert(false);
return 0;
}
}
static bool
edge_has_adjacent_output_from_cursor(struct seat *seat, struct output *output,
enum view_edge edge)
{
enum wlr_direction dir;
if (!direction_from_view_edge(edge, &dir)) {
return false;
}
return wlr_output_layout_adjacent_output(
seat->server->output_layout, get_wlr_direction(edge),
seat->server->output_layout, dir,
output->wlr_output, seat->cursor->x, seat->cursor->y);
}
static void
show_edge_overlay(struct seat *seat, enum view_edge edge,
show_edge_overlay(struct seat *seat, enum view_edge edge1, enum view_edge edge2,
struct output *output)
{
if (!rc.snap_overlay_enabled) {
return;
}
uint32_t edge = edge1 | edge2;
if (seat->overlay.active.edge == edge
&& seat->overlay.active.output == output) {
return;
@ -182,7 +168,7 @@ show_edge_overlay(struct seat *seat, enum view_edge edge,
seat->overlay.active.output = output;
int delay;
if (edge_has_adjacent_output_from_cursor(seat, output, edge)) {
if (edge_has_adjacent_output_from_cursor(seat, output, edge1)) {
delay = rc.snap_overlay_delay_inner;
} else {
delay = rc.snap_overlay_delay_outer;
@ -219,9 +205,9 @@ overlay_update(struct seat *seat)
/* Edge-snapping overlay */
struct output *output;
enum view_edge edge = edge_from_cursor(seat, &output);
if (edge != VIEW_EDGE_INVALID) {
show_edge_overlay(seat, edge, output);
enum view_edge edge1, edge2;
if (edge_from_cursor(seat, &output, &edge1, &edge2)) {
show_edge_overlay(seat, edge1, edge2, output);
return;
}

View file

@ -451,35 +451,29 @@ view_get_edge_snap_box(struct view *view, struct output *output,
enum view_edge edge)
{
struct wlr_box usable = output_usable_area_in_layout_coords(output);
int x_offset = edge == VIEW_EDGE_RIGHT
? (usable.width + rc.gap) / 2 : rc.gap;
int y_offset = edge == VIEW_EDGE_DOWN
? (usable.height + rc.gap) / 2 : rc.gap;
int x1 = rc.gap;
int y1 = rc.gap;
int x2 = usable.width - rc.gap;
int y2 = usable.height - rc.gap;
int base_width, base_height;
switch (edge) {
case VIEW_EDGE_LEFT:
case VIEW_EDGE_RIGHT:
base_width = (usable.width - 3 * rc.gap) / 2;
base_height = usable.height - 2 * rc.gap;
break;
case VIEW_EDGE_UP:
case VIEW_EDGE_DOWN:
base_width = usable.width - 2 * rc.gap;
base_height = (usable.height - 3 * rc.gap) / 2;
break;
default:
case VIEW_EDGE_CENTER:
base_width = usable.width - 2 * rc.gap;
base_height = usable.height - 2 * rc.gap;
break;
if (edge & VIEW_EDGE_RIGHT) {
x1 = (usable.width + rc.gap) / 2;
}
if (edge & VIEW_EDGE_LEFT) {
x2 = (usable.width - rc.gap) / 2;
}
if (edge & VIEW_EDGE_DOWN) {
y1 = (usable.height + rc.gap) / 2;
}
if (edge & VIEW_EDGE_UP) {
y2 = (usable.height - rc.gap) / 2;
}
struct wlr_box dst = {
.x = x_offset + usable.x,
.y = y_offset + usable.y,
.width = base_width,
.height = base_height,
.x = x1 + usable.x,
.y = y1 + usable.y,
.width = x2 - x1,
.height = y2 - y1,
};
if (view) {
@ -2149,6 +2143,14 @@ view_edge_parse(const char *direction, bool tiled, bool any)
if (tiled) {
if (!strcasecmp(direction, "center")) {
return VIEW_EDGE_CENTER;
} else if (!strcasecmp(direction, "up-left")) {
return VIEW_EDGE_UPLEFT;
} else if (!strcasecmp(direction, "up-right")) {
return VIEW_EDGE_UPRIGHT;
} else if (!strcasecmp(direction, "down-left")) {
return VIEW_EDGE_DOWNLEFT;
} else if (!strcasecmp(direction, "down-right")) {
return VIEW_EDGE_DOWNRIGHT;
}
}

View file

@ -660,6 +660,19 @@ xdg_toplevel_view_notify_tiled(struct view *view)
case VIEW_EDGE_DOWN:
edge = WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT;
break;
case VIEW_EDGE_UPLEFT:
edge = WLR_EDGE_TOP | WLR_EDGE_LEFT;
break;
case VIEW_EDGE_UPRIGHT:
edge = WLR_EDGE_TOP | WLR_EDGE_RIGHT;
break;
case VIEW_EDGE_DOWNLEFT:
edge = WLR_EDGE_BOTTOM | WLR_EDGE_LEFT;
break;
case VIEW_EDGE_DOWNRIGHT:
edge = WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT;
break;
/* TODO: VIEW_EDGE_CENTER? */
default:
edge = WLR_EDGE_NONE;
}