mirror of
https://github.com/labwc/labwc.git
synced 2026-05-03 06:47:22 -04:00
Some checks failed
labwc.github.io / notify (push) Has been cancelled
Modifier+right-drag resize was silently ignored on fully maximized views because of an early-return guard in interactive_begin(). The axis-specific un-maximization logic introduced in #3043 already handles partial maximization correctly; extend that to the VIEW_AXIS_BOTH case so both axes are cleared while keeping the current geometry as the starting point of the resize. Move already permits dragging maximized views, so this also removes an asymmetry between the move and resize paths. Fixes: #3524
368 lines
9.4 KiB
C
368 lines
9.4 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
#include <assert.h>
|
|
#include <wlr/types/wlr_cursor.h>
|
|
#include "config/rcxml.h"
|
|
#include "edges.h"
|
|
#include "input/keyboard.h"
|
|
#include "labwc.h"
|
|
#include "output.h"
|
|
#include "regions.h"
|
|
#include "resize-indicator.h"
|
|
#include "view.h"
|
|
#include "window-rules.h"
|
|
|
|
/*
|
|
* pos_old pos_cursor
|
|
* v v
|
|
* +---------+-------------------+
|
|
* <-----------size_old---------->
|
|
*
|
|
* return value
|
|
* v
|
|
* +----+---------+
|
|
* <---size_new--->
|
|
*/
|
|
static int
|
|
max_move_scale(double pos_cursor, double pos_old, double size_old,
|
|
double size_new)
|
|
{
|
|
double anchor_frac = (pos_cursor - pos_old) / size_old;
|
|
int pos_new = pos_cursor - (size_new * anchor_frac);
|
|
if (pos_new < pos_old) {
|
|
/* Clamp by using the old offsets of the maximized window */
|
|
pos_new = pos_old;
|
|
}
|
|
return pos_new;
|
|
}
|
|
|
|
void
|
|
interactive_anchor_to_cursor(struct wlr_box *geo)
|
|
{
|
|
assert(server.input_mode == LAB_INPUT_STATE_MOVE);
|
|
if (wlr_box_empty(geo)) {
|
|
return;
|
|
}
|
|
/* Resize grab_box while anchoring it to grab_{x,y} */
|
|
server.grab_box.x = max_move_scale(server.grab_x, server.grab_box.x,
|
|
server.grab_box.width, geo->width);
|
|
server.grab_box.y = max_move_scale(server.grab_y, server.grab_box.y,
|
|
server.grab_box.height, geo->height);
|
|
server.grab_box.width = geo->width;
|
|
server.grab_box.height = geo->height;
|
|
|
|
geo->x = server.grab_box.x + (server.seat.cursor->x - server.grab_x);
|
|
geo->y = server.grab_box.y + (server.seat.cursor->y - server.grab_y);
|
|
}
|
|
|
|
/*
|
|
* Called before interactive_begin() to set the initial grab parameters
|
|
* (cursor position and view geometry). Once the cursor actually moves,
|
|
* then interactive_begin() is called.
|
|
*/
|
|
void
|
|
interactive_set_grab_context(const struct cursor_context *ctx)
|
|
{
|
|
if (!ctx->view) {
|
|
return;
|
|
}
|
|
if (server.input_mode != LAB_INPUT_STATE_PASSTHROUGH) {
|
|
return;
|
|
}
|
|
|
|
server.grabbed_view = ctx->view;
|
|
server.grab_x = server.seat.cursor->x;
|
|
server.grab_y = server.seat.cursor->y;
|
|
server.grab_box = ctx->view->current;
|
|
server.resize_edges =
|
|
cursor_get_resize_edges(server.seat.cursor, ctx);
|
|
}
|
|
|
|
void
|
|
interactive_begin(struct view *view, enum input_mode mode, enum lab_edge edges)
|
|
{
|
|
assert(view);
|
|
/*
|
|
* This function sets up an interactive move or resize operation, where
|
|
* the compositor stops propagating pointer events to clients and
|
|
* instead consumes them itself, to move or resize windows.
|
|
*/
|
|
struct seat *seat = &server.seat;
|
|
|
|
if (server.input_mode != LAB_INPUT_STATE_PASSTHROUGH
|
|
|| view != server.grabbed_view) {
|
|
return;
|
|
}
|
|
|
|
/* Prevent moving/resizing fixed-position and panel-like views */
|
|
if (window_rules_get_property(view, "fixedPosition") == LAB_PROP_TRUE
|
|
|| view_has_strut_partial(view)) {
|
|
return;
|
|
}
|
|
|
|
enum lab_cursors cursor_shape = LAB_CURSOR_DEFAULT;
|
|
|
|
switch (mode) {
|
|
case LAB_INPUT_STATE_MOVE:
|
|
if (view->fullscreen) {
|
|
/**
|
|
* We don't allow moving fullscreen windows.
|
|
*
|
|
* If you think there is a good reason to allow
|
|
* it, feel free to open an issue explaining
|
|
* your use-case.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
/* Store natural geometry at start of move */
|
|
view_store_natural_geometry(view);
|
|
|
|
/* Prevent region snapping when just moving via A-Left mousebind */
|
|
seat->region_prevent_snap = keyboard_get_all_modifiers(seat);
|
|
|
|
cursor_shape = LAB_CURSOR_GRAB;
|
|
break;
|
|
case LAB_INPUT_STATE_RESIZE: {
|
|
if (view->shaded || view->fullscreen) {
|
|
/*
|
|
* We don't allow resizing while shaded or fullscreen.
|
|
* Maximized views are handled below by un-maximizing
|
|
* the axes being resized while keeping the current
|
|
* geometry as the starting point.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Override resize edges if specified explicitly.
|
|
* Otherwise, they were set already from cursor context.
|
|
*/
|
|
if (edges != LAB_EDGE_NONE) {
|
|
server.resize_edges = edges;
|
|
}
|
|
|
|
/*
|
|
* If tiled or maximized, reset tiled state and un-maximize
|
|
* the axes that are being resized, but keep the same
|
|
* geometry as the starting point.
|
|
*/
|
|
enum view_axis maximized = view->maximized;
|
|
if (server.resize_edges & LAB_EDGES_LEFT_RIGHT) {
|
|
maximized &= ~VIEW_AXIS_HORIZONTAL;
|
|
}
|
|
if (server.resize_edges & LAB_EDGES_TOP_BOTTOM) {
|
|
maximized &= ~VIEW_AXIS_VERTICAL;
|
|
}
|
|
view_set_maximized(view, maximized);
|
|
view_set_untiled(view);
|
|
cursor_shape = cursor_get_from_edge(server.resize_edges);
|
|
break;
|
|
}
|
|
default:
|
|
/* Should not be reached */
|
|
return;
|
|
}
|
|
|
|
seat_focus_override_begin(seat, mode, cursor_shape);
|
|
|
|
/*
|
|
* Un-tile maximized/tiled view immediately if <unSnapThreshold> is
|
|
* zero. Otherwise, un-tile it later in cursor motion handler.
|
|
* If the natural geometry is unknown (possible with xdg-shell views),
|
|
* then we set a size of 0x0 here and determine the correct geometry
|
|
* later. See do_late_positioning() in xdg.c.
|
|
*/
|
|
if (mode == LAB_INPUT_STATE_MOVE && !view_is_floating(view)
|
|
&& rc.unsnap_threshold <= 0) {
|
|
struct wlr_box natural_geo = view->natural_geometry;
|
|
interactive_anchor_to_cursor(&natural_geo);
|
|
/* Shaded clients will not process resize events until unshaded */
|
|
view_set_shade(view, false);
|
|
view_set_maximized(view, VIEW_AXIS_NONE);
|
|
view_set_untiled(view);
|
|
view_move_resize(view, natural_geo);
|
|
}
|
|
|
|
if (rc.resize_indicator) {
|
|
resize_indicator_show(view);
|
|
}
|
|
if (rc.window_edge_strength) {
|
|
edges_calculate_visibility(view);
|
|
}
|
|
}
|
|
|
|
bool
|
|
edge_from_cursor(struct seat *seat, struct output **dest_output,
|
|
enum lab_edge *edge1, enum lab_edge *edge2)
|
|
{
|
|
*dest_output = NULL;
|
|
*edge1 = LAB_EDGE_NONE;
|
|
*edge2 = LAB_EDGE_NONE;
|
|
|
|
if (!view_is_floating(server.grabbed_view)) {
|
|
return false;
|
|
}
|
|
|
|
if (rc.snap_edge_range_inner == 0 && rc.snap_edge_range_outer == 0) {
|
|
return false;
|
|
}
|
|
|
|
struct output *output = output_nearest_to_cursor();
|
|
if (!output_is_usable(output)) {
|
|
wlr_log(WLR_ERROR, "output at cursor is unusable");
|
|
return false;
|
|
}
|
|
*dest_output = output;
|
|
|
|
double cursor_x = seat->cursor->x;
|
|
double cursor_y = seat->cursor->y;
|
|
|
|
int top_range = rc.snap_edge_range_outer;
|
|
int bottom_range = rc.snap_edge_range_outer;
|
|
int left_range = rc.snap_edge_range_outer;
|
|
int right_range = rc.snap_edge_range_outer;
|
|
if (wlr_output_layout_adjacent_output(server.output_layout, WLR_DIRECTION_UP,
|
|
output->wlr_output, cursor_x, cursor_y)) {
|
|
top_range = rc.snap_edge_range_inner;
|
|
}
|
|
if (wlr_output_layout_adjacent_output(server.output_layout, WLR_DIRECTION_DOWN,
|
|
output->wlr_output, cursor_x, cursor_y)) {
|
|
bottom_range = rc.snap_edge_range_inner;
|
|
}
|
|
if (wlr_output_layout_adjacent_output(server.output_layout, WLR_DIRECTION_LEFT,
|
|
output->wlr_output, cursor_x, cursor_y)) {
|
|
left_range = rc.snap_edge_range_inner;
|
|
}
|
|
if (wlr_output_layout_adjacent_output(server.output_layout, WLR_DIRECTION_RIGHT,
|
|
output->wlr_output, cursor_x, cursor_y)) {
|
|
right_range = rc.snap_edge_range_inner;
|
|
}
|
|
|
|
/* Translate into output local coordinates */
|
|
wlr_output_layout_output_coords(server.output_layout,
|
|
output->wlr_output, &cursor_x, &cursor_y);
|
|
|
|
struct wlr_box *area = &output->usable_area;
|
|
|
|
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 < top_range) {
|
|
*edge1 = LAB_EDGE_TOP;
|
|
} else if (bottom < bottom_range) {
|
|
*edge1 = LAB_EDGE_BOTTOM;
|
|
} else if (left < left_range) {
|
|
*edge1 = LAB_EDGE_LEFT;
|
|
} else if (right < right_range) {
|
|
*edge1 = LAB_EDGE_RIGHT;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
if (*edge1 == LAB_EDGE_TOP || *edge1 == LAB_EDGE_BOTTOM) {
|
|
if (left < rc.snap_edge_corner_range) {
|
|
*edge2 = LAB_EDGE_LEFT;
|
|
} else if (right < rc.snap_edge_corner_range) {
|
|
*edge2 = LAB_EDGE_RIGHT;
|
|
}
|
|
} else if (*edge1 == LAB_EDGE_LEFT || *edge1 == LAB_EDGE_RIGHT) {
|
|
if (top < rc.snap_edge_corner_range) {
|
|
*edge2 = LAB_EDGE_TOP;
|
|
} else if (bottom < rc.snap_edge_corner_range) {
|
|
*edge2 = LAB_EDGE_BOTTOM;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Returns true if view was snapped to any edge */
|
|
static bool
|
|
snap_to_edge(struct view *view)
|
|
{
|
|
struct output *output;
|
|
enum lab_edge edge1, edge2;
|
|
if (!edge_from_cursor(&server.seat, &output, &edge1, &edge2)) {
|
|
return false;
|
|
}
|
|
enum lab_edge edge = edge1 | edge2;
|
|
|
|
view_set_output(view, output);
|
|
if (edge == LAB_EDGE_TOP && rc.snap_top_maximize) {
|
|
/* <topMaximize> */
|
|
view_maximize(view, VIEW_AXIS_BOTH);
|
|
} else {
|
|
view_snap_to_edge(view, edge, /*across_outputs*/ false,
|
|
/*combine*/ false);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
snap_to_region(struct view *view)
|
|
{
|
|
if (!regions_should_snap()) {
|
|
return false;
|
|
}
|
|
|
|
struct region *region = regions_from_cursor();
|
|
if (region) {
|
|
view_snap_to_region(view, region);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void
|
|
interactive_finish(struct view *view)
|
|
{
|
|
assert(view);
|
|
|
|
if (server.grabbed_view != view) {
|
|
return;
|
|
}
|
|
|
|
if (server.input_mode == LAB_INPUT_STATE_MOVE) {
|
|
if (!snap_to_region(view)) {
|
|
snap_to_edge(view);
|
|
}
|
|
}
|
|
|
|
interactive_cancel(view);
|
|
}
|
|
|
|
/*
|
|
* Cancels interactive move/resize without changing the state of the of
|
|
* the view in any way. This may leave the tiled state inconsistent with
|
|
* the actual geometry of the view.
|
|
*/
|
|
void
|
|
interactive_cancel(struct view *view)
|
|
{
|
|
assert(view);
|
|
|
|
if (server.grabbed_view != view) {
|
|
return;
|
|
}
|
|
|
|
server.grabbed_view = NULL;
|
|
|
|
/*
|
|
* It's possible that grabbed_view was set but interactive_begin()
|
|
* wasn't called yet. In that case, we are done.
|
|
*/
|
|
if (server.input_mode != LAB_INPUT_STATE_MOVE
|
|
&& server.input_mode != LAB_INPUT_STATE_RESIZE) {
|
|
return;
|
|
}
|
|
|
|
overlay_finish(&server.seat);
|
|
|
|
resize_indicator_hide(view);
|
|
|
|
/* Restore keyboard/pointer focus */
|
|
seat_focus_override_end(&server.seat, /*restore_focus*/ true);
|
|
}
|