commands/focus: implement focus wrapping for floating windows

When switching focus between floating windows, those in exactly the same
location (like newly floated windows) have a relative distance of 0.
Previously, since 0 is not `< 0`, all were considered valid candidates
for focus in any direction. This led to two issues:

1. It was not possible to directionally focus away from a set of windows
   in the same location, as the distance of 0 to the other window in
   that location was considered closer than a floating window in another
   location.
2. If floating windows were moved from the same location, any window in
   the opposite direction had a negative distance and would be ignored
   as a focus candidate, so focus switching did not "wrap".

Focusing a window raises it to the end of workspace->floating. A set of
windows in the same location were always valid focus candidates, so
shuffling focus between them created the illusion that focus was
wrapping, when it was actually just "trapped" in that location.

This change fixes both issues:

1. The distance check is changed to `<= 0`. Windows at distance 0 are no
   longer considered valid candidates in the target direction, allowing
   directional focus change to escape windows in the same location.
2. The furthest window in the opposite direction is now tracked. If no
   focus candidates are found in the target direction and focus wrapping
   is enabled, focus will now wrap around to the furthest window in the
   opposite direction.
This commit is contained in:
Scott Leggett 2026-05-29 14:30:00 +08:00 committed by Simon Ser
parent d5f2f2a413
commit f3b6431104

View file

@ -230,6 +230,8 @@ static struct sway_node *node_get_in_direction_floating(
double ref_ly = con->pending.y + con->pending.height / 2;
double closest_distance = DBL_MAX;
struct sway_container *closest_con = NULL;
double furthest_distance = 1;
struct sway_container *furthest_con = NULL;
if (!con->pending.workspace) {
return NULL;
@ -246,7 +248,12 @@ static struct sway_node *node_get_in_direction_floating(
if (dir == WLR_DIRECTION_LEFT || dir == WLR_DIRECTION_UP) {
distance = -distance;
}
if (distance < 0) {
if (distance <= 0) {
// Track the furthest window in the opposite direction for wrapping
if (distance < furthest_distance) {
furthest_distance = distance;
furthest_con = floater;
}
continue;
}
if (distance < closest_distance) {
@ -254,6 +261,11 @@ static struct sway_node *node_get_in_direction_floating(
closest_con = floater;
}
}
// If there is no window in the requested direction, wrap around to the
// furthest window in the opposite direction.
if (!closest_con && config->focus_wrapping != WRAP_NO) {
closest_con = furthest_con;
}
return closest_con ? &closest_con->node : NULL;
}