"""Capture all TUI screenshots for documentation. Runs the Birdcage TUI in demo mode using Textual's Pilot API, navigates to each screen/sub-mode, and exports SVG + PNG screenshots. Usage: cd tui && uv run python scripts/capture_screenshots.py Output goes to ../site/public/screenshots/ """ import asyncio import subprocess import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src")) from birdcage_tui.app import BirdcageApp OUTPUT_DIR = ( Path(__file__).resolve().parent.parent.parent / "site" / "public" / "screenshots" ) TERMINAL_SIZE = (120, 42) # Demo search results for craft-search screenshot DEMO_SEARCH_RESULTS = [ { "name": "ISS (ZARYA)", "target_type": "satellite", "target_id": "25544", "groups": ["stations"], }, { "name": "NOAA 19", "target_type": "satellite", "target_id": "33591", "groups": ["weather"], }, { "name": "SO-50 (SAUDISAT 1C)", "target_type": "satellite", "target_id": "27607", "groups": ["amateur"], }, { "name": "TEVEL-2", "target_type": "satellite", "target_id": "50988", "groups": ["amateur"], }, { "name": "AO-91 (FOX-1B)", "target_type": "satellite", "target_id": "43017", "groups": ["amateur"], }, ] def save(name: str, svg: str) -> None: """Write SVG and convert to PNG via rsvg-convert.""" svg_path = OUTPUT_DIR / f"{name}.svg" png_path = OUTPUT_DIR / f"{name}.png" svg_path.write_text(svg) print(f" {svg_path.name}") try: subprocess.run( ["rsvg-convert", "-o", str(png_path), str(svg_path)], check=True, capture_output=True, ) print(f" {png_path.name}") except FileNotFoundError: print(" WARNING: rsvg-convert not found, skipping PNG") async def capture_all() -> None: OUTPUT_DIR.mkdir(parents=True, exist_ok=True) app = BirdcageApp() app.demo_mode = True async with app.run_test(size=TERMINAL_SIZE) as pilot: await pilot.pause(1.0) # ---- F1 Dashboard ---- print("F1 Dashboard") await pilot.press("f1") await pilot.pause(0.5) save("tui-dashboard", app.export_screenshot()) # ---- F2 Control (Manual mode) ---- print("F2 Control") await pilot.press("f2") await pilot.pause(0.5) save("tui-control", app.export_screenshot()) # ---- F2 Control > Craft (search results) ---- print("F2 Control > Craft (search)") craft_mode_btn = app.query_one("#mode-craft") craft_mode_btn.press() await pilot.pause(0.5) # Populate search results directly via widget API craft_panel = app.query_one("#ctrl-craft-panel") craft_panel.set_search_results(DEMO_SEARCH_RESULTS) await pilot.pause(0.3) save("tui-craft-search", app.export_screenshot()) # ---- F2 Control > Craft (tracking Moon) ---- print("F2 Control > Craft (tracking)") # Clear results and set tracking state craft_panel.set_search_results([]) craft_panel.set_tracking_status( state="TRACKING", target_name="Moon", azimuth=145.00, elevation=32.01, distance_km=384400, range_rate=-0.3, moves=47, rate=1.0, ) await pilot.pause(0.3) save("tui-craft-tracking", app.export_screenshot()) # ---- F3 Signal (Monitor mode) ---- print("F3 Signal") await pilot.press("f3") await pilot.pause(0.5) save("tui-signal", app.export_screenshot()) # ---- F4 System ---- print("F4 System") await pilot.press("f4") await pilot.pause(0.5) save("tui-system", app.export_screenshot()) # ---- F5 Console overlay ---- print("F5 Console") await pilot.press("f5") await pilot.pause(0.5) save("tui-console", app.export_screenshot()) await pilot.press("f5") await pilot.pause(0.3) # ---- F6 Camera overlay ---- print("F6 Camera") await pilot.press("f6") await pilot.pause(0.5) save("tui-camera", app.export_screenshot()) await pilot.press("f6") await pilot.pause(0.3) print("\nDone!") if __name__ == "__main__": asyncio.run(capture_all())