2021-09-24 21:45:48 +01:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2020-05-29 21:27:34 +01:00
|
|
|
#include "labwc.h"
|
2022-07-06 07:19:28 +02:00
|
|
|
#include "regions.h"
|
2022-11-21 10:10:39 -05:00
|
|
|
#include "view.h"
|
2020-05-29 21:27:34 +01:00
|
|
|
|
2022-01-05 08:30:07 +01:00
|
|
|
static int
|
|
|
|
|
max_move_scale(double pos_cursor, double pos_current,
|
|
|
|
|
double size_current, double size_orig)
|
|
|
|
|
{
|
|
|
|
|
double anchor_frac = (pos_cursor - pos_current) / size_current;
|
|
|
|
|
int pos_new = pos_cursor - (size_orig * anchor_frac);
|
|
|
|
|
if (pos_new < pos_current) {
|
|
|
|
|
/* Clamp by using the old offsets of the maximized window */
|
|
|
|
|
pos_new = pos_current;
|
|
|
|
|
}
|
|
|
|
|
return pos_new;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 20:41:41 +01:00
|
|
|
void
|
2020-10-21 20:30:06 +01:00
|
|
|
interactive_begin(struct view *view, enum input_mode mode, uint32_t edges)
|
2020-05-29 21:27:34 +01:00
|
|
|
{
|
2022-09-08 11:50:40 -04:00
|
|
|
/*
|
|
|
|
|
* 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 server *server = view->server;
|
2022-11-19 12:58:52 -05:00
|
|
|
struct seat *seat = &server->seat;
|
|
|
|
|
struct wlr_box geometry = {
|
|
|
|
|
.x = view->x,
|
|
|
|
|
.y = view->y,
|
|
|
|
|
.width = view->w,
|
|
|
|
|
.height = view->h
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
2022-07-06 17:05:34 +02:00
|
|
|
if (view->maximized || view->tiled || view->tiled_region) {
|
2022-11-19 12:58:52 -05:00
|
|
|
/*
|
|
|
|
|
* Un-maximize and restore natural width/height.
|
|
|
|
|
* Don't reset tiled state yet since we may want
|
|
|
|
|
* to keep it (in the snap-to-maximize case).
|
|
|
|
|
*/
|
|
|
|
|
geometry = view->natural_geometry;
|
|
|
|
|
geometry.x = max_move_scale(seat->cursor->x, view->x,
|
|
|
|
|
view->w, geometry.width);
|
|
|
|
|
geometry.y = max_move_scale(seat->cursor->y, view->y,
|
|
|
|
|
view->h, geometry.height);
|
|
|
|
|
view_restore_to(view, geometry);
|
|
|
|
|
} else {
|
|
|
|
|
/* Store natural geometry at start of move */
|
|
|
|
|
view_store_natural_geometry(view);
|
|
|
|
|
}
|
|
|
|
|
cursor_set(seat, LAB_CURSOR_GRAB);
|
|
|
|
|
break;
|
|
|
|
|
case LAB_INPUT_STATE_RESIZE:
|
|
|
|
|
if (view->maximized || view->fullscreen) {
|
|
|
|
|
/*
|
|
|
|
|
* We don't allow resizing while maximized or
|
|
|
|
|
* fullscreen.
|
|
|
|
|
*/
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
/*
|
|
|
|
|
* Reset tiled state but keep the same geometry as the
|
|
|
|
|
* starting point for the resize.
|
|
|
|
|
*/
|
2022-11-21 13:17:14 -05:00
|
|
|
view_set_untiled(view);
|
2022-11-19 12:58:52 -05:00
|
|
|
cursor_set(seat, cursor_get_from_edge(edges));
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
/* Should not be reached */
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-09-08 11:50:40 -04:00
|
|
|
|
2022-11-19 12:58:52 -05:00
|
|
|
server->input_mode = mode;
|
|
|
|
|
server->grabbed_view = view;
|
2022-09-08 11:50:40 -04:00
|
|
|
/* Remember view and cursor positions at start of move/resize */
|
|
|
|
|
server->grab_x = seat->cursor->x;
|
|
|
|
|
server->grab_y = seat->cursor->y;
|
2022-11-19 12:58:52 -05:00
|
|
|
server->grab_box = geometry;
|
2022-09-08 11:50:40 -04:00
|
|
|
server->resize_edges = edges;
|
2022-11-19 12:58:52 -05:00
|
|
|
}
|
2022-09-08 11:50:40 -04:00
|
|
|
|
2022-11-19 12:58:52 -05:00
|
|
|
/* Returns true if view was snapped to any edge */
|
|
|
|
|
static bool
|
|
|
|
|
snap_to_edge(struct view *view)
|
|
|
|
|
{
|
|
|
|
|
int snap_range = rc.snap_edge_range;
|
|
|
|
|
if (!snap_range) {
|
|
|
|
|
return false;
|
2021-03-06 18:34:52 +00:00
|
|
|
}
|
2022-07-01 20:42:41 +02:00
|
|
|
|
2022-11-19 12:58:52 -05:00
|
|
|
/* Translate into output local coordinates */
|
|
|
|
|
double cursor_x = view->server->seat.cursor->x;
|
|
|
|
|
double cursor_y = view->server->seat.cursor->y;
|
|
|
|
|
wlr_output_layout_output_coords(view->server->output_layout,
|
|
|
|
|
view->output->wlr_output, &cursor_x, &cursor_y);
|
2021-03-06 18:34:52 +00:00
|
|
|
|
2022-11-19 12:58:52 -05:00
|
|
|
/*
|
|
|
|
|
* Don't store natural geometry here (it was
|
|
|
|
|
* stored already in interactive_begin())
|
|
|
|
|
*/
|
|
|
|
|
struct wlr_box *area = &view->output->usable_area;
|
|
|
|
|
if (cursor_x <= area->x + snap_range) {
|
|
|
|
|
view_snap_to_edge(view, "left",
|
|
|
|
|
/*store_natural_geometry*/ false);
|
|
|
|
|
} else if (cursor_x >= area->x + area->width - snap_range) {
|
|
|
|
|
view_snap_to_edge(view, "right",
|
|
|
|
|
/*store_natural_geometry*/ false);
|
|
|
|
|
} else if (cursor_y <= area->y + snap_range) {
|
|
|
|
|
if (rc.snap_top_maximize) {
|
|
|
|
|
view_maximize(view, true,
|
|
|
|
|
/*store_natural_geometry*/ false);
|
|
|
|
|
} else {
|
|
|
|
|
view_snap_to_edge(view, "up",
|
|
|
|
|
/*store_natural_geometry*/ false);
|
|
|
|
|
}
|
|
|
|
|
} else if (cursor_y >= area->y + area->height - snap_range) {
|
|
|
|
|
view_snap_to_edge(view, "down",
|
|
|
|
|
/*store_natural_geometry*/ false);
|
|
|
|
|
} else {
|
|
|
|
|
/* Not close to any edge */
|
|
|
|
|
return false;
|
2021-11-28 21:47:24 +00:00
|
|
|
}
|
2022-11-19 12:58:52 -05:00
|
|
|
|
|
|
|
|
return true;
|
2020-05-29 21:27:34 +01:00
|
|
|
}
|
2021-12-03 08:09:19 +00:00
|
|
|
|
2022-07-06 17:05:34 +02:00
|
|
|
static bool
|
|
|
|
|
snap_to_region(struct view *view)
|
|
|
|
|
{
|
|
|
|
|
if (!regions_available()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct wlr_keyboard *keyboard =
|
|
|
|
|
&view->server->seat.keyboard_group->keyboard;
|
|
|
|
|
|
|
|
|
|
if (keyboard_any_modifiers_pressed(keyboard)) {
|
|
|
|
|
struct region *region = regions_from_cursor(view->server);
|
|
|
|
|
if (region) {
|
|
|
|
|
view_snap_to_region(view, region,
|
|
|
|
|
/*store_natural_geometry*/ false);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-03 23:15:28 +00:00
|
|
|
void
|
2022-11-19 12:58:52 -05:00
|
|
|
interactive_finish(struct view *view)
|
2021-12-03 23:15:28 +00:00
|
|
|
{
|
2021-12-03 08:09:19 +00:00
|
|
|
if (view->server->grabbed_view == view) {
|
2022-11-19 12:58:52 -05:00
|
|
|
enum input_mode mode = view->server->input_mode;
|
2022-07-06 07:19:28 +02:00
|
|
|
regions_hide_overlay(view->server, &view->server->seat);
|
2021-12-03 08:09:19 +00:00
|
|
|
view->server->input_mode = LAB_INPUT_STATE_PASSTHROUGH;
|
|
|
|
|
view->server->grabbed_view = NULL;
|
2022-11-19 12:58:52 -05:00
|
|
|
if (mode == LAB_INPUT_STATE_MOVE) {
|
2022-07-06 17:05:34 +02:00
|
|
|
if (!snap_to_region(view)) {
|
|
|
|
|
if (!snap_to_edge(view)) {
|
|
|
|
|
/* Reset tiled state if not snapped */
|
|
|
|
|
view_set_untiled(view);
|
|
|
|
|
}
|
2022-01-08 11:25:18 +01:00
|
|
|
}
|
|
|
|
|
}
|
2022-09-12 13:14:18 -04:00
|
|
|
/* Update focus/cursor image */
|
|
|
|
|
cursor_update_focus(view->server);
|
2021-12-03 08:09:19 +00:00
|
|
|
}
|
|
|
|
|
}
|
2022-11-19 12:58:52 -05:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Cancels interative 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)
|
|
|
|
|
{
|
|
|
|
|
if (view->server->grabbed_view == view) {
|
|
|
|
|
view->server->input_mode = LAB_INPUT_STATE_PASSTHROUGH;
|
|
|
|
|
view->server->grabbed_view = NULL;
|
|
|
|
|
/* Update focus/cursor image */
|
|
|
|
|
cursor_update_focus(view->server);
|
|
|
|
|
}
|
|
|
|
|
}
|