diff --git a/include/edges.h b/include/edges.h index 4e7549eb..6ce029c3 100644 --- a/include/edges.h +++ b/include/edges.h @@ -3,11 +3,15 @@ #define LABWC_EDGES_H #include +#include +#include #include "common/macros.h" struct border; struct output; +struct server; struct view; +struct wlr_box; static inline int clipped_add(int a, int b) @@ -102,7 +106,7 @@ void edges_adjust_geom(struct view *view, struct border edges, void edges_find_neighbors(struct border *nearest_edges, struct view *view, struct wlr_box target, struct output *output, - edge_validator_t validator, bool use_pending); + edge_validator_t validator, bool use_pending, bool ignore_hidden); void edges_find_outputs(struct border *nearest_edges, struct view *view, struct wlr_box target, struct output *output, @@ -116,4 +120,5 @@ void edges_adjust_resize_geom(struct view *view, struct border edges, bool edges_traverse_edge(struct edge current, struct edge target, struct edge edge); +void edges_calculate_visibility(struct server *server, struct view *ignored_view); #endif /* LABWC_EDGES_H */ diff --git a/include/view.h b/include/view.h index fdedbe14..c24fa3d3 100644 --- a/include/view.h +++ b/include/view.h @@ -166,6 +166,7 @@ struct view { bool tearing_hint; bool visible_on_all_workspaces; enum view_edge tiled; + uint32_t edges_visible; /* enum wlr_edges bitset */ bool inhibits_keybinds; xkb_layout_index_t keyboard_layout; diff --git a/src/edges.c b/src/edges.c index 86736a4d..b5630a6e 100644 --- a/src/edges.c +++ b/src/edges.c @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-only #include #include +#include +#include #include #include "common/border.h" #include "common/macros.h" @@ -8,6 +10,7 @@ #include "edges.h" #include "labwc.h" #include "view.h" +#include "node.h" static void edges_for_target_geometry(struct border *edges, struct view *view, @@ -35,34 +38,54 @@ edges_initialize(struct border *edges) } static inline struct edge -build_edge(struct border region, enum view_edge direction, int pad) +build_edge(struct border region, enum wlr_edges direction, int pad) { struct edge edge = { 0 }; switch (direction) { - case VIEW_EDGE_LEFT: + case WLR_EDGE_LEFT: edge.offset = clipped_sub(region.left, pad); edge.min = region.top; edge.max = region.bottom; break; - case VIEW_EDGE_RIGHT: + case WLR_EDGE_RIGHT: edge.offset = clipped_add(region.right, pad); edge.min = region.top; edge.max = region.bottom; break; - case VIEW_EDGE_UP: + case WLR_EDGE_TOP: edge.offset = clipped_sub(region.top, pad); edge.min = region.left; edge.max = region.right; break; - case VIEW_EDGE_DOWN: + case WLR_EDGE_BOTTOM: edge.offset = clipped_add(region.bottom, pad); edge.min = region.left; edge.max = region.right; break; - default: + case WLR_EDGE_NONE: /* Should never be reached */ - assert(false); + wlr_log(WLR_ERROR, "invalid direction"); + abort(); + } + + return edge; +} + +static inline bool +is_lesser(enum wlr_edges direction) +{ + return direction == WLR_EDGE_LEFT || direction == WLR_EDGE_TOP; +} + +static inline struct edge +build_visible_edge(struct border region, enum wlr_edges direction, + int pad, uint32_t edges_visible) +{ + struct edge edge = build_edge(region, direction, pad); + + if (!(edges_visible & direction)) { + edge.offset = is_lesser(direction) ? INT_MIN : INT_MAX; } return edge; @@ -72,7 +95,7 @@ static void validate_single_region_edge(int *valid_edge, struct border view, struct border target, struct border region, edge_validator_t validator, - enum view_edge direction) + enum wlr_edges direction, uint32_t edges_visible) { /* * When a view snaps to another while moving to its target, it can do @@ -90,42 +113,63 @@ validate_single_region_edge(int *valid_edge, * the region borders for aligned edges only. */ - bool lesser = direction == VIEW_EDGE_LEFT || direction == VIEW_EDGE_UP; + enum wlr_edges opposing = WLR_EDGE_NONE; + + switch (direction) { + case WLR_EDGE_TOP: + opposing = WLR_EDGE_BOTTOM; + break; + case WLR_EDGE_BOTTOM: + opposing = WLR_EDGE_TOP; + break; + case WLR_EDGE_LEFT: + opposing = WLR_EDGE_RIGHT; + break; + case WLR_EDGE_RIGHT: + opposing = WLR_EDGE_LEFT; + break; + case WLR_EDGE_NONE: + /* Should never be reached */ + assert(false); + return; + } validator(valid_edge, build_edge(view, direction, 0), build_edge(target, direction, 0), - build_edge(region, view_edge_invert(direction), 0), - build_edge(region, direction, rc.gap), lesser); + build_visible_edge(region, opposing, 0, edges_visible), + build_visible_edge(region, direction, rc.gap, edges_visible), + is_lesser(direction)); } static void validate_edges(struct border *valid_edges, struct border view, struct border target, - struct border region, edge_validator_t validator) + struct border region, uint32_t edges_visible, + edge_validator_t validator) { /* Check for edges encountered during movement of left edge */ validate_single_region_edge(&valid_edges->left, - view, target, region, validator, VIEW_EDGE_LEFT); + view, target, region, validator, WLR_EDGE_LEFT, edges_visible); /* Check for edges encountered during movement of right edge */ validate_single_region_edge(&valid_edges->right, - view, target, region, validator, VIEW_EDGE_RIGHT); + view, target, region, validator, WLR_EDGE_RIGHT, edges_visible); /* Check for edges encountered during movement of top edge */ validate_single_region_edge(&valid_edges->top, - view, target, region, validator, VIEW_EDGE_UP); + view, target, region, validator, WLR_EDGE_TOP, edges_visible); /* Check for edges encountered during movement of bottom edge */ validate_single_region_edge(&valid_edges->bottom, - view, target, region, validator, VIEW_EDGE_DOWN); + view, target, region, validator, WLR_EDGE_BOTTOM, edges_visible); } static void validate_single_output_edge(int *valid_edge, struct border view, struct border target, struct border region, edge_validator_t validator, - enum view_edge direction) + enum wlr_edges direction) { static struct border unbounded = { .top = INT_MIN, @@ -134,13 +178,11 @@ validate_single_output_edge(int *valid_edge, .left = INT_MIN, }; - bool lesser = direction == VIEW_EDGE_LEFT || direction == VIEW_EDGE_UP; - validator(valid_edge, build_edge(view, direction, 0), build_edge(target, direction, 0), build_edge(region, direction, 0), - build_edge(unbounded, direction, 0), lesser); + build_edge(unbounded, direction, 0), is_lesser(direction)); } static void @@ -182,27 +224,160 @@ validate_output_edges(struct border *valid_edges, /* Left edge encounters a half-infinite region to the left of the output */ validate_single_output_edge(&valid_edges->left, - view, target, output, validator, VIEW_EDGE_LEFT); + view, target, output, validator, WLR_EDGE_LEFT); /* Right edge encounters a half-infinite region to the right of the output */ validate_single_output_edge(&valid_edges->right, - view, target, output, validator, VIEW_EDGE_RIGHT); + view, target, output, validator, WLR_EDGE_RIGHT); /* Top edge encounters a half-infinite region above the output */ validate_single_output_edge(&valid_edges->top, - view, target, output, validator, VIEW_EDGE_UP); + view, target, output, validator, WLR_EDGE_TOP); /* Bottom edge encounters a half-infinite region below the output */ validate_single_output_edge(&valid_edges->bottom, - view, target, output, validator, VIEW_EDGE_DOWN); + view, target, output, validator, WLR_EDGE_BOTTOM); +} + +/* Test if parts of the current view is covered by the remaining space in the region */ +static void +subtract_view_from_space(struct view *view, pixman_region32_t *available) +{ + struct wlr_box view_size = ssd_max_extents(view); + pixman_box32_t view_rect = { + .x1 = view_size.x, + .x2 = view_size.x + view_size.width, + .y1 = view_size.y, + .y2 = view_size.y + view_size.height + }; + + pixman_region_overlap_t overlap = + pixman_region32_contains_rectangle(available, &view_rect); + + switch (overlap) { + case PIXMAN_REGION_IN: + view->edges_visible = WLR_EDGE_TOP | WLR_EDGE_RIGHT + | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT; + break; + case PIXMAN_REGION_OUT: + view->edges_visible = 0; + return; + case PIXMAN_REGION_PART: + ; /* works around "a label can only be part of a statement" */ + pixman_region32_t intersection; + pixman_region32_init(&intersection); + pixman_region32_intersect_rect(&intersection, available, + view_size.x, view_size.y, + view_size.width, view_size.height); + + int nrects; + const pixman_box32_t *rects = + pixman_region32_rectangles(&intersection, &nrects); + + view->edges_visible = 0; + for (int i = 0; i < nrects; i++) { + if (rects[i].x1 == view_rect.x1) { + view->edges_visible |= WLR_EDGE_LEFT; + } + if (rects[i].y1 == view_rect.y1) { + view->edges_visible |= WLR_EDGE_TOP; + } + if (rects[i].x2 == view_rect.x2) { + view->edges_visible |= WLR_EDGE_RIGHT; + } + if (rects[i].y2 == view_rect.y2) { + view->edges_visible |= WLR_EDGE_BOTTOM; + } + } + pixman_region32_fini(&intersection); + break; + } + + /* Subtract the view geometry from the available region for the next check */ + pixman_region32_t view_region; + pixman_region32_init_rects(&view_region, &view_rect, 1); + pixman_region32_subtract(available, available, &view_region); + pixman_region32_fini(&view_region); +} + +static void +subtract_node_tree(struct wlr_scene_tree *tree, pixman_region32_t *available, + struct view *ignored_view) +{ + struct view *view; + struct wlr_scene_node *node; + struct node_descriptor *node_desc; + wl_list_for_each_reverse(node, &tree->children, link) { + if (!node->enabled) { + /* + * This skips everything that is not being + * rendered, including minimized / unmapped + * windows and workspaces other than the + * current one. + */ + continue; + } + + node_desc = node->data; + if (node_desc && node_desc->type == LAB_NODE_DESC_VIEW) { + view = node_view_from_node(node); + if (view != ignored_view) { + subtract_view_from_space(view, available); + } + } else if (node->type == WLR_SCENE_NODE_TREE) { + subtract_node_tree(wlr_scene_tree_from_node(node), + available, ignored_view); + } + } +} + +void +edges_calculate_visibility(struct server *server, struct view *ignored_view) +{ + /* + * The region stores the available output layout space + * and subtracts the window geometries in reverse rendering + * order, e.g. a window rendered on top is subtracted first. + * + * This allows to detect if a window is actually visible. + * If there is no overlap of its geometry and the remaining + * region it must be completely covered by other windows. + * + */ + pixman_region32_t region; + pixman_region32_init(®ion); + + /* + * Initialize the region with each individual output. + * + * If we were to use NULL for the reference output we + * would get a single combined wlr_box of the whole + * layout which could cover actual invisible areas + * in case the output resolutions differ. + */ + struct output *output; + struct wlr_box layout_box; + wl_list_for_each(output, &server->outputs, link) { + if (!output_is_usable(output)) { + continue; + } + wlr_output_layout_get_box(server->output_layout, + output->wlr_output, &layout_box); + pixman_region32_union_rect(®ion, ®ion, + layout_box.x, layout_box.y, layout_box.width, layout_box.height); + } + + subtract_node_tree(&server->scene->tree, ®ion, ignored_view); + + pixman_region32_fini(®ion); } void edges_find_neighbors(struct border *nearest_edges, struct view *view, struct wlr_box target, struct output *output, - edge_validator_t validator, bool use_pending) + edge_validator_t validator, bool use_pending, bool ignore_hidden) { assert(view); assert(validator); @@ -223,6 +398,14 @@ edges_find_neighbors(struct border *nearest_edges, struct view *view, continue; } + uint32_t edges_visible = ignore_hidden ? v->edges_visible : + WLR_EDGE_TOP | WLR_EDGE_LEFT + | WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT; + + if (edges_visible == 0) { + continue; + } + if (output && v->output != output) { continue; } @@ -252,7 +435,7 @@ edges_find_neighbors(struct border *nearest_edges, struct view *view, }; validate_edges(nearest_edges, view_edges, - target_edges, win_edges, validator); + target_edges, win_edges, edges_visible, validator); } } diff --git a/src/interactive.c b/src/interactive.c index 432027a6..f4b09e1d 100644 --- a/src/interactive.c +++ b/src/interactive.c @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only +#include "edges.h" #include "input/keyboard.h" #include "labwc.h" #include "regions.h" @@ -123,6 +124,9 @@ interactive_begin(struct view *view, enum input_mode mode, uint32_t edges) if (rc.resize_indicator) { resize_indicator_show(view); } + if (rc.window_edge_strength) { + edges_calculate_visibility(server, view); + } } /* Returns true if view was snapped to any edge */ diff --git a/src/resistance.c b/src/resistance.c index e7869fc1..7d317de8 100644 --- a/src/resistance.c +++ b/src/resistance.c @@ -114,8 +114,9 @@ resistance_move_apply(struct view *view, double *x, double *y) if (rc.window_edge_strength != 0) { /* Find any relevant window edges encountered by this move */ - edges_find_neighbors(&next_edges, view, target, NULL, - check_edge_window, /* use_pending */ false); + edges_find_neighbors(&next_edges, + view, target, NULL, check_edge_window, + /* use_pending */ false, /* ignore_hidden */ true); } /* If any "best" edges were encountered during this move, snap motion */ @@ -143,8 +144,9 @@ resistance_resize_apply(struct view *view, struct wlr_box *new_geom) if (rc.window_edge_strength != 0) { /* Find any relevant window edges encountered by this move */ - edges_find_neighbors(&next_edges, view, *new_geom, NULL, - check_edge_window, /* use_pending */ false); + edges_find_neighbors(&next_edges, + view, *new_geom, NULL, check_edge_window, + /* use_pending */ false, /* ignore_hidden */ true); } /* If any "best" edges were encountered during this move, snap motion */ diff --git a/src/snap.c b/src/snap.c index 1215f0be..0db5d11c 100644 --- a/src/snap.c +++ b/src/snap.c @@ -121,8 +121,9 @@ snap_move_to_edge(struct view *view, enum view_edge direction, struct border next_edges; edges_initialize(&next_edges); - edges_find_neighbors(&next_edges, view, target, - output, check_edge, /* use_pending */ true); + edges_find_neighbors(&next_edges, + view, target, output, check_edge, + /* use_pending */ true, /* ignore_hidden */ false); /* If any "best" edges were encountered, limit motion */ edges_adjust_move_coords(view, next_edges, @@ -196,8 +197,9 @@ snap_grow_to_next_edge(struct view *view, enum view_edge direction, edges_initialize(&next_edges); /* Limit motion to any intervening edge of other views on this output */ - edges_find_neighbors(&next_edges, view, *geo, - output, check_edge, /* use_pending */ true); + edges_find_neighbors(&next_edges, + view, *geo, output, check_edge, + /* use_pending */ true, /* ignore_hidden */ false); edges_adjust_resize_geom(view, next_edges, resize_edges, geo, /* use_pending */ true); } @@ -255,8 +257,9 @@ snap_shrink_to_next_edge(struct view *view, enum view_edge direction, view->output, check_edge, /* use_pending */ true); /* Limit motion to any intervening edge of ther views on this output */ - edges_find_neighbors(&next_edges, view, *geo, - view->output, check_edge, /* use_pending */ true); + edges_find_neighbors(&next_edges, + view, *geo, view->output, check_edge, + /* use_pending */ true, /* ignore_hidden */ false); edges_adjust_resize_geom(view, next_edges, resize_edges, geo, /* use_pending */ true);