mirror of
https://github.com/labwc/labwc.git
synced 2025-10-29 05:40:24 -04:00
view: implement cascade placement policy
Adds following settings:
<placement>
<policy>cascade</policy>
<cascadeOffset x="40" y="30" />
</placement>
"Cascade" policy places a new window at the center of the screen like
"center" policy, but possibly shifts its position to bottom-right so the
new window doesn't cover existing windows.
The algorithm is copied from KWin's implementation:
df9f8f8346/src/placement.cpp (L589)
Also added some helper functions to manipulate `wlr_box`.
This commit is contained in:
parent
3be8fe25f3
commit
46ec513630
10 changed files with 199 additions and 17 deletions
|
|
@ -276,9 +276,9 @@ Actions are used in menus and keyboard/mouse bindings.
|
|||
*<action name="AutoPlace" policy="value"/>*
|
||||
Reposition the window according to the desired placement policy.
|
||||
|
||||
*policy* [automatic|cursor|center] Use the specified policy, which has
|
||||
the same meaning as the corresponding value for *<placement><policy>*.
|
||||
Default is automatic.
|
||||
*policy* [automatic|cursor|center|cascade] Use the specified policy,
|
||||
which has the same meaning as the corresponding value for
|
||||
*<placement><policy>*. Default is automatic.
|
||||
|
||||
*<action name="Shade" />*++
|
||||
*<action name="Unshade" />*++
|
||||
|
|
|
|||
|
|
@ -203,12 +203,22 @@ this is for compatibility with Openbox.
|
|||
|
||||
## PLACEMENT
|
||||
|
||||
*<placement><policy>* [center|automatic|cursor]
|
||||
*<placement><policy>* [center|automatic|cursor|cascade]
|
||||
Specify a placement policy for new windows. The "center" policy will
|
||||
always place windows at the center of the active output. The "automatic"
|
||||
policy will try to place new windows in such a way that they will
|
||||
have minimal overlap with existing windows. The "cursor" policy will
|
||||
center new windows under the cursor. Default is "center".
|
||||
center new windows under the cursor. The "cascade" policy will try to
|
||||
place new windows at the center of the active output, but possibly
|
||||
shifts its position to bottom-right not to cover existing windows.
|
||||
Default is "center".
|
||||
|
||||
*<placement><cascadeOffset><x>*++
|
||||
*<placement><cascadeOffset><y>*
|
||||
Specify the offset by which a new window can be shifted from an existing
|
||||
window when <placement><policy> is "cascade". These values must be positive.
|
||||
Default is the height of titlebar (the sum of *titlebar.height* and
|
||||
*border.width* from theme) plus 5 for both *x* and *y*.
|
||||
|
||||
## WINDOW SWITCHER
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,11 @@
|
|||
|
||||
<placement>
|
||||
<policy>center</policy>
|
||||
<!--
|
||||
When <placement><policy> is "cascade", the offset for cascading new
|
||||
windows can be overwritten like this:
|
||||
<cascadeOffset x="40" y="30" />
|
||||
-->
|
||||
</placement>
|
||||
|
||||
<!-- <font><theme> can be defined without an attribute to set all places -->
|
||||
|
|
|
|||
17
include/common/box.h
Normal file
17
include/common/box.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
#ifndef LABWC_BOX_H
|
||||
#define LABWC_BOX_H
|
||||
|
||||
#include <wlr/util/box.h>
|
||||
|
||||
bool
|
||||
box_contains(struct wlr_box *box_super, struct wlr_box *box_sub);
|
||||
|
||||
bool
|
||||
box_intersects(struct wlr_box *box_a, struct wlr_box *box_b);
|
||||
|
||||
/* Returns the bounding box of 2 boxes */
|
||||
void
|
||||
box_union(struct wlr_box *box_dest, struct wlr_box *box_a, struct wlr_box *box_b);
|
||||
|
||||
#endif /* LABWC_BOX_H */
|
||||
|
|
@ -20,7 +20,8 @@ enum view_placement_policy {
|
|||
LAB_PLACE_INVALID = 0,
|
||||
LAB_PLACE_CENTER,
|
||||
LAB_PLACE_CURSOR,
|
||||
LAB_PLACE_AUTOMATIC
|
||||
LAB_PLACE_AUTOMATIC,
|
||||
LAB_PLACE_CASCADE,
|
||||
};
|
||||
|
||||
enum adaptive_sync_mode {
|
||||
|
|
@ -57,6 +58,8 @@ struct rcxml {
|
|||
bool reuse_output_mode;
|
||||
enum view_placement_policy placement_policy;
|
||||
bool xwayland_persistence;
|
||||
int placement_cascade_offset_x;
|
||||
int placement_cascade_offset_y;
|
||||
|
||||
/* focus */
|
||||
bool focus_follow_mouse;
|
||||
|
|
|
|||
49
src/common/box.c
Normal file
49
src/common/box.c
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
#include <assert.h>
|
||||
#include "common/box.h"
|
||||
#include "common/macros.h"
|
||||
|
||||
bool
|
||||
box_contains(struct wlr_box *box_super, struct wlr_box *box_sub)
|
||||
{
|
||||
if (wlr_box_empty(box_super) || wlr_box_empty(box_sub)) {
|
||||
return false;
|
||||
}
|
||||
return box_super->x <= box_sub->x
|
||||
&& box_super->x + box_super->width >= box_sub->x + box_sub->width
|
||||
&& box_super->y <= box_sub->y
|
||||
&& box_super->y + box_super->height >= box_sub->y + box_sub->height;
|
||||
}
|
||||
|
||||
bool
|
||||
box_intersects(struct wlr_box *box_a, struct wlr_box *box_b)
|
||||
{
|
||||
if (wlr_box_empty(box_a) || wlr_box_empty(box_b)) {
|
||||
return false;
|
||||
}
|
||||
return box_a->x < box_b->x + box_b->width
|
||||
&& box_b->x < box_a->x + box_a->width
|
||||
&& box_a->y < box_b->y + box_b->height
|
||||
&& box_b->y < box_a->y + box_a->height;
|
||||
}
|
||||
|
||||
void
|
||||
box_union(struct wlr_box *box_dest, struct wlr_box *box_a, struct wlr_box *box_b)
|
||||
{
|
||||
if (wlr_box_empty(box_a)) {
|
||||
*box_dest = *box_b;
|
||||
return;
|
||||
}
|
||||
if (wlr_box_empty(box_b)) {
|
||||
*box_dest = *box_a;
|
||||
return;
|
||||
}
|
||||
int x1 = MIN(box_a->x, box_b->x);
|
||||
int y1 = MIN(box_a->y, box_b->y);
|
||||
int x2 = MAX(box_a->x + box_a->width, box_b->x + box_b->width);
|
||||
int y2 = MAX(box_a->y + box_a->height, box_b->y + box_b->height);
|
||||
box_dest->x = x1;
|
||||
box_dest->y = y1;
|
||||
box_dest->width = x2 - x1;
|
||||
box_dest->height = y2 - y1;
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
labwc_sources += files(
|
||||
'box.c',
|
||||
'buf.c',
|
||||
'dir.c',
|
||||
'fd-util.c',
|
||||
|
|
|
|||
|
|
@ -901,6 +901,10 @@ entry(xmlNode *node, char *nodename, char *content)
|
|||
}
|
||||
} else if (!strcasecmp(nodename, "xwaylandPersistence.core")) {
|
||||
set_bool(content, &rc.xwayland_persistence);
|
||||
} else if (!strcasecmp(nodename, "x.cascadeOffset.placement")) {
|
||||
rc.placement_cascade_offset_x = atoi(content);
|
||||
} else if (!strcasecmp(nodename, "y.cascadeOffset.placement")) {
|
||||
rc.placement_cascade_offset_y = atoi(content);
|
||||
} else if (!strcmp(nodename, "name.theme")) {
|
||||
rc.theme_name = xstrdup(content);
|
||||
} else if (!strcmp(nodename, "cornerradius.theme")) {
|
||||
|
|
@ -1234,6 +1238,8 @@ rcxml_init(void)
|
|||
has_run = true;
|
||||
|
||||
rc.placement_policy = LAB_PLACE_CENTER;
|
||||
rc.placement_cascade_offset_x = 0;
|
||||
rc.placement_cascade_offset_y = 0;
|
||||
|
||||
rc.xdg_shell_server_side_deco = true;
|
||||
rc.ssd_keep_border = true;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <wlr/util/edges.h>
|
||||
#include <wlr/util/box.h>
|
||||
#include "common/border.h"
|
||||
#include "common/box.h"
|
||||
#include "common/macros.h"
|
||||
#include "config/rcxml.h"
|
||||
#include "edges.h"
|
||||
|
|
@ -466,9 +467,8 @@ edges_find_outputs(struct border *nearest_edges, struct view *view,
|
|||
struct wlr_box usable =
|
||||
output_usable_area_in_layout_coords(o);
|
||||
|
||||
struct wlr_box ol;
|
||||
if (!wlr_box_intersection(&ol, &origin, &usable) &&
|
||||
!wlr_box_intersection(&ol, &target, &usable)) {
|
||||
if (!box_intersects(&origin, &usable)
|
||||
&& !box_intersects(&target, &usable)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
107
src/view.c
107
src/view.c
|
|
@ -4,6 +4,7 @@
|
|||
#include <strings.h>
|
||||
#include <wlr/types/wlr_output_layout.h>
|
||||
#include <wlr/types/wlr_security_context_v1.h>
|
||||
#include "common/box.h"
|
||||
#include "common/macros.h"
|
||||
#include "common/match.h"
|
||||
#include "common/mem.h"
|
||||
|
|
@ -873,6 +874,94 @@ view_center(struct view *view, const struct wlr_box *ref)
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Algorithm based on KWin's implementation:
|
||||
* https://github.com/KDE/kwin/blob/df9f8f8346b5b7645578e37365dabb1a7b02ca5a/src/placement.cpp#L589
|
||||
*/
|
||||
static void
|
||||
view_cascade(struct view *view)
|
||||
{
|
||||
/* "cascade" policy places a new view at center by default */
|
||||
struct wlr_box center = view->pending;
|
||||
view_compute_centered_position(view, NULL,
|
||||
center.width, center.height, ¢er.x, ¢er.y);
|
||||
struct border margin = ssd_get_margin(view->ssd);
|
||||
center.x -= margin.left;
|
||||
center.y -= margin.top;
|
||||
center.width += margin.left + margin.right;
|
||||
center.height += margin.top + margin.bottom;
|
||||
|
||||
/* Candidate geometry to which the view is moved */
|
||||
struct wlr_box candidate = center;
|
||||
|
||||
struct wlr_box usable = output_usable_area_in_layout_coords(view->output);
|
||||
|
||||
/* TODO: move this logic to rcxml.c */
|
||||
int offset_x = rc.placement_cascade_offset_x;
|
||||
int offset_y = rc.placement_cascade_offset_y;
|
||||
struct theme *theme = view->server->theme;
|
||||
int default_offset = theme->title_height + theme->border_width + 5;
|
||||
if (offset_x <= 0) {
|
||||
offset_x = default_offset;
|
||||
}
|
||||
if (offset_y <= 0) {
|
||||
offset_y = default_offset;
|
||||
}
|
||||
|
||||
/*
|
||||
* Keep updating the candidate until it doesn't cover any existing views
|
||||
* or doesn't fit within the usable area.
|
||||
*/
|
||||
bool candidate_updated = true;
|
||||
while (candidate_updated) {
|
||||
candidate_updated = false;
|
||||
struct wlr_box covered = {0};
|
||||
|
||||
/* Iterate over views from top to bottom */
|
||||
struct view *other_view;
|
||||
for_each_view(other_view, &view->server->views,
|
||||
LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) {
|
||||
struct wlr_box other = ssd_max_extents(other_view);
|
||||
if (other_view == view
|
||||
|| view->minimized
|
||||
|| !box_intersects(&candidate, &other)) {
|
||||
continue;
|
||||
}
|
||||
/*
|
||||
* If the candidate covers an existing view whose
|
||||
* top-left corner is not covered by other views,
|
||||
* shift the candidate to bottom-right.
|
||||
*/
|
||||
if (box_contains(&candidate, &other)
|
||||
&& !wlr_box_contains_point(
|
||||
&covered, other.x, other.y)) {
|
||||
candidate.x = other.x + offset_x;
|
||||
candidate.y = other.y + offset_y;
|
||||
if (!box_contains(&usable, &candidate)) {
|
||||
/*
|
||||
* If the candidate doesn't fit within
|
||||
* the usable area, fall back to center
|
||||
* and finish updating the candidate.
|
||||
*/
|
||||
candidate = center;
|
||||
break;
|
||||
} else {
|
||||
/* Repeat with the new candidate */
|
||||
candidate_updated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* We use just a bounding box to represent the covered
|
||||
* area, which would be fine for our use-case.
|
||||
*/
|
||||
box_union(&covered, &covered, &other);
|
||||
}
|
||||
}
|
||||
|
||||
view_move(view, candidate.x + margin.left, candidate.y + margin.top);
|
||||
}
|
||||
|
||||
void
|
||||
view_place_by_policy(struct view *view, bool allow_cursor,
|
||||
enum view_placement_policy policy)
|
||||
|
|
@ -886,6 +975,9 @@ view_place_by_policy(struct view *view, bool allow_cursor,
|
|||
view_move(view, geometry.x, geometry.y);
|
||||
return;
|
||||
}
|
||||
} else if (policy == LAB_PLACE_CASCADE) {
|
||||
view_cascade(view);
|
||||
return;
|
||||
}
|
||||
|
||||
view_center(view, NULL);
|
||||
|
|
@ -1075,14 +1167,11 @@ view_apply_maximized_geometry(struct view *view)
|
|||
* center the unmaximized axis.
|
||||
*/
|
||||
struct wlr_box natural = view->natural_geometry;
|
||||
if (view->maximized != VIEW_AXIS_BOTH) {
|
||||
struct wlr_box intersect;
|
||||
wlr_box_intersection(&intersect, &box, &natural);
|
||||
if (wlr_box_empty(&intersect)) {
|
||||
view_compute_centered_position(view, NULL,
|
||||
natural.width, natural.height,
|
||||
&natural.x, &natural.y);
|
||||
}
|
||||
if (view->maximized != VIEW_AXIS_BOTH
|
||||
&& !box_intersects(&box, &natural)) {
|
||||
view_compute_centered_position(view, NULL,
|
||||
natural.width, natural.height,
|
||||
&natural.x, &natural.y);
|
||||
}
|
||||
|
||||
if (view->ssd_enabled) {
|
||||
|
|
@ -2004,6 +2093,8 @@ view_placement_parse(const char *policy)
|
|||
return LAB_PLACE_CURSOR;
|
||||
} else if (!strcasecmp(policy, "center")) {
|
||||
return LAB_PLACE_CENTER;
|
||||
} else if (!strcasecmp(policy, "cascade")) {
|
||||
return LAB_PLACE_CASCADE;
|
||||
}
|
||||
|
||||
return LAB_PLACE_INVALID;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue