]> git.mdlowis.com Git - proto/labwc.git/commitdiff
Add snap to window edge framework
authorAxel Burri <axel@tty0.ch>
Sat, 5 Aug 2023 21:53:01 +0000 (23:53 +0200)
committerJohan Malm <johanmalm@users.noreply.github.com>
Thu, 19 Oct 2023 18:09:42 +0000 (19:09 +0100)
Adds functions for calculation of distances between window edges, as
well as for window growing and shrinking.

All calculations are based on the "pending" geometry.

Ignored from snapping:

 - views that do not share the same output
 - minimized views
 - maximized views
 - views that are neither:
   - part of the current workspace
   - part of the always-on-top tree

include/snap.h [new file with mode: 0644]
src/meson.build
src/snap.c [new file with mode: 0644]

diff --git a/include/snap.h b/include/snap.h
new file mode 100644 (file)
index 0000000..20d9b9d
--- /dev/null
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef LABWC_SNAP_H
+#define LABWC_SNAP_H
+
+#include "common/border.h"
+#include "view.h"
+
+struct wlr_box;
+
+struct border snap_get_max_distance(struct view *view);
+
+void snap_vector_to_next_edge(struct view *view, enum view_edge direction, int *dx, int *dy);
+int snap_distance_to_next_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);
+void snap_shrink_to_next_edge(struct view *view, enum view_edge direction, struct wlr_box *geo);
+
+#endif /* LABWC_SNAP_H */
index 58dfacf3cb3eb15e50d55d61aef0d01bde8bf2a1..e88014f3746eb8f181f23d3657b0d963a797bd7e 100644 (file)
@@ -20,6 +20,7 @@ labwc_sources = files(
   'seat.c',
   'server.c',
   'session-lock.c',
+  'snap.c',
   'touch.c',
   'theme.c',
   'view.c',
diff --git a/src/snap.c b/src/snap.c
new file mode 100644 (file)
index 0000000..b403898
--- /dev/null
@@ -0,0 +1,279 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <assert.h>
+#include <strings.h>
+#include <wlr/util/box.h>
+#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);
+}