aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-14 14:53:34 +0200
committeruser <user@clank>2026-08-14 14:53:34 +0200
commit7f9e2a5eb2607667eb46811b2f22aba8dff09834 (patch)
treed300225f31a8485e2119478aaf23e991a748ad1f /tests
parentsl0ppty: a floor under splitting, and an arrow saying which way (diff)
downloadslosh-7f9e2a5eb2607667eb46811b2f22aba8dff09834.tar.gz
slosh-7f9e2a5eb2607667eb46811b2f22aba8dff09834.tar.xz
slosh-7f9e2a5eb2607667eb46811b2f22aba8dff09834.zip
sl0ppty: the resize gap says it is a handle
The gap between two panes has always been draggable and has always looked exactly like nothing. Resting on one now marks it dotted, and the mark doubles and goes bold once you actually have hold of it — because "you could move this" and "you are moving this" are different claims, and the second should not have to be inferred from the panes changing size underneath you. Dotted for a possibility, weight for a commitment: the same grammar the split guide already uses, so there is one visual language to learn rather than two. Vertical boundaries get vertical marks and horizontal ones horizontal, along the axis the boundary actually runs. It arms on dwell like the split guide, because crossing a gap on the way to the pane on the other side is the most ordinary mouse movement there is, and a pointer already carrying a pane is not shopping for a boundary — a title drag passing over a gap arms nothing. The hint is tested against the very rect registered as that gap's hit, in the same loop iteration that registers it, so it cannot appear anywhere the click would not land without having to trust a lookup to agree. One thing that would have made this silently useless: app_next_timeout only scheduled a repaint for hovers over `border:` and `title:`, so a hint that arms on dwell over `edge:` would only have appeared when some unrelated event happened to repaint the frame — visible when the session was busy and not when it was idle, which is the worst way for it to be wrong. Everything that arms on dwell has to be listed there, and now is.
Diffstat (limited to 'tests')
-rw-r--r--tests/test_drag.py103
1 files changed, 103 insertions, 0 deletions
diff --git a/tests/test_drag.py b/tests/test_drag.py
index b392f3c..36fa83d 100644
--- a/tests/test_drag.py
+++ b/tests/test_drag.py
@@ -6,6 +6,7 @@ split is equal weights, and every layout pass is still the same pure function
of the tree and the rect.
"""
import sys
+import time
from harness import Session, check, report
@@ -212,6 +213,104 @@ def test_drop_target_is_visible():
s.send(rf"\e[<0;{right['x'] + 5};{right['y'] + 3}m")
+# ---- the resize zone announces itself --------------------------------------
+
+GRIP_V, GRAB_V = "\u250a", "\u2551" # dotted / doubled, vertical boundary
+GRIP_H, GRAB_H = "\u2508", "\u2550" # dotted / doubled, horizontal boundary
+
+
+def test_the_gap_says_it_is_a_handle():
+ with Session(SH, cols=120, rows=26) as s:
+ s.settle()
+ s.key("\\\\")
+ s.settle(200)
+ left, right = s.panes()
+ gx, gy = left["x"] + left["w"], left["y"] + 3
+
+ check("an idle gap is blank", GRIP_V not in s.snapshot().screen(), "")
+
+ # Crossing a gap on the way somewhere is the most ordinary mouse
+ # movement there is, so contact alone must not arm it.
+ s.send(rf"\e[<35;{gx + 1};{gy + 1}M")
+ s.settle(60)
+ check("contact alone does not arm it",
+ GRIP_V not in s.snapshot().screen(), "")
+
+ time.sleep(0.35)
+ s.settle(60)
+ snap = s.snapshot()
+ check("resting on it says it can be moved", GRIP_V in snap.screen(), "")
+ check("and does not yet claim it is being moved",
+ GRAB_V not in snap.screen(), "")
+
+ pos = snap.find(GRIP_V)
+ check("the hint is in the gap, not in either pane",
+ pos and left["x"] + left["w"] <= pos[0] < right["x"], str(pos))
+
+
+def test_the_hint_changes_while_resizing():
+ with Session(SH, cols=120, rows=26) as s:
+ s.settle()
+ s.key("\\\\")
+ s.settle(200)
+ left, right = s.panes()
+ gx, gy = left["x"] + left["w"], left["y"] + 3
+ before = left["w"]
+
+ s.send(rf"\e[<0;{gx + 1};{gy + 1}M")
+ s.send(rf"\e[<32;{gx + 6};{gy + 1}M")
+ s.settle(80)
+ snap = s.snapshot()
+ check("a resize in progress reads differently from a hover",
+ GRAB_V in snap.screen() and GRIP_V not in snap.screen(), "")
+
+ s.send(rf"\e[<0;{gx + 6};{gy + 1}m")
+ s.settle(150)
+ snap = s.snapshot()
+ check("and the boundary actually moved",
+ s.panes()[0]["w"] > before, f'{before} -> {s.panes()[0]["w"]}')
+ check("both hints are gone once it is dropped",
+ GRAB_V not in snap.screen() and GRIP_V not in snap.screen(), "")
+
+
+def test_a_horizontal_boundary_uses_horizontal_marks():
+ with Session(SH, cols=120, rows=26) as s:
+ s.settle()
+ s.key("-") # split into rows
+ s.settle(200)
+ top, bottom = s.panes()
+ gx, gy = top["x"] + 6, top["y"] + top["h"]
+
+ s.send(rf"\e[<35;{gx + 1};{gy + 1}M")
+ time.sleep(0.35)
+ s.settle(60)
+ snap = s.snapshot()
+ check("a row boundary hints along its own axis",
+ GRIP_H in snap.screen() and GRIP_V not in snap.screen(), "")
+ pos = snap.find(GRIP_H)
+ check("in the gap between the two rows",
+ pos and top["y"] + top["h"] <= pos[1] < bottom["y"], str(pos))
+
+
+def test_no_resize_hint_during_another_drag():
+ """A pointer already carrying a pane is not shopping for a boundary."""
+ with Session(SH, cols=120, rows=26) as s:
+ s.settle()
+ s.key("\\\\")
+ s.settle(200)
+ left, right = s.panes()
+ gx, gy = left["x"] + left["w"], left["y"] + 3
+
+ s.send(rf"\e[<0;{left['x'] + 5};{left['y'] + 1}M") # grab the title
+ s.send(rf"\e[<32;{gx + 1};{gy + 1}M") # drag over the gap
+ time.sleep(0.35)
+ s.settle(80)
+ check("a title drag over a gap arms nothing",
+ GRIP_V not in s.snapshot().screen(), "")
+ s.send(rf"\e[<0;{gx + 1};{gy + 1}m")
+ s.settle(80)
+
+
if __name__ == "__main__":
test_keyboard_resize()
test_vertical_resize()
@@ -220,4 +319,8 @@ if __name__ == "__main__":
test_title_drag_reorders()
test_drag_edge_cases()
test_drop_target_is_visible()
+ test_the_gap_says_it_is_a_handle()
+ test_the_hint_changes_while_resizing()
+ test_a_horizontal_boundary_uses_horizontal_marks()
+ test_no_resize_hint_during_another_drag()
sys.exit(report())