action: allow SnapToEdge to combine two cardinal directions

This patch adds `combine` argument to (Toggle)SnapToEdge actions.
This allows to snap a window to e.g. up-left by running two actions:
- `<action name="SnapToEdge" direction="left" combine="yes" />`
- `<action name="SnapToEdge" direction="up" combine="yes" />`

Then running `<action name="SnapToEdge" direction="down" combine="yes" />`
snaps it to left again. This behavior is almost the same as KWin, except
that snapping a up-right-tiled window to right doesn't move it to the
right-adjacent output, but makes it right-tiled first.
This commit is contained in:
tokyo4j 2025-09-05 12:14:52 +09:00 committed by Hiroaki Yamamoto
parent af6a0df231
commit 2ac48116e1
5 changed files with 68 additions and 26 deletions

View file

@ -92,11 +92,18 @@ Actions are used in menus and keyboard/mouse bindings.
Move window relative to its current position. Positive value of x moves
it right, negative left. Positive value of y moves it down, negative up.
*<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", "up-left", "up-right", "down-left",
"down-right" and "center".
*<action name="ToggleSnapToEdge" direction="value" combine="value" />*++
*<action name="SnapToEdge" direction="value" combine="value" />*
Resize window to fill half or quarter the output in the given direction.
*direction* [up|down|left|right|up-left|up-right|down-left|down-right|center]
Direction in which to snap the window.
*combine* [yes|no]
Allows to snap a window to an output corner by combining two
directions. For example, snapping a window to *right* and then
to *up* places it in the *up-right* quarter of the output.
Default is no.
ToggleSnapToEdge additionally toggles the active window between
tiled to the given direction and its untiled position.

View file

@ -548,7 +548,7 @@ void view_move_to_edge(struct view *view, enum lab_edge direction, bool snap_to_
void view_grow_to_edge(struct view *view, enum lab_edge direction);
void view_shrink_to_edge(struct view *view, enum lab_edge direction);
void view_snap_to_edge(struct view *view, enum lab_edge direction,
bool across_outputs, bool store_natural_geometry);
bool across_outputs, bool combine, bool store_natural_geometry);
void view_snap_to_region(struct view *view, struct region *region, bool store_natural_geometry);
void view_move_to_output(struct view *view, struct output *output);

View file

@ -337,11 +337,6 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
}
break;
case ACTION_TYPE_MOVE_TO_EDGE:
if (!strcasecmp(argument, "snapWindows")) {
action_arg_add_bool(action, argument, parse_bool(content, true));
goto cleanup;
}
/* Falls through */
case ACTION_TYPE_TOGGLE_SNAP_TO_EDGE:
case ACTION_TYPE_SNAP_TO_EDGE:
case ACTION_TYPE_GROW_TO_EDGE:
@ -358,6 +353,17 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
}
goto cleanup;
}
if (action->type == ACTION_TYPE_MOVE_TO_EDGE
&& !strcasecmp(argument, "snapWindows")) {
action_arg_add_bool(action, argument, parse_bool(content, true));
goto cleanup;
}
if ((action->type == ACTION_TYPE_SNAP_TO_EDGE
|| action->type == ACTION_TYPE_TOGGLE_SNAP_TO_EDGE)
&& !strcasecmp(argument, "combine")) {
action_arg_add_bool(action, argument, parse_bool(content, false));
goto cleanup;
}
break;
case ACTION_TYPE_SHOW_MENU:
if (!strcmp(argument, "menu")) {
@ -1031,9 +1037,9 @@ run_action(struct view *view, struct server *server, struct action *action,
view_apply_natural_geometry(view);
break;
}
view_snap_to_edge(view, edge,
/*across_outputs*/ true,
/*store_natural_geometry*/ true);
bool combine = action_get_bool(action, "combine", false);
view_snap_to_edge(view, edge, /*across_outputs*/ true,
combine, /*store_natural_geometry*/ true);
}
break;
case ACTION_TYPE_GROW_TO_EDGE:

View file

@ -260,9 +260,8 @@ snap_to_edge(struct view *view)
view_maximize(view, VIEW_AXIS_BOTH,
/*store_natural_geometry*/ false);
} else {
view_snap_to_edge(view, edge,
/*across_outputs*/ false,
/*store_natural_geometry*/ false);
view_snap_to_edge(view, edge, /*across_outputs*/ false,
/*combine*/ false, /*store_natural_geometry*/ false);
}
return true;

View file

@ -2126,7 +2126,7 @@ view_placement_parse(const char *policy)
void
view_snap_to_edge(struct view *view, enum lab_edge edge,
bool across_outputs, bool store_natural_geometry)
bool across_outputs, bool combine, bool store_natural_geometry)
{
assert(view);
@ -2142,15 +2142,45 @@ view_snap_to_edge(struct view *view, enum lab_edge edge,
view_set_shade(view, false);
if (across_outputs && view->tiled == edge && view->maximized == VIEW_AXIS_NONE) {
/* We are already tiled for this edge; try to switch outputs */
output = output_get_adjacent(view->output, edge, /* wrap */ false);
if (lab_edge_is_cardinal(edge) && view->maximized == VIEW_AXIS_NONE) {
enum lab_edge invert_edge = lab_edge_invert(edge);
/* Represents axis of snapping direction */
enum lab_edge parallel_mask = edge | invert_edge;
/*
* The vector view->tiled is split to components
* parallel/orthogonal to snapping direction. For example,
* view->tiled=TOP_LEFT is split to parallel_tiled=TOP and
* orthogonal_tiled=LEFT when edge=TOP or edge=BOTTOM.
*/
enum lab_edge parallel_tiled = view->tiled & parallel_mask;
enum lab_edge orthogonal_tiled = view->tiled & ~parallel_mask;
if (across_outputs && view->tiled == edge) {
/*
* E.g. when window is tiled to up and being snapped
* to up again, move it to the output above and tile
* it to down.
*/
output = output_get_adjacent(view->output, edge,
/* wrap */ false);
if (!output) {
return;
}
/* When switching outputs, jump to the opposite edge */
edge = lab_edge_invert(edge);
edge = invert_edge;
} else if (combine && parallel_tiled == invert_edge
&& orthogonal_tiled != LAB_EDGE_NONE) {
/*
* E.g. when window is tiled to downleft/downright and
* being snapped to up, tile it to left/right.
*/
edge = view->tiled & ~parallel_mask;
} else if (combine && parallel_tiled == LAB_EDGE_NONE) {
/*
* E.g. when window is tiled to left/right and being
* snapped to up, tile it to upleft/upright.
*/
edge = view->tiled | edge;
}
}
if (view->maximized != VIEW_AXIS_NONE) {