// SPDX-License-Identifier: GPL-2.0-only #include #include #include #include "labwc.h" #include "snap.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 + view->pending.width + margin.right, .bottom = view->pending.y + view->pending.height + margin.bottom }; 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->pending.height - 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) { 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 * v->pending.height; vp += gap; if (def.search_dir * vp > 0 && def.search_dir * (vp - p) < 0) { p = vp; } } return p; } static void _snap_move_resize_to_edge(struct view *view, enum view_edge direction, enum snap_mode mode, struct wlr_box *delta) { struct border edge = snap_get_view_edge(view); struct border dmax; if (mode == SNAP_MODE_SHRINK) { /* limit to half of current size */ int width_max_dx = max(view->pending.width - LAB_MIN_VIEW_WIDTH, 0); int height_max_dy = max(view->pending.height - LAB_MIN_VIEW_HEIGHT, 0); dmax.right = min(width_max_dx, view->pending.width / 2); dmax.bottom = min(height_max_dy, view->pending.height / 2); dmax.left = -dmax.right; dmax.top = -dmax.bottom; } else { dmax = snap_get_max_distance(view); } switch (direction) { case VIEW_EDGE_LEFT: if (mode == SNAP_MODE_MOVE) { delta->x += 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) ); } 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; case VIEW_EDGE_RIGHT: if (mode == SNAP_MODE_MOVE) { delta->x += min3( /* 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), /* right edge to left edge */ _snap_next_edge(view, edge.right, near_right, dmax.right, -rc.gap) ); } else if (mode == SNAP_MODE_GROW) { delta->width += min( /* 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; case VIEW_EDGE_DOWN: if (mode == SNAP_MODE_MOVE) { delta->y += min3( /* 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), /* 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; default: return; } } void snap_vector_to_next_edge(struct view *view, enum view_edge direction, int *dx, int *dy) { struct wlr_box delta = {0}; _snap_move_resize_to_edge(view, direction, SNAP_MODE_MOVE, &delta); *dx = delta.x; *dy = delta.y; } int snap_distance_to_next_edge(struct view *view, enum view_edge direction) { struct wlr_box delta = {0}; _snap_move_resize_to_edge(view, direction, SNAP_MODE_MOVE, &delta); switch (direction) { 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; } } void 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); } void 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); }