From 64a8e28f0ad314a1f9a1ae58ab5e42f36bb4787a Mon Sep 17 00:00:00 2001 From: blasty Date: Fri, 7 Aug 2026 23:56:34 +0200 Subject: splash: scale the logo to the pane instead of dropping it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported as "the splash logo stopped rendering". It had not stopped: the splash asks for the artwork's NATURAL size and shows nothing when that does not fit, and the artwork needs 31 rows plus 10 of box chrome. A pane in a split zellij window is 31 rows — one row short of the 41 it wanted — so the logo silently disappeared. Traced with $IDATUI_KITTY_LOG in the real session: compose: supported=True app.size=Size(width=159, height=31) cells=60x23 fits=False The terminal scales an image into whatever cell box it is placed in (`c=`/`r=` on the placement), so there was never a reason for all-or-nothing. `logo_cells(max_rows)` now fits the art to the room left after the box's furniture, and the same number reserves the cells and sizes the placement, so a resize needs no relayout. In that same 31-row pane it now draws 55x21 instead of nothing. Two things fixed on the way: * The chrome constant was one row optimistic (`rows + 9` where the box measures 10: border 2, padding 2, art margin 1, title 1, note 1+1, help 1+1). At exactly the old threshold the help line was clipped off the bottom. * `_fits` conflated "is the terminal big enough" with "is the artwork the right size", which is what made the image path inherit the block art's all-or-nothing behaviour. The block art genuinely cannot scale (it is half-block cells, 26 rows) and still falls back to the text splash; the image no longer does. `splash_scaling` pins it at 31, 30 and 44 rows: the logo is drawn, it is scaled to the room, the box is never clipped, and a big pane still gets the natural size. 905 passed, 0 failed. --- tests/test_scenarios.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) (limited to 'tests') diff --git a/tests/test_scenarios.py b/tests/test_scenarios.py index 37ec017..5297f0c 100644 --- a/tests/test_scenarios.py +++ b/tests/test_scenarios.py @@ -1094,6 +1094,65 @@ async def s_structs(c: Ctx): c.check("Esc closes the struct editor", not isinstance(app.screen, StructEditor)) +@scenario("splash_scaling") +async def s_splash_scaling(c: Ctx): + """The splash scales the logo to the pane instead of dropping it. + + The bug this pins: the artwork's natural size is ~31 rows plus 10 of box + chrome, and the check was "do you have 41 rows?". A 31-row pane -- what a + split zellij window actually gives you -- was one row short, so the logo + silently disappeared. The terminal scales an image into whatever cell box + it is placed in, so there was never a reason for all-or-nothing. + """ + from idatui import kittygfx + from idatui.app import (LOGO_CHROME_ROWS, LOGO_MIN_ROWS, LoadingScreen, + logo_cells) + + app = c.app + placed: list[tuple] = [] + real_supported, real_upload, real_place = ( + kittygfx.supported, kittygfx.upload, kittygfx.place) + kittygfx.supported = lambda: True + kittygfx.upload = lambda *a, **k: True + kittygfx.place = lambda *a, **k: (placed.append(a), True)[1] + try: + for width, height in ((159, 31), (100, 30), (140, 44)): + await c.pilot.resize_terminal(width, height) + await c.pause(0.05) + app.push_screen(LoadingScreen("echo")) + await c.wait(lambda: isinstance(app.screen, LoadingScreen), 5) + scr = app.screen + # push_screen returns before compose has mounted the children. + await c.wait(lambda: scr._cells is not None, 5) + room = height - LOGO_CHROME_ROWS + has_image = bool(scr.query("#loading-image")) + c.check(f"{width}x{height}: the logo is drawn, not dropped", + has_image and room >= LOGO_MIN_ROWS, + f"image={has_image} room={room}") + if has_image: + cols, rows = scr._cells + c.check(f"{width}x{height}: scaled to the room available", + rows <= room and rows == min(room, logo_cells()[1]), + f"cells={scr._cells} room={room} natural={logo_cells()}") + await c.wait(lambda: scr.query_one("#loading-box").region.height > 0, 5) + box = scr.query_one("#loading-box").region + c.check(f"{width}x{height}: the box is not clipped", + box.y >= 0 and box.y + box.height <= height, + f"box={box} screen={height}") + app.pop_screen() + await c.pause(0.05) + c.check("a full-size pane still gets the artwork's natural size", + logo_cells(999) == logo_cells(), f"{logo_cells(999)}") + c.check("and the image was actually placed each time", len(placed) >= 3, + f"{placed}") + finally: + kittygfx.supported, kittygfx.upload, kittygfx.place = ( + real_supported, real_upload, real_place) + # Every later scenario assumes the suite's own geometry. + await c.pilot.resize_terminal(140, 44) + await c.pause(0.05) + + @scenario("modal_centering") async def s_modal_centering(c: Ctx): """Every dialog we define is centred, without anyone maintaining a list. -- cgit v1.3.1-sl0p