]> git.mdlowis.com Git - proto/labwc.git/commitdiff
interactive: allow snapping to corner edges
authortokyo4j <hrak1529@gmail.com>
Sat, 2 Aug 2025 12:35:51 +0000 (21:35 +0900)
committerJohan Malm <johanmalm@users.noreply.github.com>
Mon, 4 Aug 2025 20:24:12 +0000 (21:24 +0100)
In addition to <snapping><range>, <snapping><cornerRange> configures the
distance from the screen corner to trigger quater window snapping.

Also, new values "up-left", "up-right", "down-left" and "down-right" are
allowed for <action name="(Toggle)SnapToEdge" direction="[value]"> and
<query tiled="[value]">.

14 files changed:
docs/labwc-actions.5.scd
docs/labwc-config.5.scd
docs/rc.xml.all
include/common/direction.h
include/config/rcxml.h
include/labwc.h
include/view.h
src/common/direction.c
src/config/rcxml.c
src/interactive.c
src/output.c
src/overlay.c
src/view.c
src/xdg.c

index 604a8468dd351fa8e8a12277ed883f12bc12cea0..fa38698a378754676b005293d48ac7433b1b82be 100644 (file)
@@ -95,7 +95,8 @@ Actions are used in menus and keyboard/mouse bindings.
 *<action name="ToggleSnapToEdge" direction="value" />*++
 *<action name="SnapToEdge" direction="value" />*
        Resize window to fill half the output in the given direction. Supports
-       directions "left", "up", "right", "down" and "center".
+       directions "left", "up", "right", "down", "up-left", "up-right", "down-left",
+       "down-right" and "center".
 
        ToggleSnapToEdge additionally toggles the active window between
        tiled to the given direction and its untiled position.
@@ -474,7 +475,7 @@ Actions that execute other actions. Used in keyboard/mouse bindings.
                        The "left" , "right", "left-occupied" and
                        "right-occupied" directions will not wrap.
 
-               *tiled* [up|right|down|left|center|any]
+               *tiled* [up|right|down|left|top-left|top-right|down-left|down-right|center|any]
                        Whether the client is tiled (snapped) along the the
                        indicated screen edge.
 
index 5b303138358260c92472fe06382e46dd10c41ce4..7def5b9207fef91c2060ff8193168e73de397fa3 100644 (file)
@@ -412,11 +412,13 @@ activated with SnapToEdge actions or, optionally, by dragging windows to the
 edges of an output. Edge snapping causes a window to occupy half of its output,
 extending outward from the snapped edge.
 
-*<snapping><range>*
-       If an interactive move ends with the cursor a maximum distance *range*,
-       (in pixels) from the edge of an output, the move will trigger a
-       SnapToEdge action for that edge. A *range* of 0 disables snapping via
-       interactive moves. Default is 10.
+*<snapping><range>*++
+*<snapping><cornerRange>*
+       If an interactive move ends with the cursor within *<range>* pixels of an
+       output edge, the window is snapped to the edge. If it's also within
+       *<cornerRange>* pixels of an output corner, the window is snapped to the
+       corner instead. A *<range>* of 0 disables snapping.
+       Default is 10 for *<range>* and 50 for *<cornerRange>*.
 
 *<snapping><overlay><enabled>* [yes|no]
        Show an overlay when snapping to a window to an edge. Default is yes.
index 9342ee15d1657eec5154aa301344f2ba0585f1b2..bc0af78aa590ffe3bdf0ce3271a7b6dd924add84 100644 (file)
   <snapping>
     <!-- Set range to 0 to disable window snapping completely -->
     <range>10</range>
+    <cornerRange>50</cornerRange>
     <overlay enabled="yes">
       <delay inner="500" outer="500" />
     </overlay>
index bc8cbd0c4873cc3e877bb29e23bb1440f5182fe1..1faa62bb097354e962d03c6d1fde5e1c476d7f99 100644 (file)
@@ -2,9 +2,10 @@
 #ifndef LABWC_DIRECTION_H
 #define LABWC_DIRECTION_H
 
+#include <wlr/types/wlr_output_layout.h>
 #include "view.h"
 
-enum wlr_direction direction_from_view_edge(enum view_edge edge);
+bool direction_from_view_edge(enum view_edge edge, enum wlr_direction *direction);
 enum wlr_direction direction_get_opposite(enum wlr_direction direction);
 
 #endif /* LABWC_DIRECTION_H */
index 05f750980ad878f4e6bf1f72ce2260e89304ceee..a96b662085bbc62072e06f70948c51efa6291a9b 100644 (file)
@@ -145,6 +145,7 @@ struct rcxml {
 
        /* window snapping */
        int snap_edge_range;
+       int snap_edge_corner_range;
        bool snap_overlay_enabled;
        int snap_overlay_delay_inner;
        int snap_overlay_delay_outer;
index 0cfdce19d2d951911a613b0a0583ae21f2fcf017..6b90bfd6e359fb12d89f2b15f1d0eea9e198ad0a 100644 (file)
@@ -429,7 +429,16 @@ void interactive_anchor_to_cursor(struct server *server, struct wlr_box *geo);
 void interactive_begin(struct view *view, enum input_mode mode, uint32_t edges);
 void interactive_finish(struct view *view);
 void interactive_cancel(struct view *view);
-enum view_edge edge_from_cursor(struct seat *seat, struct output **dest_output);
+
+/**
+ * Returns the edge to snap a window to.
+ * For example, if the output-relative cursor position (x,y) fulfills
+ * x <= (<snapping><cornerRange>) and y <= (<snapping><range>),
+ * then edge1=VIEW_EDGE_UP and edge2=VIEW_EDGE_LEFT.
+ * The value of (edge1|edge2) can be passed to view_snap_to_edge().
+ */
+bool edge_from_cursor(struct seat *seat, struct output **dest_output,
+       enum view_edge *edge1, enum view_edge *edge2);
 
 void handle_tearing_new_object(struct wl_listener *listener, void *data);
 
index 240d1f7dab951fced55204010c04c175d1a96752..c7f5908c62fc72de7ebb854cf6ebd01635f0f3c4 100644 (file)
@@ -73,6 +73,10 @@ enum view_edge {
        VIEW_EDGE_DOWN = (1 << 3),
        VIEW_EDGE_CENTER = (1 << 4),
        VIEW_EDGE_ANY = (1 << 5),
+       VIEW_EDGE_UPLEFT = (VIEW_EDGE_UP | VIEW_EDGE_LEFT),
+       VIEW_EDGE_UPRIGHT = (VIEW_EDGE_UP | VIEW_EDGE_RIGHT),
+       VIEW_EDGE_DOWNLEFT = (VIEW_EDGE_DOWN | VIEW_EDGE_LEFT),
+       VIEW_EDGE_DOWNRIGHT = (VIEW_EDGE_DOWN | VIEW_EDGE_RIGHT),
 };
 
 enum view_wants_focus {
@@ -524,6 +528,9 @@ bool view_is_focusable(struct view *view);
  */
 void view_offer_focus(struct view *view);
 
+struct wlr_box view_get_edge_snap_box(struct view *view, struct output *output,
+       enum view_edge edge);
+
 void mappable_connect(struct mappable *mappable, struct wlr_surface *surface,
        wl_notify_func_t notify_map, wl_notify_func_t notify_unmap);
 void mappable_disconnect(struct mappable *mappable);
index ba23d1891d29f23d00315a61680fbd180a31744d..3729f3d70a0e7290125ea105a598b54805a6405a 100644 (file)
@@ -4,22 +4,24 @@
 #include <wlr/types/wlr_output_layout.h>
 #include "view.h"
 
-enum wlr_direction
-direction_from_view_edge(enum view_edge edge)
+bool
+direction_from_view_edge(enum view_edge edge, enum wlr_direction *direction)
 {
        switch (edge) {
        case VIEW_EDGE_LEFT:
-               return WLR_DIRECTION_LEFT;
+               *direction = WLR_DIRECTION_LEFT;
+               return true;
        case VIEW_EDGE_RIGHT:
-               return WLR_DIRECTION_RIGHT;
+               *direction = WLR_DIRECTION_RIGHT;
+               return true;
        case VIEW_EDGE_UP:
-               return WLR_DIRECTION_UP;
+               *direction = WLR_DIRECTION_UP;
+               return true;
        case VIEW_EDGE_DOWN:
-               return WLR_DIRECTION_DOWN;
-       case VIEW_EDGE_CENTER:
-       case VIEW_EDGE_INVALID:
+               *direction = WLR_DIRECTION_DOWN;
+               return true;
        default:
-               return WLR_DIRECTION_UP;
+               return false;
        }
 }
 
index 9c8e3e6764197d261d954b3a28858f8d86c53b62..928784912c22bf16fd45286b0a17b98a36420cda 100644 (file)
@@ -1175,6 +1175,8 @@ entry(xmlNode *node, char *nodename, char *content)
                rc.unmaximize_threshold = atoi(content);
        } else if (!strcasecmp(nodename, "range.snapping")) {
                rc.snap_edge_range = atoi(content);
+       } else if (!strcasecmp(nodename, "cornerRange.snapping")) {
+               rc.snap_edge_corner_range = atoi(content);
        } else if (!strcasecmp(nodename, "enabled.overlay.snapping")) {
                set_bool(content, &rc.snap_overlay_enabled);
        } else if (!strcasecmp(nodename, "inner.delay.overlay.snapping")) {
@@ -1411,6 +1413,7 @@ rcxml_init(void)
        rc.unmaximize_threshold = 150;
 
        rc.snap_edge_range = 10;
+       rc.snap_edge_corner_range = 50;
        rc.snap_overlay_enabled = true;
        rc.snap_overlay_delay_inner = 500;
        rc.snap_overlay_delay_outer = 500;
index 15b4323d61da0e6c855206b5a4033db6999e617e..057426fada078a58f811e2506c96ca42b35ff2f4 100644 (file)
@@ -164,22 +164,26 @@ interactive_begin(struct view *view, enum input_mode mode, uint32_t edges)
        }
 }
 
-enum view_edge
-edge_from_cursor(struct seat *seat, struct output **dest_output)
+bool
+edge_from_cursor(struct seat *seat, struct output **dest_output,
+               enum view_edge *edge1, enum view_edge *edge2)
 {
+       *dest_output = NULL;
+       *edge1 = VIEW_EDGE_INVALID;
+       *edge2 = VIEW_EDGE_INVALID;
+
        if (!view_is_floating(seat->server->grabbed_view)) {
-               return VIEW_EDGE_INVALID;
+               return false;
        }
 
-       int snap_range = rc.snap_edge_range;
-       if (!snap_range) {
-               return VIEW_EDGE_INVALID;
+       if (rc.snap_edge_range == 0) {
+               return false;
        }
 
        struct output *output = output_nearest_to_cursor(seat->server);
        if (!output_is_usable(output)) {
                wlr_log(WLR_ERROR, "output at cursor is unusable");
-               return VIEW_EDGE_INVALID;
+               return false;
        }
        *dest_output = output;
 
@@ -190,18 +194,39 @@ edge_from_cursor(struct seat *seat, struct output **dest_output)
                output->wlr_output, &cursor_x, &cursor_y);
 
        struct wlr_box *area = &output->usable_area;
-       if (cursor_x <= area->x + snap_range) {
-               return VIEW_EDGE_LEFT;
-       } else if (cursor_x >= area->x + area->width - snap_range) {
-               return VIEW_EDGE_RIGHT;
-       } else if (cursor_y <= area->y + snap_range) {
-               return VIEW_EDGE_UP;
-       } else if (cursor_y >= area->y + area->height - snap_range) {
-               return VIEW_EDGE_DOWN;
+
+       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 < rc.snap_edge_range) {
+               *edge1 = VIEW_EDGE_UP;
+       } else if (bottom < rc.snap_edge_range) {
+               *edge1 = VIEW_EDGE_DOWN;
+       } else if (left < rc.snap_edge_range) {
+               *edge1 = VIEW_EDGE_LEFT;
+       } else if (right < rc.snap_edge_range) {
+               *edge1 = VIEW_EDGE_RIGHT;
        } else {
-               /* Not close to any edge */
-               return VIEW_EDGE_INVALID;
+               return false;
        }
+
+       if (*edge1 == VIEW_EDGE_UP || *edge1 == VIEW_EDGE_DOWN) {
+               if (left < rc.snap_edge_corner_range) {
+                       *edge2 = VIEW_EDGE_LEFT;
+               } else if (right < rc.snap_edge_corner_range) {
+                       *edge2 = VIEW_EDGE_RIGHT;
+               }
+       } else if (*edge1  == VIEW_EDGE_LEFT || *edge1 == VIEW_EDGE_RIGHT) {
+               if (top < rc.snap_edge_corner_range) {
+                       *edge2 = VIEW_EDGE_UP;
+               } else if (bottom < rc.snap_edge_corner_range) {
+                       *edge2 = VIEW_EDGE_DOWN;
+               }
+       }
+
+       return true;
 }
 
 /* Returns true if view was snapped to any edge */
@@ -209,10 +234,11 @@ static bool
 snap_to_edge(struct view *view)
 {
        struct output *output;
-       enum view_edge edge = edge_from_cursor(&view->server->seat, &output);
-       if (edge == VIEW_EDGE_INVALID) {
+       enum view_edge edge1, edge2;
+       if (!edge_from_cursor(&view->server->seat, &output, &edge1, &edge2)) {
                return false;
        }
+       enum view_edge edge = edge1 | edge2;
 
        view_set_output(view, output);
        /*
index 5288b6e97bab44e8b4602a53b972907a30f91f9a..42d8205d7c6029dac79f91c2dba29659122e0c8a 100644 (file)
@@ -979,6 +979,11 @@ output_get_adjacent(struct output *output, enum view_edge edge, bool wrap)
                return NULL;
        }
 
+       enum wlr_direction direction;
+       if (!direction_from_view_edge(edge, &direction)) {
+               return NULL;
+       }
+
        struct wlr_box box = output_usable_area_in_layout_coords(output);
        int lx = box.x + box.width / 2;
        int ly = box.y + box.height / 2;
@@ -987,7 +992,6 @@ output_get_adjacent(struct output *output, enum view_edge edge, bool wrap)
        struct wlr_output *new_output = NULL;
        struct wlr_output *current_output = output->wlr_output;
        struct wlr_output_layout *layout = output->server->output_layout;
-       enum wlr_direction direction = direction_from_view_edge(edge);
        new_output = wlr_output_layout_adjacent_output(layout, direction,
                current_output, lx, ly);
 
index b7557b23becf04fff649e912e30be7222e59a7e6..c6c51067f4dd3b9cd688722089c04c3050a2f4e2 100644 (file)
@@ -2,6 +2,7 @@
 #include "overlay.h"
 #include <assert.h>
 #include <wlr/types/wlr_scene.h>
+#include "common/direction.h"
 #include "common/lab-scene-rect.h"
 #include "labwc.h"
 #include "output.h"
@@ -137,42 +138,27 @@ handle_edge_overlay_timeout(void *data)
        return 0;
 }
 
-static enum wlr_direction
-get_wlr_direction(enum view_edge edge)
-{
-       switch (edge) {
-       case VIEW_EDGE_LEFT:
-               return WLR_DIRECTION_LEFT;
-       case VIEW_EDGE_RIGHT:
-               return WLR_DIRECTION_RIGHT;
-       case VIEW_EDGE_UP:
-       case VIEW_EDGE_CENTER:
-               return WLR_DIRECTION_UP;
-       case VIEW_EDGE_DOWN:
-               return WLR_DIRECTION_DOWN;
-       default:
-               /* not reached */
-               assert(false);
-               return 0;
-       }
-}
-
 static bool
 edge_has_adjacent_output_from_cursor(struct seat *seat, struct output *output,
                enum view_edge edge)
 {
+       enum wlr_direction dir;
+       if (!direction_from_view_edge(edge, &dir)) {
+               return false;
+       }
        return wlr_output_layout_adjacent_output(
-               seat->server->output_layout, get_wlr_direction(edge),
+               seat->server->output_layout, dir,
                output->wlr_output, seat->cursor->x, seat->cursor->y);
 }
 
 static void
-show_edge_overlay(struct seat *seat, enum view_edge edge,
+show_edge_overlay(struct seat *seat, enum view_edge edge1, enum view_edge edge2,
                struct output *output)
 {
        if (!rc.snap_overlay_enabled) {
                return;
        }
+       uint32_t edge = edge1 | edge2;
        if (seat->overlay.active.edge == edge
                        && seat->overlay.active.output == output) {
                return;
@@ -182,7 +168,7 @@ show_edge_overlay(struct seat *seat, enum view_edge edge,
        seat->overlay.active.output = output;
 
        int delay;
-       if (edge_has_adjacent_output_from_cursor(seat, output, edge)) {
+       if (edge_has_adjacent_output_from_cursor(seat, output, edge1)) {
                delay = rc.snap_overlay_delay_inner;
        } else {
                delay = rc.snap_overlay_delay_outer;
@@ -219,9 +205,9 @@ overlay_update(struct seat *seat)
 
        /* Edge-snapping overlay */
        struct output *output;
-       enum view_edge edge = edge_from_cursor(seat, &output);
-       if (edge != VIEW_EDGE_INVALID) {
-               show_edge_overlay(seat, edge, output);
+       enum view_edge edge1, edge2;
+       if (edge_from_cursor(seat, &output, &edge1, &edge2)) {
+               show_edge_overlay(seat, edge1, edge2, output);
                return;
        }
 
index 4f9345d5a0f7838b16fd38c8772bbaf5e568c5b6..95cafc809eb0fef61ba5cb59afb4a2734050b7e3 100644 (file)
@@ -451,35 +451,29 @@ view_get_edge_snap_box(struct view *view, struct output *output,
                enum view_edge edge)
 {
        struct wlr_box usable = output_usable_area_in_layout_coords(output);
-       int x_offset = edge == VIEW_EDGE_RIGHT
-               ? (usable.width + rc.gap) / 2 : rc.gap;
-       int y_offset = edge == VIEW_EDGE_DOWN
-               ? (usable.height + rc.gap) / 2 : rc.gap;
+       int x1 = rc.gap;
+       int y1 = rc.gap;
+       int x2 = usable.width - rc.gap;
+       int y2 = usable.height - rc.gap;
 
-       int base_width, base_height;
-       switch (edge) {
-       case VIEW_EDGE_LEFT:
-       case VIEW_EDGE_RIGHT:
-               base_width = (usable.width - 3 * rc.gap) / 2;
-               base_height = usable.height - 2 * rc.gap;
-               break;
-       case VIEW_EDGE_UP:
-       case VIEW_EDGE_DOWN:
-               base_width = usable.width - 2 * rc.gap;
-               base_height = (usable.height - 3 * rc.gap) / 2;
-               break;
-       default:
-       case VIEW_EDGE_CENTER:
-               base_width = usable.width - 2 * rc.gap;
-               base_height = usable.height - 2 * rc.gap;
-               break;
+       if (edge & VIEW_EDGE_RIGHT) {
+               x1 = (usable.width + rc.gap) / 2;
+       }
+       if (edge & VIEW_EDGE_LEFT) {
+               x2 = (usable.width - rc.gap) / 2;
+       }
+       if (edge & VIEW_EDGE_DOWN) {
+               y1 = (usable.height + rc.gap) / 2;
+       }
+       if (edge & VIEW_EDGE_UP) {
+               y2 = (usable.height - rc.gap) / 2;
        }
 
        struct wlr_box dst = {
-               .x = x_offset + usable.x,
-               .y = y_offset + usable.y,
-               .width = base_width,
-               .height = base_height,
+               .x = x1 + usable.x,
+               .y = y1 + usable.y,
+               .width = x2 - x1,
+               .height = y2 - y1,
        };
 
        if (view) {
@@ -2149,6 +2143,14 @@ view_edge_parse(const char *direction, bool tiled, bool any)
        if (tiled) {
                if (!strcasecmp(direction, "center")) {
                        return VIEW_EDGE_CENTER;
+               } else if (!strcasecmp(direction, "up-left")) {
+                       return VIEW_EDGE_UPLEFT;
+               } else if (!strcasecmp(direction, "up-right")) {
+                       return VIEW_EDGE_UPRIGHT;
+               } else if (!strcasecmp(direction, "down-left")) {
+                       return VIEW_EDGE_DOWNLEFT;
+               } else if (!strcasecmp(direction, "down-right")) {
+                       return VIEW_EDGE_DOWNRIGHT;
                }
        }
 
index 5a7a6176866eec127a81b7affb6fe75e568c379d..363bd478cee7d8c2e4574179680bad8b7cac6d11 100644 (file)
--- a/src/xdg.c
+++ b/src/xdg.c
@@ -660,6 +660,19 @@ xdg_toplevel_view_notify_tiled(struct view *view)
                case VIEW_EDGE_DOWN:
                        edge = WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT;
                        break;
+               case VIEW_EDGE_UPLEFT:
+                       edge = WLR_EDGE_TOP | WLR_EDGE_LEFT;
+                       break;
+               case VIEW_EDGE_UPRIGHT:
+                       edge = WLR_EDGE_TOP | WLR_EDGE_RIGHT;
+                       break;
+               case VIEW_EDGE_DOWNLEFT:
+                       edge = WLR_EDGE_BOTTOM | WLR_EDGE_LEFT;
+                       break;
+               case VIEW_EDGE_DOWNRIGHT:
+                       edge = WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT;
+                       break;
+               /* TODO: VIEW_EDGE_CENTER? */
                default:
                        edge = WLR_EDGE_NONE;
                }