diff --git a/tui/scripts/capture_screenshots.py b/tui/scripts/capture_screenshots.py new file mode 100644 index 0000000..869a932 --- /dev/null +++ b/tui/scripts/capture_screenshots.py @@ -0,0 +1,160 @@ +"""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())