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 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. it right, negative left. Positive value of y moves it down, negative up.
*<action name="ToggleSnapToEdge" direction="value" />*++ *<action name="ToggleSnapToEdge" direction="value" combine="value" />*++
*<action name="SnapToEdge" direction="value" />* *<action name="SnapToEdge" direction="value" combine="value" />*
Resize window to fill half the output in the given direction. Supports Resize window to fill half or quarter the output in the given direction.
directions "left", "up", "right", "down", "up-left", "up-right", "down-left",
"down-right" and "center". *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 ToggleSnapToEdge additionally toggles the active window between
tiled to the given direction and its untiled position. 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_grow_to_edge(struct view *view, enum lab_edge direction);
void view_shrink_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, 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_snap_to_region(struct view *view, struct region *region, bool store_natural_geometry);
void view_move_to_output(struct view *view, struct output *output); 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; break;
case ACTION_TYPE_MOVE_TO_EDGE: 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_TOGGLE_SNAP_TO_EDGE:
case ACTION_TYPE_SNAP_TO_EDGE: case ACTION_TYPE_SNAP_TO_EDGE:
case ACTION_TYPE_GROW_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; 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; break;
case ACTION_TYPE_SHOW_MENU: case ACTION_TYPE_SHOW_MENU:
if (!strcmp(argument, "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); view_apply_natural_geometry(view);
break; break;
} }
view_snap_to_edge(view, edge, bool combine = action_get_bool(action, "combine", false);
/*across_outputs*/ true, view_snap_to_edge(view, edge, /*across_outputs*/ true,
/*store_natural_geometry*/ true); combine, /*store_natural_geometry*/ true);
} }
break; break;
case ACTION_TYPE_GROW_TO_EDGE: case ACTION_TYPE_GROW_TO_EDGE:

View file

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

View file

@ -2126,7 +2126,7 @@ view_placement_parse(const char *policy)
void void
view_snap_to_edge(struct view *view, enum lab_edge edge, 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); assert(view);
@ -2142,15 +2142,45 @@ view_snap_to_edge(struct view *view, enum lab_edge edge,
view_set_shade(view, false); view_set_shade(view, false);
if (across_outputs && view->tiled == edge && view->maximized == VIEW_AXIS_NONE) { if (lab_edge_is_cardinal(edge) && view->maximized == VIEW_AXIS_NONE) {
/* We are already tiled for this edge; try to switch outputs */ enum lab_edge invert_edge = lab_edge_invert(edge);
output = output_get_adjacent(view->output, edge, /* wrap */ false); /* Represents axis of snapping direction */
if (!output) { enum lab_edge parallel_mask = edge | invert_edge;
return; /*
} * 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;
/* When switching outputs, jump to the opposite edge */ if (across_outputs && view->tiled == edge) {
edge = lab_edge_invert(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;
}
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) { if (view->maximized != VIEW_AXIS_NONE) {