mirror of
https://github.com/labwc/labwc.git
synced 2025-10-29 05:40:24 -04:00
snap: cache and ignore last-snapped edge when growing or shrinking
When growing or shrinking a view by snapping to an edge, a client may ignore the requested size and instead keep its original size or substitute a different (possibly constrained) size. In this case, the view may not actually contact the snapped edge, and a subsequent snap attempt will just keep re-trying (and failing) to contact the same ege. To mitigate this, remember the last-snapped view, snapping direction and offset of the snapping edge in snap.c; when re-attempting a snap for the same view in the same direction, ignore the edge that was last "hit", to allow snapping to progress beyond the problematic edge.
This commit is contained in:
parent
9de487cecf
commit
2bf285a2c6
11 changed files with 271 additions and 44 deletions
|
|
@ -105,12 +105,12 @@ 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, bool ignore_hidden);
|
||||
struct wlr_box origin, struct wlr_box target,
|
||||
struct output *output, edge_validator_t validator, bool ignore_hidden);
|
||||
|
||||
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);
|
||||
struct wlr_box origin, struct wlr_box target,
|
||||
struct output *output, edge_validator_t validator);
|
||||
|
||||
void edges_adjust_move_coords(struct view *view, struct border edges,
|
||||
int *x, int *y, bool use_pending);
|
||||
|
|
|
|||
20
include/snap-constraints.h
Normal file
20
include/snap-constraints.h
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
#ifndef LABWC_SNAP_CONSTRAINTS_H
|
||||
#define LABWC_SNAP_CONSTRAINTS_H
|
||||
|
||||
#include "common/border.h"
|
||||
#include "view.h"
|
||||
|
||||
struct wlr_box;
|
||||
|
||||
void snap_constraints_set(struct view *view,
|
||||
enum view_edge direction, struct wlr_box geom);
|
||||
|
||||
void snap_constraints_invalidate(struct view *view);
|
||||
|
||||
void snap_constraints_update(struct view *view);
|
||||
|
||||
struct wlr_box snap_constraints_effective(struct view *view,
|
||||
enum view_edge direction);
|
||||
|
||||
#endif /* LABWC_SNAP_CONSTRAINTS_H */
|
||||
|
|
@ -16,4 +16,7 @@ void snap_grow_to_next_edge(struct view *view,
|
|||
void snap_shrink_to_next_edge(struct view *view,
|
||||
enum view_edge direction, struct wlr_box *geo);
|
||||
|
||||
void snap_invalidate_edge_cache(struct view *view);
|
||||
void snap_update_cache_geometry(struct view *view);
|
||||
|
||||
#endif /* LABWC_SNAP_H */
|
||||
|
|
|
|||
20
src/edges.c
20
src/edges.c
|
|
@ -376,8 +376,8 @@ edges_calculate_visibility(struct server *server, struct view *ignored_view)
|
|||
|
||||
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, bool ignore_hidden)
|
||||
struct wlr_box origin, struct wlr_box target,
|
||||
struct output *output, edge_validator_t validator, bool ignore_hidden)
|
||||
{
|
||||
assert(view);
|
||||
assert(validator);
|
||||
|
|
@ -391,10 +391,7 @@ edges_find_neighbors(struct border *nearest_edges, struct view *view,
|
|||
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(&view_edges, view, origin);
|
||||
edges_for_target_geometry(&target_edges, view, target);
|
||||
|
||||
struct view *v;
|
||||
|
|
@ -437,8 +434,8 @@ edges_find_neighbors(struct border *nearest_edges, struct view *view,
|
|||
|
||||
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)
|
||||
struct wlr_box origin, struct wlr_box target,
|
||||
struct output *output, edge_validator_t validator)
|
||||
{
|
||||
assert(view);
|
||||
assert(validator);
|
||||
|
|
@ -453,10 +450,7 @@ edges_find_outputs(struct border *nearest_edges, struct view *view,
|
|||
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(&view_edges, view, origin);
|
||||
edges_for_target_geometry(&target_edges, view, target);
|
||||
|
||||
struct output *o;
|
||||
|
|
@ -473,7 +467,7 @@ edges_find_outputs(struct border *nearest_edges, struct view *view,
|
|||
output_usable_area_in_layout_coords(o);
|
||||
|
||||
struct wlr_box ol;
|
||||
if (!wlr_box_intersection(&ol, view_geom, &usable) &&
|
||||
if (!wlr_box_intersection(&ol, &origin, &usable) &&
|
||||
!wlr_box_intersection(&ol, &target, &usable)) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "labwc.h"
|
||||
#include "regions.h"
|
||||
#include "resize_indicator.h"
|
||||
#include "snap.h"
|
||||
#include "view.h"
|
||||
#include "window-rules.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ labwc_sources = files(
|
|||
'seat.c',
|
||||
'server.c',
|
||||
'session-lock.c',
|
||||
'snap-constraints.c',
|
||||
'snap.c',
|
||||
'tearing.c',
|
||||
'theme.c',
|
||||
|
|
|
|||
|
|
@ -108,15 +108,14 @@ resistance_move_apply(struct view *view, double *x, double *y)
|
|||
|
||||
if (rc.screen_edge_strength != 0) {
|
||||
/* Find any relevant output edges encountered by this move */
|
||||
edges_find_outputs(&next_edges, view, target, NULL,
|
||||
check_edge_output, /* use_pending */ false);
|
||||
edges_find_outputs(&next_edges, view,
|
||||
view->current, target, NULL, check_edge_output);
|
||||
}
|
||||
|
||||
if (rc.window_edge_strength != 0) {
|
||||
/* Find any relevant window edges encountered by this move */
|
||||
edges_find_neighbors(&next_edges,
|
||||
view, target, NULL, check_edge_window,
|
||||
/* use_pending */ false, /* ignore_hidden */ true);
|
||||
edges_find_neighbors(&next_edges, view, view->current, target,
|
||||
NULL, check_edge_window, /* ignore_hidden */ true);
|
||||
}
|
||||
|
||||
/* If any "best" edges were encountered during this move, snap motion */
|
||||
|
|
@ -138,15 +137,14 @@ resistance_resize_apply(struct view *view, struct wlr_box *new_geom)
|
|||
|
||||
if (rc.screen_edge_strength != 0) {
|
||||
/* Find any relevant output edges encountered by this move */
|
||||
edges_find_outputs(&next_edges, view, *new_geom, NULL,
|
||||
check_edge_output, /* use_pending */ false);
|
||||
edges_find_outputs(&next_edges, view,
|
||||
view->current, *new_geom, NULL, check_edge_output);
|
||||
}
|
||||
|
||||
if (rc.window_edge_strength != 0) {
|
||||
/* Find any relevant window edges encountered by this move */
|
||||
edges_find_neighbors(&next_edges,
|
||||
view, *new_geom, NULL, check_edge_window,
|
||||
/* use_pending */ false, /* ignore_hidden */ true);
|
||||
edges_find_neighbors(&next_edges, view, view->current, *new_geom,
|
||||
NULL, check_edge_window, /* ignore_hidden */ true);
|
||||
}
|
||||
|
||||
/* If any "best" edges were encountered during this move, snap motion */
|
||||
|
|
|
|||
189
src/snap-constraints.c
Normal file
189
src/snap-constraints.c
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
#include <assert.h>
|
||||
#include <wlr/util/box.h>
|
||||
#include "common/macros.h"
|
||||
#include "labwc.h"
|
||||
#include "snap-constraints.h"
|
||||
#include "view.h"
|
||||
|
||||
/*
|
||||
* When snapping to edges during a resize action, XDG clients can override the
|
||||
* desired size to honor arbitrary sizing constraints (*e.g.*, honoring some
|
||||
* aspect ratio or ensuring that a terminal window consists of an integer
|
||||
* number of character cells). In that case, the final position of the view
|
||||
* edge may fall short of the edge to which it's snapped.
|
||||
*
|
||||
* This can prevent a subsequent resize action in the same direction from ever
|
||||
* crossing the "missed" edge, because the next action will keep trying to hit
|
||||
* the edge and the client will overriding the desired size. To compensate,
|
||||
* every snapped resize will update a "last snapped" cache, recording the
|
||||
* view that was resized, the position (and orientation) of the resized edge,
|
||||
* and the expected geometry resulting from the snapped resize. At first, the
|
||||
* expected geometry is the "pending" geometry that will be sent in a configure
|
||||
* request. However, if the client overrides this pending geometry with some
|
||||
* other, constrained value, the expectation should be updated (only once!) to
|
||||
* reflect the size that the client chooses to honor.
|
||||
*
|
||||
* In subsequent snapped resize actions, if:
|
||||
*
|
||||
* 1. The view is the same as in the last attemped snapped resize;
|
||||
* 2. The direction of resizing is the same as in the last attempt; and
|
||||
* 3. The geometry of the view matches that expected from the last attempt;
|
||||
*
|
||||
* then the view geometry will be modified to reflect the *original* intended
|
||||
* geometry from last snapped resize, which will allow the current attempt to
|
||||
* progress beyond the "sticky" edge.
|
||||
*/
|
||||
static struct {
|
||||
struct view *view;
|
||||
bool pending;
|
||||
int offset;
|
||||
enum view_edge direction;
|
||||
struct wlr_box geom;
|
||||
} last_snap_hit;
|
||||
|
||||
static void
|
||||
snap_constraints_reset(void)
|
||||
{
|
||||
last_snap_hit.view = NULL;
|
||||
last_snap_hit.pending = false;
|
||||
last_snap_hit.offset = INT_MIN;
|
||||
last_snap_hit.direction = VIEW_EDGE_INVALID;
|
||||
memset(&last_snap_hit.geom, 0, sizeof(last_snap_hit.geom));
|
||||
}
|
||||
|
||||
static bool
|
||||
snap_constraints_are_valid(struct view *view, enum view_edge direction)
|
||||
{
|
||||
assert(view);
|
||||
|
||||
/* Cache is not valid if view has changed */
|
||||
if (view != last_snap_hit.view) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Cache is not valid if direction has changed */
|
||||
if (direction != last_snap_hit.direction) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Cache is not valid if offset is unbounded */
|
||||
if (!BOUNDED_INT(last_snap_hit.offset)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Cache is valid iff pending view geometry matches expectation */
|
||||
return wlr_box_equal(&view->pending, &last_snap_hit.geom);
|
||||
}
|
||||
|
||||
void
|
||||
snap_constraints_set(struct view *view,
|
||||
enum view_edge direction, struct wlr_box geom)
|
||||
{
|
||||
assert(view);
|
||||
|
||||
int offset = INT_MIN;
|
||||
switch (direction) {
|
||||
case VIEW_EDGE_LEFT:
|
||||
offset = geom.x;
|
||||
break;
|
||||
case VIEW_EDGE_RIGHT:
|
||||
offset = geom.x + geom.width;
|
||||
break;
|
||||
case VIEW_EDGE_UP:
|
||||
offset = geom.y;
|
||||
break;
|
||||
case VIEW_EDGE_DOWN:
|
||||
offset = geom.y + geom.height;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!BOUNDED_INT(offset)) {
|
||||
snap_constraints_reset();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Capture the current geometry and effective snapped edge */
|
||||
last_snap_hit.view = view;
|
||||
last_snap_hit.offset = offset;
|
||||
last_snap_hit.direction = direction;
|
||||
last_snap_hit.geom = geom;
|
||||
|
||||
/*
|
||||
* Client geometry change is pending, and XDG clients may adjust the
|
||||
* geometry to match arbitrary constraints. Allow the client to update
|
||||
* our concept of constraints exactly once after the configure request.
|
||||
*/
|
||||
last_snap_hit.pending = true;
|
||||
}
|
||||
|
||||
void
|
||||
snap_constraints_invalidate(struct view *view)
|
||||
{
|
||||
if (view == last_snap_hit.view) {
|
||||
snap_constraints_reset();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
snap_constraints_update(struct view *view)
|
||||
{
|
||||
assert(view);
|
||||
|
||||
/* Ignore update if this is the wrong view */
|
||||
if (view != last_snap_hit.view) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Never update constraints more than once */
|
||||
if (!last_snap_hit.pending) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Only update constraints when view geometry matches expectation */
|
||||
if (!wlr_box_equal(&view->pending, &last_snap_hit.geom)) {
|
||||
return;
|
||||
}
|
||||
|
||||
last_snap_hit.geom = view->current;
|
||||
last_snap_hit.pending = false;
|
||||
}
|
||||
|
||||
struct wlr_box
|
||||
snap_constraints_effective(struct view *view, enum view_edge direction)
|
||||
{
|
||||
assert(view);
|
||||
|
||||
/* Use actual geometry when constraints do not apply */
|
||||
if (!snap_constraints_are_valid(view, direction)) {
|
||||
return view->pending;
|
||||
}
|
||||
|
||||
/* Override changing edge with constrained value */
|
||||
struct wlr_box geom = view->pending;
|
||||
switch (last_snap_hit.direction) {
|
||||
case VIEW_EDGE_LEFT:
|
||||
geom.x = last_snap_hit.offset;
|
||||
break;
|
||||
case VIEW_EDGE_RIGHT:
|
||||
geom.width = last_snap_hit.offset - geom.x;
|
||||
break;
|
||||
case VIEW_EDGE_UP:
|
||||
geom.y = last_snap_hit.offset;
|
||||
break;
|
||||
case VIEW_EDGE_DOWN:
|
||||
geom.height = last_snap_hit.offset - geom.y;
|
||||
break;
|
||||
default:
|
||||
return view->pending;
|
||||
}
|
||||
|
||||
/* Fall back to actual geometry when constrained geometry is nonsense */
|
||||
if (geom.width <= 0 || geom.height <= 0) {
|
||||
return view->pending;
|
||||
}
|
||||
|
||||
return geom;
|
||||
}
|
||||
49
src/snap.c
49
src/snap.c
|
|
@ -4,10 +4,9 @@
|
|||
#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-constraints.h"
|
||||
#include "snap.h"
|
||||
#include "view.h"
|
||||
|
||||
|
|
@ -121,9 +120,8 @@ snap_move_to_edge(struct view *view, enum view_edge direction,
|
|||
struct border next_edges;
|
||||
edges_initialize(&next_edges);
|
||||
|
||||
edges_find_neighbors(&next_edges,
|
||||
view, target, output, check_edge,
|
||||
/* use_pending */ true, /* ignore_hidden */ false);
|
||||
edges_find_neighbors(&next_edges, view, view->pending, target,
|
||||
output, check_edge, /* ignore_hidden */ false);
|
||||
|
||||
/* If any "best" edges were encountered, limit motion */
|
||||
edges_adjust_move_coords(view, next_edges,
|
||||
|
|
@ -135,8 +133,8 @@ snap_move_to_edge(struct view *view, enum view_edge direction,
|
|||
}
|
||||
|
||||
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)
|
||||
{
|
||||
assert(view);
|
||||
assert(!view->shaded);
|
||||
|
|
@ -196,17 +194,26 @@ snap_grow_to_next_edge(struct view *view, enum view_edge direction,
|
|||
struct border next_edges;
|
||||
edges_initialize(&next_edges);
|
||||
|
||||
/* Use a constrained, effective geometry for snapping if appropriate */
|
||||
struct wlr_box origin = snap_constraints_effective(view, direction);
|
||||
|
||||
/* 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, /* ignore_hidden */ false);
|
||||
edges_find_neighbors(&next_edges, view, origin, *geo,
|
||||
output, check_edge, /* ignore_hidden */ false);
|
||||
|
||||
edges_adjust_resize_geom(view, next_edges,
|
||||
resize_edges, geo, /* use_pending */ true);
|
||||
|
||||
/*
|
||||
* Record effective geometry after snapping in case the client opts to
|
||||
* ignore or modify the configured geometry
|
||||
*/
|
||||
snap_constraints_set(view, direction, *geo);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
assert(view);
|
||||
assert(!view->shaded);
|
||||
|
|
@ -252,15 +259,23 @@ snap_shrink_to_next_edge(struct view *view, enum view_edge direction,
|
|||
struct border next_edges;
|
||||
edges_initialize(&next_edges);
|
||||
|
||||
/* Use a constrained, effective geometry for snapping if appropriate */
|
||||
struct wlr_box origin = snap_constraints_effective(view, direction);
|
||||
|
||||
/* 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);
|
||||
edges_find_outputs(&next_edges, view,
|
||||
origin, *geo, view->output, check_edge);
|
||||
|
||||
/* 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, /* ignore_hidden */ false);
|
||||
edges_find_neighbors(&next_edges, view, origin, *geo,
|
||||
view->output, check_edge, /* ignore_hidden */ false);
|
||||
|
||||
edges_adjust_resize_geom(view, next_edges,
|
||||
resize_edges, geo, /* use_pending */ true);
|
||||
|
||||
/*
|
||||
* Record effective geometry after snapping in case the client opts to
|
||||
* ignore or modify the configured geometry
|
||||
*/
|
||||
snap_constraints_set(view, direction, *geo);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include "placement.h"
|
||||
#include "regions.h"
|
||||
#include "resize_indicator.h"
|
||||
#include "snap-constraints.h"
|
||||
#include "snap.h"
|
||||
#include "ssd.h"
|
||||
#include "view.h"
|
||||
|
|
@ -2203,6 +2204,8 @@ view_destroy(struct view *view)
|
|||
assert(view);
|
||||
struct server *server = view->server;
|
||||
|
||||
snap_constraints_invalidate(view);
|
||||
|
||||
if (view->mappable.connected) {
|
||||
mappable_disconnect(&view->mappable);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include "decorations.h"
|
||||
#include "labwc.h"
|
||||
#include "node.h"
|
||||
#include "snap-constraints.h"
|
||||
#include "view.h"
|
||||
#include "view-impl-common.h"
|
||||
#include "window-rules.h"
|
||||
|
|
@ -141,6 +142,7 @@ handle_commit(struct wl_listener *listener, void *data)
|
|||
* actual view.
|
||||
*/
|
||||
if (!view->pending_configure_serial) {
|
||||
snap_constraints_update(view);
|
||||
view->pending = view->current;
|
||||
|
||||
/*
|
||||
|
|
@ -188,6 +190,7 @@ handle_configure_timeout(void *data)
|
|||
view->current.width, view->current.height);
|
||||
|
||||
/* Re-sync pending view with current state */
|
||||
snap_constraints_update(view);
|
||||
view->pending = view->current;
|
||||
|
||||
return 0; /* ignored per wl_event_loop docs */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue