mirror of
https://github.com/labwc/labwc.git
synced 2025-10-29 05:40:24 -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
|
|
@ -2,6 +2,8 @@
|
||||||
#ifndef LABWC_MACROS_H
|
#ifndef LABWC_MACROS_H
|
||||||
#define LABWC_MACROS_H
|
#define LABWC_MACROS_H
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ARRAY_SIZE() - Get the number of elements in array.
|
* ARRAY_SIZE() - Get the number of elements in array.
|
||||||
* @arr: array to be sized
|
* @arr: array to be sized
|
||||||
|
|
@ -50,4 +52,13 @@
|
||||||
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BOUNDED_INT() - Returns true if an integer is not INT_MAX or INT_MIN
|
||||||
|
*
|
||||||
|
* @param val Value to test (integer)
|
||||||
|
*/
|
||||||
|
#ifndef BOUNDED_INT
|
||||||
|
#define BOUNDED_INT(a) ((a) < INT_MAX && (a) > INT_MIN)
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif /* LABWC_MACROS_H */
|
#endif /* LABWC_MACROS_H */
|
||||||
|
|
|
||||||
110
include/edges.h
Normal file
110
include/edges.h
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||||
|
#ifndef LABWC_EDGES_H
|
||||||
|
#define LABWC_EDGES_H
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include "common/macros.h"
|
||||||
|
|
||||||
|
struct border;
|
||||||
|
struct output;
|
||||||
|
struct view;
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
clipped_add(int a, int b)
|
||||||
|
{
|
||||||
|
if (b > 0) {
|
||||||
|
return a >= (INT_MAX - b) ? INT_MAX : (a + b);
|
||||||
|
} else if (b < 0) {
|
||||||
|
return a <= (INT_MIN - b) ? INT_MIN : (a + b);
|
||||||
|
}
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
clipped_sub(int a, int b)
|
||||||
|
{
|
||||||
|
if (b > 0) {
|
||||||
|
return a <= (INT_MIN + b) ? INT_MIN : (a - b);
|
||||||
|
} else if (b < 0) {
|
||||||
|
return a >= (INT_MAX + b) ? INT_MAX : (a - b);
|
||||||
|
}
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
edge_get_best(int next, int edge, bool decreasing)
|
||||||
|
{
|
||||||
|
if (!BOUNDED_INT(next)) {
|
||||||
|
/* Any bounded edge beats an unbounded next */
|
||||||
|
return BOUNDED_INT(edge) ? edge : next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No unbounded edge ever beats next */
|
||||||
|
if (!BOUNDED_INT(edge)) {
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Max edge wins for decreasing moves, min edge for increasing */
|
||||||
|
return decreasing ? MAX(next, edge) : MIN(next, edge);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* edge_validator_t - edge validator signature
|
||||||
|
* @best: pointer to the current "best" edge
|
||||||
|
* @current: current position of a moving 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).
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* an opposing edge, the view should maintain the configured gap. Aligned edges
|
||||||
|
* are on the same side of the target region from the moving edge (i.e.,
|
||||||
|
* left <-> left, right <-> right, top <-> top, bottom <-> bottom). When the
|
||||||
|
* moving edge snaps to an aligned edge, the view should *not* include a gap.
|
||||||
|
*
|
||||||
|
* If window gaps are configured, all edges will be offset as appropriate to
|
||||||
|
* reflect the desired padding. Thus, the validator should generally compare
|
||||||
|
* the given current or target values directly to the opposing and aligned edge
|
||||||
|
* without regard for rc.gap.
|
||||||
|
*
|
||||||
|
* Any edge may take the values INT_MIN or INT_MAX to indicate that the edge
|
||||||
|
* should be effectively ignored. Should the validator decide that a given
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
void edges_initialize(struct border *edges);
|
||||||
|
|
||||||
|
void edges_adjust_geom(struct view *view, struct border edges,
|
||||||
|
uint32_t resize_edges, struct wlr_box *geom);
|
||||||
|
|
||||||
|
void edges_find_neighbors(struct border *nearest_edges, struct view *view,
|
||||||
|
struct wlr_box target, struct output *output,
|
||||||
|
edge_validator_t validator, bool use_pending);
|
||||||
|
|
||||||
|
void edges_find_outputs(struct border *nearest_edges, struct view *view,
|
||||||
|
struct wlr_box target, struct output *output,
|
||||||
|
edge_validator_t validator, bool use_pending);
|
||||||
|
|
||||||
|
void edges_adjust_move_coords(struct view *view, struct border edges,
|
||||||
|
int *x, int *y, bool use_pending);
|
||||||
|
|
||||||
|
void edges_adjust_resize_geom(struct view *view, struct border edges,
|
||||||
|
uint32_t resize_edges, struct wlr_box *geom, bool use_pending);
|
||||||
|
|
||||||
|
#endif /* LABWC_EDGES_H */
|
||||||
|
|
@ -7,11 +7,13 @@
|
||||||
|
|
||||||
struct wlr_box;
|
struct wlr_box;
|
||||||
|
|
||||||
struct border snap_get_max_distance(struct view *view);
|
void snap_move_to_edge(struct view *view,
|
||||||
|
enum view_edge direction, bool snap_to_windows, int *dx, int *dy);
|
||||||
|
|
||||||
void snap_vector_to_next_edge(struct view *view, enum view_edge direction, int *dx, int *dy);
|
void snap_grow_to_next_edge(struct view *view,
|
||||||
int snap_distance_to_next_edge(struct view *view, enum view_edge direction);
|
enum view_edge direction, struct wlr_box *geo);
|
||||||
void snap_grow_to_next_edge(struct view *view, enum view_edge direction, struct wlr_box *geo);
|
|
||||||
void snap_shrink_to_next_edge(struct view *view, enum view_edge direction, struct wlr_box *geo);
|
void snap_shrink_to_next_edge(struct view *view,
|
||||||
|
enum view_edge direction, struct wlr_box *geo);
|
||||||
|
|
||||||
#endif /* LABWC_SNAP_H */
|
#endif /* LABWC_SNAP_H */
|
||||||
|
|
|
||||||
303
src/edges.c
Normal file
303
src/edges.c
Normal file
|
|
@ -0,0 +1,303 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
#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 "view.h"
|
||||||
|
|
||||||
|
static void
|
||||||
|
edges_for_target_geometry(struct border *edges, struct view *view,
|
||||||
|
struct wlr_box target)
|
||||||
|
{
|
||||||
|
struct border border = ssd_get_margin(view->ssd);
|
||||||
|
|
||||||
|
/* Use the effective height to properly handle shaded views */
|
||||||
|
int eff_height = view->shaded ? 0 : target.height;
|
||||||
|
|
||||||
|
edges->left = target.x - border.left - rc.gap;
|
||||||
|
edges->top = target.y - border.top - rc.gap;
|
||||||
|
edges->right = target.x + target.width + border.right + rc.gap;
|
||||||
|
edges->bottom = target.y + eff_height + border.bottom + rc.gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
edges_initialize(struct border *edges)
|
||||||
|
{
|
||||||
|
assert(edges);
|
||||||
|
edges->top = INT_MIN;
|
||||||
|
edges->right = INT_MAX;
|
||||||
|
edges->bottom = INT_MAX;
|
||||||
|
edges->left = INT_MIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
validate_edges(struct border *valid_edges,
|
||||||
|
struct border view, struct border target,
|
||||||
|
struct border region, edge_validator_t validator)
|
||||||
|
{
|
||||||
|
/* When a view snaps to a region while moving to its target, it can do
|
||||||
|
* so in two ways: a view edge can snap to an "opposing" edge of the
|
||||||
|
* region (left <-> right, top <-> bottom) or to an "aligned" edge
|
||||||
|
* (left <-> left, right <-> right, top <-> top, bottom <-> bottom).
|
||||||
|
*
|
||||||
|
* When a view hits the opposing edge of a region, it should be
|
||||||
|
* separated by a gap; when a view hits the aligned edge, it should not
|
||||||
|
* be separated. The view and its target already include necessary
|
||||||
|
* padding to reflect the gap. The region does not. To make sure the
|
||||||
|
* "aligned" edges are properly aligned, add padding to 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),
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Check for edges encountered during movement of left edge */
|
||||||
|
validator(&valid_edges->left, view.left, target.left,
|
||||||
|
region.right, region_pad.left, /* lesser */ true);
|
||||||
|
|
||||||
|
/* Check for edges encountered during movement of right edge */
|
||||||
|
validator(&valid_edges->right, view.right, target.right,
|
||||||
|
region.left, region_pad.right, /* lesser */ false);
|
||||||
|
|
||||||
|
/* Check for edges encountered during movement of top edge */
|
||||||
|
validator(&valid_edges->top, view.top, target.top,
|
||||||
|
region.bottom, region_pad.top, /* lesser */ true);
|
||||||
|
|
||||||
|
/* Check for edges encountered during movement of bottom edge */
|
||||||
|
validator(&valid_edges->bottom, view.bottom, target.bottom,
|
||||||
|
region.top, region_pad.bottom, /* lesser */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
edges_find_neighbors(struct border *nearest_edges, struct view *view,
|
||||||
|
struct wlr_box target, struct output *output,
|
||||||
|
edge_validator_t validator, bool use_pending)
|
||||||
|
{
|
||||||
|
assert(view);
|
||||||
|
assert(validator);
|
||||||
|
assert(nearest_edges);
|
||||||
|
|
||||||
|
struct border view_edges = { 0 };
|
||||||
|
struct border target_edges = { 0 };
|
||||||
|
|
||||||
|
edges_for_target_geometry(&view_edges, view,
|
||||||
|
use_pending ? view->pending : view->current);
|
||||||
|
edges_for_target_geometry(&target_edges, view, target);
|
||||||
|
|
||||||
|
struct view *v;
|
||||||
|
for_each_view(v, &view->server->views, LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) {
|
||||||
|
if (v == view || !output_is_usable(v->output)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output && v->output != output) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct border border = ssd_get_margin(v->ssd);
|
||||||
|
|
||||||
|
struct border win_edges = {
|
||||||
|
.top = v->current.y - border.top,
|
||||||
|
.left = v->current.x - border.left,
|
||||||
|
.bottom = v->current.y + border.bottom
|
||||||
|
+ view_effective_height(v, /* use_pending */ false),
|
||||||
|
.right = v->current.x + v->current.width + border.right,
|
||||||
|
};
|
||||||
|
|
||||||
|
validate_edges(nearest_edges, view_edges,
|
||||||
|
target_edges, win_edges, validator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
edges_find_outputs(struct border *nearest_edges, struct view *view,
|
||||||
|
struct wlr_box target, struct output *output,
|
||||||
|
edge_validator_t validator, bool use_pending)
|
||||||
|
{
|
||||||
|
assert(view);
|
||||||
|
assert(validator);
|
||||||
|
assert(nearest_edges);
|
||||||
|
|
||||||
|
struct border view_edges = { 0 };
|
||||||
|
struct border target_edges = { 0 };
|
||||||
|
|
||||||
|
struct wlr_box *view_geom =
|
||||||
|
use_pending ? &view->pending : &view->current;
|
||||||
|
|
||||||
|
edges_for_target_geometry(&view_edges, view, *view_geom);
|
||||||
|
edges_for_target_geometry(&target_edges, view, target);
|
||||||
|
|
||||||
|
struct output *o;
|
||||||
|
wl_list_for_each(o, &view->server->outputs, link) {
|
||||||
|
if (!output_is_usable(o)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output && o != output) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct wlr_box usable =
|
||||||
|
output_usable_area_in_layout_coords(o);
|
||||||
|
|
||||||
|
struct wlr_box ol;
|
||||||
|
if (!wlr_box_intersection(&ol, view_geom, &usable) &&
|
||||||
|
!wlr_box_intersection(&ol, &target, &usable)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Split a single "leaving output" problem into four "entering
|
||||||
|
* complementary region" problems, treating the view, its
|
||||||
|
* target and the screen boundaries as half planes. This
|
||||||
|
* prevents unexpected snapping behavior like the bottom of a
|
||||||
|
* window snapping above the top of an output, where it would
|
||||||
|
* become invisible.
|
||||||
|
*/
|
||||||
|
struct border screen;
|
||||||
|
struct border view_eff;
|
||||||
|
struct border target_eff;
|
||||||
|
|
||||||
|
/* First problem: view toward upper half-plane */
|
||||||
|
edges_initialize(&screen);
|
||||||
|
edges_initialize(&view_eff);
|
||||||
|
edges_initialize(&target_eff);
|
||||||
|
|
||||||
|
screen.bottom = usable.y;
|
||||||
|
view_eff.top = view_edges.top;
|
||||||
|
target_eff.top = target_edges.top;
|
||||||
|
validate_edges(nearest_edges, view_eff, target_eff, screen, validator);
|
||||||
|
|
||||||
|
/* Second problem: view toward lower half-plane */
|
||||||
|
edges_initialize(&screen);
|
||||||
|
edges_initialize(&view_eff);
|
||||||
|
edges_initialize(&target_eff);
|
||||||
|
|
||||||
|
screen.top = usable.y + usable.height;
|
||||||
|
view_eff.bottom = view_edges.bottom;
|
||||||
|
target_eff.bottom = target_edges.bottom;
|
||||||
|
validate_edges(nearest_edges, view_eff, target_eff, screen, validator);
|
||||||
|
|
||||||
|
/* Third problem: view toward left half-plane */
|
||||||
|
edges_initialize(&screen);
|
||||||
|
edges_initialize(&view_eff);
|
||||||
|
edges_initialize(&target_eff);
|
||||||
|
|
||||||
|
screen.right = usable.x;
|
||||||
|
view_eff.left = view_edges.left;
|
||||||
|
target_eff.left = target_edges.left;
|
||||||
|
validate_edges(nearest_edges, view_eff, target_eff, screen, validator);
|
||||||
|
|
||||||
|
/* Fourth problem: view toward right half-plane */
|
||||||
|
edges_initialize(&screen);
|
||||||
|
edges_initialize(&view_eff);
|
||||||
|
edges_initialize(&target_eff);
|
||||||
|
|
||||||
|
screen.left = usable.x + usable.width;
|
||||||
|
view_eff.right = view_edges.right;
|
||||||
|
target_eff.right = target_edges.right;
|
||||||
|
validate_edges(nearest_edges, view_eff, target_eff, screen, validator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
adjust_move_coords_1d(int *edge, int lesser, int lesser_offset,
|
||||||
|
int greater, int greater_offset, bool decreasing)
|
||||||
|
{
|
||||||
|
/* Default best candidate is not valid */
|
||||||
|
int best = INT_MAX;
|
||||||
|
|
||||||
|
if (BOUNDED_INT(lesser)) {
|
||||||
|
/* A valid lesser edge is the always the first candidate */
|
||||||
|
best = clipped_add(lesser, lesser_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BOUNDED_INT(greater)) {
|
||||||
|
/* Check if a valid greater edge is a better candidate */
|
||||||
|
best = edge_get_best(best,
|
||||||
|
clipped_sub(greater, greater_offset), decreasing);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BOUNDED_INT(best)) {
|
||||||
|
/* Replace the edge if a valid candidate was found */
|
||||||
|
*edge = best;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
edges_adjust_move_coords(struct view *view, struct border edges,
|
||||||
|
int *x, int *y, bool use_pending)
|
||||||
|
{
|
||||||
|
assert(view);
|
||||||
|
|
||||||
|
struct border border = ssd_get_margin(view->ssd);
|
||||||
|
struct wlr_box *view_geom =
|
||||||
|
use_pending ? &view->pending : &view->current;
|
||||||
|
|
||||||
|
/* When moving, limit motion to the best valid, intervening edge */
|
||||||
|
|
||||||
|
if (view_geom->x != *x) {
|
||||||
|
int lshift = border.left + rc.gap;
|
||||||
|
int rshift = border.right + rc.gap + view->pending.width;
|
||||||
|
|
||||||
|
adjust_move_coords_1d(x, edges.left, lshift,
|
||||||
|
edges.right, rshift, *x < view_geom->x);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (view_geom->y != *y) {
|
||||||
|
int tshift = border.top + rc.gap;
|
||||||
|
int bshift = border.bottom + rc.gap
|
||||||
|
+ view_effective_height(view, /* use_pending */ true);
|
||||||
|
|
||||||
|
adjust_move_coords_1d(y, edges.top, tshift,
|
||||||
|
edges.bottom, bshift, *y < view_geom->y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
edges_adjust_resize_geom(struct view *view, struct border edges,
|
||||||
|
uint32_t resize_edges, struct wlr_box *geom, bool use_pending)
|
||||||
|
{
|
||||||
|
assert(view);
|
||||||
|
|
||||||
|
struct border border = ssd_get_margin(view->ssd);
|
||||||
|
struct wlr_box *view_geom =
|
||||||
|
use_pending ? &view->pending : &view->current;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When resizing along a given edge, limit the motion of that edge to
|
||||||
|
* any valid nearest edge in the corresponding direction.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (resize_edges & WLR_EDGE_LEFT) {
|
||||||
|
if (BOUNDED_INT(edges.left)) {
|
||||||
|
geom->x = edges.left + border.left + rc.gap;
|
||||||
|
geom->width = view_geom->width + view_geom->x - geom->x;
|
||||||
|
}
|
||||||
|
} else if (resize_edges & WLR_EDGE_RIGHT) {
|
||||||
|
if (BOUNDED_INT(edges.right)) {
|
||||||
|
geom->width = edges.right
|
||||||
|
- view_geom->x - border.right - rc.gap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resize_edges & WLR_EDGE_TOP) {
|
||||||
|
if (BOUNDED_INT(edges.top)) {
|
||||||
|
geom->y = edges.top + border.top + rc.gap;
|
||||||
|
geom->height = view_geom->height + view_geom->y - geom->y;
|
||||||
|
}
|
||||||
|
} else if (resize_edges & WLR_EDGE_BOTTOM) {
|
||||||
|
if (BOUNDED_INT(edges.bottom)) {
|
||||||
|
geom->height = edges.bottom
|
||||||
|
- view_geom->y - border.bottom - rc.gap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ labwc_sources = files(
|
||||||
'debug.c',
|
'debug.c',
|
||||||
'desktop.c',
|
'desktop.c',
|
||||||
'dnd.c',
|
'dnd.c',
|
||||||
|
'edges.c',
|
||||||
'foreign.c',
|
'foreign.c',
|
||||||
'idle.c',
|
'idle.c',
|
||||||
'interactive.c',
|
'interactive.c',
|
||||||
|
|
|
||||||
282
src/resistance.c
282
src/resistance.c
|
|
@ -4,164 +4,84 @@
|
||||||
#include "common/border.h"
|
#include "common/border.h"
|
||||||
#include "common/macros.h"
|
#include "common/macros.h"
|
||||||
#include "config/rcxml.h"
|
#include "config/rcxml.h"
|
||||||
|
#include "edges.h"
|
||||||
#include "labwc.h"
|
#include "labwc.h"
|
||||||
#include "resistance.h"
|
#include "resistance.h"
|
||||||
#include "view.h"
|
#include "view.h"
|
||||||
|
|
||||||
static void
|
static void
|
||||||
is_within_resistance_range(struct border view, struct border target,
|
check_edge(int *next, int current, int target,
|
||||||
struct border other, struct border *flags, int strength)
|
int oppose, int align, bool lesser, int tolerance)
|
||||||
{
|
{
|
||||||
if (view.left >= other.left) {
|
/* Ignore non-moving edges */
|
||||||
const int lo = other.left - abs(strength);
|
if (current == target) {
|
||||||
const int hi = other.left - MIN(strength, 0);
|
|
||||||
flags->left = target.left >= lo && target.left < hi;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!flags->left && view.right <= other.right) {
|
|
||||||
const int lo = other.right + MIN(strength, 0);
|
|
||||||
const int hi = other.right + abs(strength);
|
|
||||||
flags->right = target.right > lo && target.right <= hi;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (view.top >= other.top) {
|
|
||||||
const int lo = other.top - abs(strength);
|
|
||||||
const int hi = other.top - MIN(strength, 0);
|
|
||||||
flags->top = target.top >= lo && target.top < hi;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!flags->top && view.bottom <= other.bottom) {
|
|
||||||
const int lo = other.bottom + MIN(strength, 0);
|
|
||||||
const int hi = other.bottom + abs(strength);
|
|
||||||
flags->bottom = target.bottom > lo && target.bottom <= hi;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
build_view_edges(struct view *view, struct wlr_box new_geom,
|
|
||||||
struct border *view_edges, struct border *target_edges, bool move)
|
|
||||||
{
|
|
||||||
struct border border = ssd_get_margin(view->ssd);
|
|
||||||
|
|
||||||
/* Use the effective height to properly snap shaded views */
|
|
||||||
int eff_height = view_effective_height(view, /* use_pending */ false);
|
|
||||||
|
|
||||||
view_edges->left = view->current.x - border.left + (move ? 1 : 0);
|
|
||||||
view_edges->top = view->current.y - border.top + (move ? 1 : 0);
|
|
||||||
view_edges->right = view->current.x + view->current.width + border.right;
|
|
||||||
view_edges->bottom = view->current.y + eff_height + border.bottom;
|
|
||||||
|
|
||||||
target_edges->left = new_geom.x - border.left;
|
|
||||||
target_edges->top = new_geom.y - border.top;
|
|
||||||
target_edges->right = new_geom.x + new_geom.width + border.right;
|
|
||||||
target_edges->bottom = new_geom.y + border.bottom +
|
|
||||||
(view->shaded ? 0 : new_geom.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
update_nearest_edge(struct border view_edges, struct border target_edges,
|
|
||||||
struct border region_edges, int strength,
|
|
||||||
struct border *next_edges)
|
|
||||||
{
|
|
||||||
struct border flags = { 0 };
|
|
||||||
is_within_resistance_range(view_edges,
|
|
||||||
target_edges, region_edges, &flags, strength);
|
|
||||||
|
|
||||||
if (flags.left == 1) {
|
|
||||||
next_edges->left = MAX(region_edges.left, next_edges->left);
|
|
||||||
} else if (flags.right == 1) {
|
|
||||||
next_edges->right = MIN(region_edges.right, next_edges->right);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flags.top == 1) {
|
|
||||||
next_edges->top = MAX(region_edges.top, next_edges->top);
|
|
||||||
} else if (flags.bottom == 1) {
|
|
||||||
next_edges->bottom = MIN(region_edges.bottom, next_edges->bottom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
find_neighbor_edges(struct view *view, struct wlr_box new_geom,
|
|
||||||
struct border *next_edges, bool move)
|
|
||||||
{
|
|
||||||
if (rc.window_edge_strength == 0) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct border view_edges = { 0 };
|
/*
|
||||||
struct border target_edges = { 0 };
|
* 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 resist/attract zone (defined by tolerance) of
|
||||||
|
* that edge contains the target position of the moving edge.
|
||||||
|
*/
|
||||||
|
|
||||||
build_view_edges(view, new_geom, &view_edges, &target_edges, move);
|
/* Direction of motion for the edge */
|
||||||
|
const bool decreasing = target < current;
|
||||||
|
|
||||||
struct view *v;
|
/* Check the opposing edge */
|
||||||
for_each_view(v, &view->server->views, LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) {
|
bool valid = false;
|
||||||
if (v == view || !output_is_usable(v->output)) {
|
if (decreasing) {
|
||||||
continue;
|
const int lo = clipped_sub(oppose, abs(tolerance));
|
||||||
}
|
const int hi = clipped_sub(oppose, MIN(tolerance, 0));
|
||||||
|
valid = target >= lo && target < 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;
|
||||||
|
}
|
||||||
|
|
||||||
struct border border = ssd_get_margin(v->ssd);
|
if (valid) {
|
||||||
|
*next = edge_get_best(*next, oppose, decreasing);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/* Check the aligned edge */
|
||||||
* The significance of window edges here is inverted with
|
valid = false;
|
||||||
* respect to the usual orientation, because the edges of the
|
if (decreasing) {
|
||||||
* view v of interest are those that would be encountered by a
|
const int lo = clipped_sub(align, abs(tolerance));
|
||||||
* change in geometry in view along the named edge of view.
|
const int hi = clipped_sub(align, MIN(tolerance, 0));
|
||||||
* Hence, when moving or resizing view *left*, it is the
|
valid = target >= lo && target < hi;
|
||||||
* *right* edge of v that would be encountered, and vice versa;
|
} else {
|
||||||
* when moving or resizing view *down* ("bottom"), it is the
|
const int lo = clipped_add(align, MIN(tolerance, 0));
|
||||||
* *top* edge of v that would be encountered, and vice versa.
|
const int hi = clipped_add(align, abs(tolerance));
|
||||||
*/
|
valid = target > lo && target <= hi;
|
||||||
struct border win_edges = {
|
}
|
||||||
.top = v->current.y + border.bottom
|
|
||||||
+ view_effective_height(v, /* use_pending */ false),
|
|
||||||
.right = v->current.x - border.left,
|
|
||||||
.bottom = v->current.y - border.top,
|
|
||||||
.left = v->current.x + v->current.width + border.right,
|
|
||||||
};
|
|
||||||
|
|
||||||
update_nearest_edge(view_edges, target_edges,
|
if (valid) {
|
||||||
win_edges, rc.window_edge_strength, next_edges);
|
*next = edge_get_best(*next, align, decreasing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
find_screen_edges(struct view *view, struct wlr_box new_geom,
|
check_edge_output(int *next, int current, int target,
|
||||||
struct border *next_edges, bool move)
|
int oppose, int align, bool lesser)
|
||||||
{
|
{
|
||||||
if (rc.screen_edge_strength == 0) {
|
check_edge(next, current, target,
|
||||||
return;
|
oppose, align, lesser, rc.screen_edge_strength);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct border view_edges = { 0 };
|
static void
|
||||||
struct border target_edges = { 0 };
|
check_edge_window(int *next, int current, int target,
|
||||||
|
int oppose, int align, bool lesser)
|
||||||
build_view_edges(view, new_geom, &view_edges, &target_edges, move);
|
{
|
||||||
|
check_edge(next, current, target,
|
||||||
struct output *output;
|
oppose, align, lesser, rc.window_edge_strength);
|
||||||
wl_list_for_each(output, &view->server->outputs, link) {
|
|
||||||
if (!output_is_usable(output)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct wlr_box mgeom =
|
|
||||||
output_usable_area_in_layout_coords(output);
|
|
||||||
|
|
||||||
struct wlr_box ol;
|
|
||||||
if (!wlr_box_intersection(&ol, &view->current, &mgeom) &&
|
|
||||||
!wlr_box_intersection(&ol, &new_geom, &mgeom)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct border screen_edges = {
|
|
||||||
.top = mgeom.y,
|
|
||||||
.right = mgeom.x + mgeom.width,
|
|
||||||
.bottom = mgeom.y + mgeom.height,
|
|
||||||
.left = mgeom.x,
|
|
||||||
};
|
|
||||||
|
|
||||||
update_nearest_edge(view_edges, target_edges,
|
|
||||||
screen_edges, rc.screen_edge_strength, next_edges);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
@ -169,37 +89,34 @@ resistance_move_apply(struct view *view, double *x, double *y)
|
||||||
{
|
{
|
||||||
assert(view);
|
assert(view);
|
||||||
|
|
||||||
struct border border = ssd_get_margin(view->ssd);
|
struct border next_edges;
|
||||||
|
edges_initialize(&next_edges);
|
||||||
|
|
||||||
struct border next_edges = {
|
struct wlr_box target = {
|
||||||
.top = INT_MIN,
|
|
||||||
.right = INT_MAX,
|
|
||||||
.bottom = INT_MAX,
|
|
||||||
.left = INT_MIN,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct wlr_box new_geom = {
|
|
||||||
.x = *x,
|
.x = *x,
|
||||||
.y = *y,
|
.y = *y,
|
||||||
.width = view->current.width,
|
.width = view->current.width,
|
||||||
.height = view->current.height,
|
.height = view->current.height,
|
||||||
};
|
};
|
||||||
|
|
||||||
find_screen_edges(view, new_geom, &next_edges, /* move */ true);
|
if (rc.screen_edge_strength != 0) {
|
||||||
find_neighbor_edges(view, new_geom, &next_edges, /* move */ true);
|
/* Find any relevant output edges encountered by this move */
|
||||||
|
edges_find_outputs(&next_edges, view, target, NULL,
|
||||||
if (next_edges.left > INT_MIN) {
|
check_edge_output, /* use_pending */ false);
|
||||||
*x = next_edges.left + border.left;
|
|
||||||
} else if (next_edges.right < INT_MAX) {
|
|
||||||
*x = next_edges.right - view->current.width - border.right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (next_edges.top > INT_MIN) {
|
if (rc.window_edge_strength != 0) {
|
||||||
*y = next_edges.top + border.top;
|
/* Find any relevant window edges encountered by this move */
|
||||||
} else if (next_edges.bottom < INT_MAX) {
|
edges_find_neighbors(&next_edges, view, target, NULL,
|
||||||
*y = next_edges.bottom - border.bottom
|
check_edge_window, /* use_pending */ false);
|
||||||
- view_effective_height(view, /* use_pending */ false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If any "best" edges were encountered during this move, snap motion */
|
||||||
|
edges_adjust_move_coords(view, next_edges,
|
||||||
|
&target.x, &target.y, /* use_pending */ false);
|
||||||
|
|
||||||
|
*x = target.x;
|
||||||
|
*y = target.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
@ -208,41 +125,22 @@ resistance_resize_apply(struct view *view, struct wlr_box *new_geom)
|
||||||
assert(view);
|
assert(view);
|
||||||
assert(!view->shaded);
|
assert(!view->shaded);
|
||||||
|
|
||||||
struct border border = ssd_get_margin(view->ssd);
|
struct border next_edges;
|
||||||
|
edges_initialize(&next_edges);
|
||||||
|
|
||||||
struct border next_edges = {
|
if (rc.screen_edge_strength != 0) {
|
||||||
.top = INT_MIN,
|
/* Find any relevant output edges encountered by this move */
|
||||||
.right = INT_MAX,
|
edges_find_outputs(&next_edges, view, *new_geom, NULL,
|
||||||
.bottom = INT_MAX,
|
check_edge_output, /* use_pending */ false);
|
||||||
.left = INT_MIN,
|
|
||||||
};
|
|
||||||
|
|
||||||
find_screen_edges(view, *new_geom, &next_edges, /* move */ false);
|
|
||||||
find_neighbor_edges(view, *new_geom, &next_edges, /* move */ false);
|
|
||||||
|
|
||||||
if (view->server->resize_edges & WLR_EDGE_LEFT) {
|
|
||||||
if (next_edges.left > INT_MIN) {
|
|
||||||
new_geom->x = next_edges.left + border.left;
|
|
||||||
new_geom->width = view->current.width
|
|
||||||
+ view->current.x - new_geom->x;
|
|
||||||
}
|
|
||||||
} else if (view->server->resize_edges & WLR_EDGE_RIGHT) {
|
|
||||||
if (next_edges.right < INT_MAX) {
|
|
||||||
new_geom->width = next_edges.right
|
|
||||||
- view->current.x - border.right;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (view->server->resize_edges & WLR_EDGE_TOP) {
|
if (rc.window_edge_strength != 0) {
|
||||||
if (next_edges.top > INT_MIN) {
|
/* Find any relevant window edges encountered by this move */
|
||||||
new_geom->y = next_edges.top + border.top;
|
edges_find_neighbors(&next_edges, view, *new_geom, NULL,
|
||||||
new_geom->height = view->current.height
|
check_edge_window, /* use_pending */ false);
|
||||||
+ view->current.y - new_geom->y;
|
|
||||||
}
|
|
||||||
} else if (view->server->resize_edges & WLR_EDGE_BOTTOM) {
|
|
||||||
if (next_edges.bottom < INT_MAX) {
|
|
||||||
new_geom->height = next_edges.bottom
|
|
||||||
- view->current.y - border.bottom;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If any "best" edges were encountered during this move, snap motion */
|
||||||
|
edges_adjust_resize_geom(view, next_edges,
|
||||||
|
view->server->resize_edges, new_geom, /* use_pending */ false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
474
src/snap.c
474
src/snap.c
|
|
@ -1,285 +1,259 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0-only
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
#include <strings.h>
|
#include <assert.h>
|
||||||
|
#include <limits.h>
|
||||||
#include <wlr/util/box.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 "labwc.h"
|
||||||
|
#include "resistance.h"
|
||||||
#include "snap.h"
|
#include "snap.h"
|
||||||
#include "view.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
|
static void
|
||||||
_snap_move_resize_to_edge(struct view *view, enum view_edge direction, enum snap_mode mode,
|
check_edge(int *next, int current, int target, int oppose, int align, bool lesser)
|
||||||
struct wlr_box *delta)
|
|
||||||
{
|
{
|
||||||
struct border edge = snap_get_view_edge(view);
|
if (current == target) {
|
||||||
struct border dmax;
|
return;
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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) {
|
switch (direction) {
|
||||||
case VIEW_EDGE_LEFT:
|
case VIEW_EDGE_LEFT:
|
||||||
if (mode == SNAP_MODE_MOVE) {
|
target.x = usable.x + ssd.left + rc.gap;
|
||||||
delta->x += max(
|
if (target.x >= view->pending.x) {
|
||||||
/* left edge to left/right edges */
|
return;
|
||||||
_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;
|
break;
|
||||||
case VIEW_EDGE_RIGHT:
|
case VIEW_EDGE_RIGHT:
|
||||||
if (mode == SNAP_MODE_MOVE) {
|
target.x = usable.x + usable.width
|
||||||
delta->x += min3(
|
- rc.gap - target.width - ssd.right;
|
||||||
/* left edge to left/right edges */
|
if (target.x <= view->pending.x) {
|
||||||
_snap_next_edge(view, edge.left, near_right, dmax.right, 0),
|
return;
|
||||||
_snap_next_edge(view, edge.left, far_right, dmax.right, rc.gap),
|
}
|
||||||
/* right edge to left edge */
|
break;
|
||||||
_snap_next_edge(view, edge.right, near_right, dmax.right, -rc.gap)
|
case VIEW_EDGE_UP:
|
||||||
);
|
target.y = usable.y + ssd.top + rc.gap;
|
||||||
} else if (mode == SNAP_MODE_GROW) {
|
if (target.y >= view->pending.y) {
|
||||||
delta->width += min(
|
return;
|
||||||
/* 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;
|
break;
|
||||||
case VIEW_EDGE_DOWN:
|
case VIEW_EDGE_DOWN:
|
||||||
if (mode == SNAP_MODE_MOVE) {
|
target.y = usable.y + usable.height - rc.gap - ssd.bottom
|
||||||
delta->y += min3(
|
- view_effective_height(view, /* use_pending */ true);
|
||||||
/* top edge to top/bottom edges */
|
if (target.y <= view->pending.y) {
|
||||||
_snap_next_edge(view, edge.top, near_down, dmax.bottom, 0),
|
return;
|
||||||
_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;
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void
|
/*
|
||||||
snap_vector_to_next_edge(struct view *view, enum view_edge direction, int *dx, int *dy)
|
* 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
|
||||||
struct wlr_box delta = {0};
|
* snapping to view is desired, check for snapping against any view on
|
||||||
_snap_move_resize_to_edge(view, direction, SNAP_MODE_MOVE, &delta);
|
* the same output.
|
||||||
*dx = delta.x;
|
*/
|
||||||
*dy = delta.y;
|
if (snap_to_windows) {
|
||||||
}
|
struct border next_edges;
|
||||||
|
edges_initialize(&next_edges);
|
||||||
|
|
||||||
int
|
edges_find_neighbors(&next_edges, view, target,
|
||||||
snap_distance_to_next_edge(struct view *view, enum view_edge direction)
|
output, check_edge, /* use_pending */ true);
|
||||||
{
|
|
||||||
struct wlr_box delta = {0};
|
/* If any "best" edges were encountered, limit motion */
|
||||||
_snap_move_resize_to_edge(view, direction, SNAP_MODE_MOVE, &delta);
|
edges_adjust_move_coords(view, next_edges,
|
||||||
switch (direction) {
|
&target.x, &target.y, /* use_pending */ true);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*dx = target.x - view->pending.x;
|
||||||
|
*dy = target.y - view->pending.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
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
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
24
src/view.c
24
src/view.c
|
|
@ -1550,27 +1550,7 @@ view_move_to_edge(struct view *view, enum view_edge direction, bool snap_to_wind
|
||||||
}
|
}
|
||||||
|
|
||||||
int dx = 0, dy = 0;
|
int dx = 0, dy = 0;
|
||||||
if (snap_to_windows) {
|
snap_move_to_edge(view, direction, snap_to_windows, &dx, &dy);
|
||||||
snap_vector_to_next_edge(view, direction, &dx, &dy);
|
|
||||||
} else {
|
|
||||||
struct border distance = snap_get_max_distance(view);
|
|
||||||
switch (direction) {
|
|
||||||
case VIEW_EDGE_LEFT:
|
|
||||||
dx = distance.left;
|
|
||||||
break;
|
|
||||||
case VIEW_EDGE_UP:
|
|
||||||
dy = distance.top;
|
|
||||||
break;
|
|
||||||
case VIEW_EDGE_RIGHT:
|
|
||||||
dx = distance.right;
|
|
||||||
break;
|
|
||||||
case VIEW_EDGE_DOWN:
|
|
||||||
dy = distance.bottom;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dx != 0 || dy != 0) {
|
if (dx != 0 || dy != 0) {
|
||||||
/* Move the window if a change was discovered */
|
/* Move the window if a change was discovered */
|
||||||
|
|
@ -1665,7 +1645,7 @@ view_grow_to_edge(struct view *view, enum view_edge direction)
|
||||||
|
|
||||||
view_set_shade(view, false);
|
view_set_shade(view, false);
|
||||||
|
|
||||||
struct wlr_box geo = view->pending;
|
struct wlr_box geo;
|
||||||
snap_grow_to_next_edge(view, direction, &geo);
|
snap_grow_to_next_edge(view, direction, &geo);
|
||||||
view_move_resize(view, geo);
|
view_move_resize(view, geo);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue