From: tokyo4j Date: Sat, 2 Aug 2025 12:35:51 +0000 (+0900) Subject: interactive: allow snapping to corner edges X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=2f183cdcb6be9e2a8926ccaa276ad6345444fe98;p=proto%2Flabwc.git interactive: allow snapping to corner edges In addition to , 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 and . --- diff --git a/docs/labwc-actions.5.scd b/docs/labwc-actions.5.scd index 604a8468..fa38698a 100644 --- a/docs/labwc-actions.5.scd +++ b/docs/labwc-actions.5.scd @@ -95,7 +95,8 @@ Actions are used in menus and keyboard/mouse bindings. **++ ** 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. diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 5b303138..7def5b92 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -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. -** - 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. +**++ +** + If an interactive move ends with the cursor within ** pixels of an + output edge, the window is snapped to the edge. If it's also within + ** pixels of an output corner, the window is snapped to the + corner instead. A ** of 0 disables snapping. + Default is 10 for ** and 50 for **. ** [yes|no] Show an overlay when snapping to a window to an edge. Default is yes. diff --git a/docs/rc.xml.all b/docs/rc.xml.all index 9342ee15..bc0af78a 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -155,6 +155,7 @@ 10 + 50 diff --git a/include/common/direction.h b/include/common/direction.h index bc8cbd0c..1faa62bb 100644 --- a/include/common/direction.h +++ b/include/common/direction.h @@ -2,9 +2,10 @@ #ifndef LABWC_DIRECTION_H #define LABWC_DIRECTION_H +#include #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 */ diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 05f75098..a96b6620 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -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; diff --git a/include/labwc.h b/include/labwc.h index 0cfdce19..6b90bfd6 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -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 <= () and y <= (), + * 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); diff --git a/include/view.h b/include/view.h index 240d1f7d..c7f5908c 100644 --- a/include/view.h +++ b/include/view.h @@ -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); diff --git a/src/common/direction.c b/src/common/direction.c index ba23d189..3729f3d7 100644 --- a/src/common/direction.c +++ b/src/common/direction.c @@ -4,22 +4,24 @@ #include #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; } } diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 9c8e3e67..92878491 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -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; diff --git a/src/interactive.c b/src/interactive.c index 15b4323d..057426fa 100644 --- a/src/interactive.c +++ b/src/interactive.c @@ -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); /* diff --git a/src/output.c b/src/output.c index 5288b6e9..42d8205d 100644 --- a/src/output.c +++ b/src/output.c @@ -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); diff --git a/src/overlay.c b/src/overlay.c index b7557b23..c6c51067 100644 --- a/src/overlay.c +++ b/src/overlay.c @@ -2,6 +2,7 @@ #include "overlay.h" #include #include +#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; } diff --git a/src/view.c b/src/view.c index 4f9345d5..95cafc80 100644 --- a/src/view.c +++ b/src/view.c @@ -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; } } diff --git a/src/xdg.c b/src/xdg.c index 5a7a6176..363bd478 100644 --- 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; }