Skip to content

Commit ffee838

Browse files
czeiclaude
andcommitted
demo(showcase): add ColumnRain and (directional) DropFromSky scenes
The reel now also announces RAIN (ColumnRain, a mask of falling drops) and DROP IN (DropFromSky sliding content in from top -> bottom -> left -> right, showing the new per-edge directions). ColumnRain fits the existing _TransitionScene; DropFromSky is duck-typed (animates the content's x/y via pre_render_hook), so it gets a small DropScene that draws the text at the animated position. The reel test now expects 14 scenes (3 + 7 transitions + 4) and still passes the strict feasibility gate end to end. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 6f959cd commit ffee838

2 files changed

Lines changed: 67 additions & 5 deletions

File tree

‎demos/hard/showcase.py‎

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
66
Class 1 — characterful scrolling : KineticMarquee, WaveRider, SplitFlap
77
Class 2 — theatrical transitions : IrisSnap, VenetianShutters, MosaicResolve,
8-
CRTCollapse, LightSlitRewrite
8+
CRTCollapse, LightSlitRewrite, ColumnRain,
9+
DropFromSky (slides in from any edge)
910
Class 3 — palette-animated text : RainbowChase, NeonTubeCrawl, ChromeSheen,
1011
HazardStripes
1112
@@ -44,6 +45,7 @@
4445
from scrollkit.effects.scrolling import KineticMarquee, WaveRider, SplitFlap
4546
from scrollkit.effects.transitions import (
4647
IrisSnap, VenetianShutters, MosaicResolve, CRTCollapse, LightSlitRewrite,
48+
ColumnRain, DropFromSky,
4749
)
4850

4951

@@ -229,6 +231,64 @@ class LightSlitScene(_TransitionScene):
229231
slit_color=0xFFFFFF))
230232

231233

234+
class ColumnRainScene(_TransitionScene):
235+
LABEL = "RAIN"
236+
FACTORY = staticmethod(lambda: ColumnRain(cover_color=0x102840))
237+
238+
239+
class _Pos:
240+
"""A minimal content stand-in (just x/y) for DropFromSky to animate in a scene."""
241+
242+
def __init__(self, x, y):
243+
self.x = x
244+
self.y = y
245+
246+
247+
class DropScene(_Scene):
248+
"""Announce, then slide a screen of content in from each edge with DropFromSky —
249+
top, then bottom, left, right — showing the new directional entry. DropFromSky
250+
is duck-typed (not an OverlayMask transition): it animates the content's own
251+
x/y via pre_render_hook, so the scene draws the text at that moving position."""
252+
253+
CATEGORY = "TRANSITION"
254+
LABEL = "DROP IN"
255+
STEPS = (("TOP", 0xFFCC22, "top"),
256+
("BOTTOM", 0x66FF88, "bottom"),
257+
("LEFT", 0xFF66AA, "left"),
258+
("RIGHT", 0x66CCFF, "right"))
259+
HOLD = 16 # frames to hold each landed screen before the next
260+
261+
async def setup(self, display):
262+
self._i = 0
263+
self._hold = 0
264+
self._pos = _Pos(0, CONTENT_Y)
265+
self._t = DropFromSky(direction=self.STEPS[0][2])
266+
await self._t.start(display, lambda: None)
267+
268+
async def run(self, display):
269+
label, color, _dir = self.STEPS[self._i]
270+
# Reset to the natural centred position, let the transition pull it to the
271+
# entry edge for this frame, draw the text there, then advance/restore.
272+
self._pos.x = _center_x(display, label)
273+
self._pos.y = CONTENT_Y
274+
self._t.pre_render_hook(self._pos)
275+
await display.draw_text(label, self._pos.x, self._pos.y, color)
276+
await self._t.render(display, self._pos)
277+
if self._t.is_complete:
278+
if self._hold < self.HOLD:
279+
self._hold += 1
280+
else:
281+
self._hold = 0
282+
self._i += 1
283+
if self._i < len(self.STEPS):
284+
self._t = DropFromSky(direction=self.STEPS[self._i][2])
285+
await self._t.start(display, lambda: None)
286+
287+
@property
288+
def _effect_done(self):
289+
return self._i >= len(self.STEPS)
290+
291+
232292
# --- Class 3: palette-animated bitmap text ----------------------------------
233293

234294
class _PaletteScene(_Scene):
@@ -281,7 +341,8 @@ class ShowcaseReel(DisplayContent):
281341

282342
SCENES = [
283343
MarqueeScene, WaveScene, FlapScene, # Class 1
284-
IrisScene, VenetianScene, MosaicScene, CRTScene, LightSlitScene, # Class 2
344+
IrisScene, VenetianScene, MosaicScene, CRTScene, LightSlitScene,
345+
ColumnRainScene, DropScene, # Class 2
285346
RainbowScene, NeonScene, ChromeScene, HazardScene, # Class 3
286347
]
287348

‎test/unit/demos/test_showcase_reel.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
"""US7 — the showcase reel runs strict end-to-end and demos every signature effect.
22
33
Loads the actual ``demos/hard/showcase.py`` reel and verifies:
4-
- it visits ALL 13 announced scenes (3 scrollers + 5 transitions + 4 palette
5-
effects + the easing meters), and
4+
- it visits ALL 14 announced scenes (3 scrollers + 7 transitions + 4 palette
5+
effects), and
66
- it cleans up its effect layers between scenes (no layer leak), and
77
- a full pass passes the strict feasibility gate at 20 fps end to end.
88
"""
@@ -35,7 +35,7 @@ def _load_demo():
3535
# Enough frames to complete one full pass through every scene (and wrap), measured
3636
# from the reel pacing — so every scene boundary (layer attach/detach + the strict
3737
# gate) is exercised, not just the first few.
38-
FULL_PASS_FRAMES = 1700
38+
FULL_PASS_FRAMES = 2300
3939

4040

4141
def _reel_app():
@@ -93,5 +93,6 @@ def test_reel_advertises_every_signature_effect():
9393
labels = [s.LABEL for s in DEMO.ShowcaseReel().SCENES]
9494
for expected in ["MARQUEE", "WAVERIDER", "SPLITFLAP",
9595
"IRIS", "VENETIAN", "MOSAIC", "CRT FOLD", "LIGHTSLIT",
96+
"RAIN", "DROP IN",
9697
"RAINBOW", "NEON TUBE", "CHROME", "HAZARD"]:
9798
assert expected in labels, expected

0 commit comments

Comments
 (0)