birdcage/tui/scripts/capture_screenshots.py

161 lines
4.6 KiB
Python
Raw Permalink Normal View History

"""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())