diff options
| author | user <user@clank> | 2026-08-20 23:53:43 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-20 23:53:43 +0200 |
| commit | 64db2051192a6a6f5a9333f503d14b1ccccd699d (patch) | |
| tree | f14bdeee8ccbe6f413e20cf2b746ec16f4414ee1 /tests | |
| parent | slosh: fold floating panes and the drop grammar into DESIGN.md (diff) | |
| download | slosh-64db2051192a6a6f5a9333f503d14b1ccccd699d.tar.gz slosh-64db2051192a6a6f5a9333f503d14b1ccccd699d.tar.xz slosh-64db2051192a6a6f5a9333f503d14b1ccccd699d.zip | |
slosh: M10c, the memory -- floats survive being written down
A dumped session quietly re-tiled its floats, which was the one data-loss
gap left in D22. dump-layout now records `floating=true x= y= w= h=` -- the
*wanted* rect, because intent is what survives a session and the next
screen's layout clamps it like any frame -- layout files declare the same
(linted: floating is a bool, x/y/w/h mean nothing without it), and a file
whose every pane floats self-heals through ensure_tiled: the top float
lands, the same rule closing the last tiled pane follows, no lint required.
save-workspace inherits all of it from the shared dumper.
`float` over the API takes x/y/w/h and then *places* rather than toggles --
a placement is a statement about where, not about whether -- floating the
pane first when it is tiled, re-placing when it already floats, with
omitted fields meaning "keep". The intent is stored exactly as asked: the
write-back rule is for hands, whose drags need the clamp to agree with the
eye; a script means what it typed.
And kitty graphics respect the stack. The cell compositor gets occlusion
free from paint order, but placements are sent after the diff and would
paint an image straight over a float covering it. Each pane's placements
are now clipped by the float rects above it -- every float for a tiled
pane, the higher-raised for a float -- cropping what one clean edge can
express (the pane-edge source-rectangle arithmetic aimed at another
clipper, including from the top and left, where the source origin moves and
the sub-cell offset is consumed) and suppressing what it cannot: a strip
across the middle or a corner overlap leaves a shape one placement cannot
say, and the image returns whole when the float moves. No z= needed --
clipping is the z-order, since nothing is ever allowed to overlap.
D22's "still owed" paragraph is paid and rewritten; M10 is fully checked.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_float.py | 77 | ||||
| -rw-r--r-- | tests/test_graphics.py | 60 |
2 files changed, 136 insertions, 1 deletions
diff --git a/tests/test_float.py b/tests/test_float.py index 6ace740..1745ef7 100644 --- a/tests/test_float.py +++ b/tests/test_float.py @@ -793,6 +793,80 @@ def test_the_grabbed_edge_stays_lit_through_the_drag(): s.send(rf"\e[<0;{x1 - 5};{ey + 1}m") +def test_the_api_places_a_float(): + """The script's version of the drag: with x/y/w/h the verb places — + floats first when tiled, re-places when floating, never un-floats. The + intent is kept as asked; the layout clamps the drawn rect per frame.""" + with Session(SH, cols=100, rows=28) as s: + s.settle(20) + s.api("split", dir="cols") + s.settle(20) + r = s.api("float", x=10, y=5, w=30, h=12) + s.settle(20) + check("placing floats the pane", r.get("ok") and r.get("floating") == 1, str(r)) + check( + "exactly where asked", + rect(floats(s)[0]) == (10, 5, 30, 12), + str(rect(floats(s)[0])), + ) + s.api("float", x=40) # move only: w/h/y mean "keep" + s.settle(20) + check( + "a partial placement moves without unfloating or resizing", + rect(floats(s)[0]) == (40, 5, 30, 12), + str(rect(floats(s)[0])), + ) + + +def test_a_float_survives_dump_and_apply(): + """The memory: a dumped session used to quietly re-tile its floats — the + one data-loss gap. The dump writes the wanted rect (intent is what + survives a session; the next screen's layout clamps it), and apply + restores the pane floating, placed, focused.""" + with Session(SH, cols=100, rows=28) as s: + s.settle(20) + s.api("split", dir="cols") + s.settle(20) + s.api("float", x=12, y=6, w=34, h=14) + s.settle(20) + d = s.api("dump-layout") + check( + "the dump writes the float and its wanted rect", + "floating=true x=12 y=6 w=34 h=14" in d["kdl"], + d["kdl"], + ) + r = s.api("apply-layout", kdl=d["kdl"], replace=True) + s.settle(30) + check("apply accepts it", r.get("ok"), str(r)) + fl = floats(s) + check( + "and the float is back, placed exactly", + len(fl) == 1 and rect(fl[0]) == (12, 6, 34, 14), + str(here(s)), + ) + check("still focused", fl[0]["focused"], str(fl)) + + +def test_a_layout_of_only_floats_self_heals(): + """An overlay needs a backdrop: a file whose every pane floats lands its + top float through ensure_tiled, the same rule closing the last tiled + pane follows — no lint required, the invariant holds itself.""" + kdl = ( + "layout { tab { pane floating=true x=5 y=5 w=30 h=10\n" + " pane floating=true x=40 y=5 w=30 h=10 } }" + ) + with Session(SH, cols=100, rows=28) as s: + s.settle(20) + r = s.api("apply-layout", kdl=kdl, replace=True) + s.settle(30) + check("the layout applies", r.get("ok"), str(r)) + check( + "one float landed as the backdrop, one still floats", + len(tiled(s)) == 1 and len(floats(s)) == 1, + str(here(s)), + ) + + def test_a_float_is_not_a_drop_target(): """Swapping tree seats with a pane that is not in its seat would rearrange something invisible, so a title drag promises nothing over a @@ -867,6 +941,9 @@ if __name__ == "__main__": test_hovering_a_float_edge_shows_the_resize() test_a_float_title_promises_no_split() test_the_grabbed_edge_stays_lit_through_the_drag() + test_the_api_places_a_float() + test_a_float_survives_dump_and_apply() + test_a_layout_of_only_floats_self_heals() test_a_float_is_not_a_drop_target() test_the_key_and_the_palette_both_run_it() sys.exit(report()) diff --git a/tests/test_graphics.py b/tests/test_graphics.py index 805eaeb..245a0bd 100644 --- a/tests/test_graphics.py +++ b/tests/test_graphics.py @@ -547,6 +547,64 @@ def test_scrolled_away_placements_are_dropped(): ) +def test_a_float_occludes_a_placement(): + """The cell compositor gets occlusion free from paint order; placements + are sent after the diff and get it from clipping (D22): a clean edge + crops -- the pane-edge arithmetic aimed at another clipper -- and a + float in the middle is a shape one placement cannot express, so that + placement is suppressed for the frame and returns when the float moves. + + Every pane here runs the same command, so the float shows the image too; + the tiled pane's placement is told apart by the image id it had before + the split.""" + with Session(sends_image(cols=60, rows=6, after="cat"), cols=100, rows=28) as s: + s.settle(120) + base = places(s) + check("one placement to start", len(base) == 1, str(base)) + if not base: + return + img = base[0] + mine = lambda: [p for p in places(s) if p["image"] == img["image"]] + + s.api("split", dir="rows") + s.settle(40) + + # A float over the image's right side: cropped at the float's edge. + s.api("float", x=img["x"] + 20, y=0, w=60, h=28) + s.settle(20) + got = mine() + check( + "a clean edge crops the placement", + len(got) == 1 and got[0]["cols"] == 20 and got[0]["x"] == img["x"], + str(got), + ) + + # Across the middle: no single rect can say what remains. + s.api("float", x=img["x"] + 10, y=0, w=30, h=28) + s.settle(20) + check("a middle strip suppresses it", mine() == [], str(places(s))) + + # Moved clear: the placement returns whole. + s.api("float", x=img["x"], y=14, w=30, h=12) + s.settle(20) + got = mine() + check( + "and it returns whole when the float moves off", + len(got) == 1 and got[0]["cols"] == img["cols"], + str(got), + ) + + # Over the top rows: cropped from the top, the source origin moving. + s.api("float", x=0, y=0, w=100, h=img["y"] + 3) + s.settle(20) + got = mine() + check( + "a top cover crops from the top", + len(got) == 1 and got[0]["y"] > img["y"] and got[0]["rows"] < img["rows"], + str(got), + ) + + if __name__ == "__main__": test_a_pane_image_reaches_the_screen() test_a_png_is_decoded_and_placed() @@ -571,5 +629,5 @@ if __name__ == "__main__": test_partial_visibility_crops_rather_than_squashes() test_hidden_panes_place_nothing() test_placement_goes_away_with_its_pane() - test_scrolled_away_placements_are_dropped() + test_a_float_occludes_a_placement() sys.exit(report()) |
