aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-14 20:52:17 +0200
committeruser <user@clank>2026-08-14 20:52:17 +0200
commita8cd32986134c2ba516a11c64dc474dd79855b0d (patch)
tree246817144348f42badcec4158592dac65b12396b
parentsl0ppty: hold the boundaries, not a seat number (diff)
downloadslosh-a8cd32986134c2ba516a11c64dc474dd79855b0d.tar.gz
slosh-a8cd32986134c2ba516a11c64dc474dd79855b0d.tar.xz
slosh-a8cd32986134c2ba516a11c64dc474dd79855b0d.zip
sl0ppty: every place the boundaries meet is a crossing
Resizing one row of a two-by-two puts its column boundary out of line with the one below, which should leave two crossings on the row boundary, one for each. Only one was found. find_corners took the first column boundary it saw and then accepted others only if they occupied exactly the same columns, which is the aligned case and nothing else: anything out of line was discarded rather than becoming a crossing of its own. It groups by the columns a boundary occupies now, so aligned pairs are one crossing that moves both — still the case worth having, since moving one of a matched pair would put a step in what reads as one line — and everything else is its own crossing moving its own boundary. The test starts aligned, moves one row's boundary, and checks the second crossing appears and that dragging it leaves the other row alone.
-rw-r--r--src/app.c43
-rw-r--r--tests/test_corner.py37
2 files changed, 67 insertions, 13 deletions
diff --git a/src/app.c b/src/app.c
index 720a4ef..4b4e31a 100644
--- a/src/app.c
+++ b/src/app.c
@@ -2510,14 +2510,16 @@ static void find_corners(app_t *a) {
struct gapinfo g[64];
size_t n = collect_gaps(cur(a)->root, g, 64, 0);
- for (size_t i = 0; i < n && a->ncorners < 16; i++) {
+ /* Every place a column boundary runs into a row boundary is its own
+ * crossing. Two of them that happen to line up are one crossing with two
+ * boundaries to move; two that do not are two crossings, each moving its
+ * own. Grouping by the columns they occupy gets both, where taking the first
+ * and discarding anything unlike it only ever found the aligned case. */
+ for (size_t i = 0; i < n; i++) {
if (g[i].sp->dir != SPLIT_ROWS) continue;
rect_t h = g[i].r;
- corner_t c = {0};
- c.h_id = g[i].sp->id;
- c.h_edge = g[i].i;
- for (size_t j = 0; j < n && c.nv < 2; j++) {
+ for (size_t j = 0; j < n; j++) {
if (g[j].sp->dir != SPLIT_COLS) continue;
rect_t v = g[j].r;
if ((uint16_t)(v.y + v.h) != h.y && (uint16_t)(h.y + h.h) != v.y) continue;
@@ -2526,16 +2528,31 @@ static void find_corners(app_t *a) {
? (uint16_t)(v.x + v.w)
: (uint16_t)(h.x + h.w);
if (xe <= x0) continue;
- if (!c.nv) {
- c.r = (rect_t){x0, h.y, (uint16_t)(xe - x0), h.h};
- } else if (c.r.x != x0 || c.r.w != (uint16_t)(xe - x0)) {
- continue; /* out of line with the first: not one corner */
+ uint16_t w = (uint16_t)(xe - x0);
+
+ corner_t *c = NULL;
+ for (size_t k = 0; k < a->ncorners; k++) {
+ corner_t *e = &a->corners[k];
+ if (e->h_id == g[i].sp->id && e->h_edge == g[i].i && e->r.x == x0 &&
+ e->r.w == w) {
+ c = e;
+ break;
+ }
+ }
+ if (!c) {
+ if (a->ncorners >= 16) break;
+ c = &a->corners[a->ncorners++];
+ *c = (corner_t){0};
+ c->r = (rect_t){x0, h.y, w, h.h};
+ c->h_id = g[i].sp->id;
+ c->h_edge = g[i].i;
+ }
+ if (c->nv < 2) {
+ c->v_id[c->nv] = g[j].sp->id;
+ c->v_edge[c->nv] = g[j].i;
+ c->nv++;
}
- c.v_id[c.nv] = g[j].sp->id;
- c.v_edge[c.nv] = g[j].i;
- c.nv++;
}
- if (c.nv) a->corners[a->ncorners++] = c;
}
}
diff --git a/tests/test_corner.py b/tests/test_corner.py
index 83645bc..e89e0d0 100644
--- a/tests/test_corner.py
+++ b/tests/test_corner.py
@@ -187,6 +187,42 @@ def test_the_crossing_shows_itself_before_you_rest_on_it():
"\u250a" not in s.snapshot().screen(), "")
+def test_boundaries_out_of_line_are_two_crossings():
+ """Aligned, they are one crossing with two boundaries to move. Out of line,
+ they are two crossings each moving its own -- taking the first and
+ discarding anything unlike it only ever found the aligned case."""
+ l = lay(GRID)
+ with Session(SH, cols=70, rows=22, layout=l) as l_s:
+ s = l_s
+ s.settle(30)
+ check("aligned, there is one", len(corners(s)) == 1, str(corners(s)))
+
+ ids = sorted(rects(s))
+ s.api("focus", id=ids[0])
+ s.settle(10)
+ for _ in range(4):
+ s.send(r"\x01L")
+ s.settle(30)
+
+ c = sorted(corners(s), key=lambda h: h["x"])
+ check("moving one of them makes a second crossing", len(c) == 2,
+ str(c))
+ if len(c) != 2:
+ return
+ check("at the two different columns",
+ c[0]["x"] != c[1]["x"], str(c))
+
+ w0 = [p["w"] for p in s.panes()]
+ s.send(rf"\e[<0;{c[0]['x'] + 1};{c[0]['y'] + 1}M")
+ s.send(rf"\e[<32;{c[0]['x'] + 5};{c[0]['y'] + 1}M")
+ s.send(rf"\e[<0;{c[0]['x'] + 5};{c[0]['y'] + 1}m")
+ s.settle(30)
+ w1 = [p["w"] for p in s.panes()]
+ check("and each moves only the boundary that meets it",
+ w1[:2] == w0[:2] and w1[2:] != w0[2:], f"{w0} -> {w1}")
+ os.unlink(l)
+
+
if __name__ == "__main__":
test_a_grid_has_a_corner_where_the_boundaries_cross()
test_a_layout_with_no_crossing_has_no_corner()
@@ -195,4 +231,5 @@ if __name__ == "__main__":
test_the_hint_says_both_ways()
test_a_drag_keeps_the_boundaries_it_grabbed()
test_the_crossing_shows_itself_before_you_rest_on_it()
+ test_boundaries_out_of_line_are_two_crossings()
sys.exit(report())