""" haTerm - v0.1 (c) Sophia Schmidhofer haTerm is a Terminal Based hafas client, using the ivb endpoint. Prompt_toolkit (https://github.com/prompt-toolkit/python-prompt-toolkit) will be used to render a terminal user interface (TUI). The application will provide routing information and station departures/arrivals. Route planning UI. Wrapped in a function so it can be launched from `main.py` with a shared `HafasClient` instance from `backend.py`. Note: This module is a minimal TUI stub. Many features are not implemented yet (station lookup, route rendering). Add TODOs where appropriate. """ import datetime from prompt_toolkit import Application, prompt from prompt_toolkit.buffer import Buffer from prompt_toolkit.layout.containers import HSplit, VSplit, Window from prompt_toolkit.formatted_text import HTML from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl from prompt_toolkit.layout.layout import Layout from prompt_toolkit.key_binding import KeyBindings from prompt_toolkit.shortcuts import yes_no_dialog import backend # ── Colour palette ──────────────────────────────────────────────────────────── BLUE_DARK = "#0d1f3c" # header / footer background BLUE_MID = "#1b4b9a" # accent / separator BLUE_LIGHT = "#add4ff" # secondary text (clock date, labels) WHITE = "#e8f0fe" # primary text GREY = "#4a5568" # muted text / disabled PURPLE = "#a78bfa" # journey-path dot trail BG_MAIN = "#0a0f1e" # main body background BG_ROW = "#111827" # alternating row tint # ───────────────────────────────────────────────────────────────────────────── def run_route_planning(hafas: backend.HafasClient | None = None): """Run the route planning TUI. Parameters: - hafas: optional shared HafasClient instance. If omitted, a new one is created. Prefer passing the shared client from `main.py`. """ if hafas is None: hafas = backend.HafasClient() kb = KeyBindings() # ── Input phase (before TUI) ────────────────────────────────────────────── start_input = prompt("Von: ") end_input = prompt("Nach: ") station1 = hafas.getStationNames(start_input) station2 = hafas.getStationNames(end_input) # Resolved station names (fall back to raw input if lookup fails) start_name = station1[0][0] if station1 else start_input end_name = station2[0][0] if station2 else end_input # TODO: fetch actual departure / arrival times from `hafas`. # TODO: fetch actual journey duration from `hafas`. # ── Buffers ─────────────────────────────────────────────────────────────── start_buf = Buffer() start_buf.text = start_name etd_buf = Buffer() etd_buf.text = "──:──" # TODO: real departure time end_buf = Buffer() end_buf.text = end_name eta_buf = Buffer() eta_buf.text = "──:──" # TODO: real arrival time # ── Dynamic text helpers ────────────────────────────────────────────────── def clock_text(): now = datetime.datetime.now() return HTML( f'' f'' ) def header_text(): return HTML( f'' f'' ) def hint_text(): return HTML( f'' f'' ) def label(text): """Left-aligned label in the narrow column.""" return FormattedTextControl( HTML(f'') ) def sublabel(text): """Muted sublabel for times.""" return FormattedTextControl( HTML(f'') ) # ── Layout ──────────────────────────────────────────────────────────────── LABEL_W = 6 # width of the label column root_container = HSplit([ # ── Header bar ──────────────────────────────────────────────────────── Window( height=1, content=FormattedTextControl(header_text), style=f"bg:{BLUE_DARK}", ), Window(height=1, char="─", style=f"fg:{BLUE_MID} bg:{BG_MAIN}"), # ── Spacer ──────────────────────────────────────────────────────────── Window(height=1, char=" ", style=f"bg:{BG_MAIN}"), # ── Departure row ───────────────────────────────────────────────────── VSplit([ Window(width=LABEL_W, content=label("Von"), style=f"bg:{BG_ROW}"), Window( content=BufferControl(buffer=start_buf, focusable=True), style=f"fg:{WHITE} bg:{BG_ROW}", ), ], height=1), VSplit([ Window(width=LABEL_W, content=sublabel("Ab"), style=f"bg:{BG_ROW}"), Window( content=BufferControl(buffer=etd_buf, focusable=False), style=f"fg:{GREY} bg:{BG_ROW}", ), ], height=1), # ── Journey path (dot trail) ─────────────────────────────────────────── Window(height=1, char=" ", style=f"bg:{BG_MAIN}"), Window( height=1, content=FormattedTextControl( HTML(f'') ), style=f"bg:{BG_MAIN}", ), Window(height=1, char=" ", style=f"bg:{BG_MAIN}"), # ── Arrival row ─────────────────────────────────────────────────────── VSplit([ Window(width=LABEL_W, content=label("Nach"), style=f"bg:{BG_ROW}"), Window( content=BufferControl(buffer=end_buf, focusable=True), style=f"fg:{WHITE} bg:{BG_ROW}", ), ], height=1), VSplit([ Window(width=LABEL_W, content=sublabel("An"), style=f"bg:{BG_ROW}"), Window( content=BufferControl(buffer=eta_buf, focusable=False), style=f"fg:{GREY} bg:{BG_ROW}", ), ], height=1), # ── Spacer ──────────────────────────────────────────────────────────── Window(char=" ", style=f"bg:{BG_MAIN}"), # ── Clock bar ───────────────────────────────────────────────────────── Window(height=1, char="─", style=f"fg:{BLUE_MID} bg:{BG_MAIN}"), Window( content=FormattedTextControl(clock_text), height=1, style=f"bg:{BLUE_DARK}", ), # ── Keybinding hint bar ─────────────────────────────────────────────── Window( height=1, content=FormattedTextControl(hint_text), style=f"bg:{BLUE_DARK}", ), ]) # ── Key bindings ────────────────────────────────────────────────────────── @kb.add("c-e") def _(event): confirmed = yes_no_dialog( title="Programm beenden", text="haTerm schließen?", ).run() if confirmed: event.app.exit() # ── Run ─────────────────────────────────────────────────────────────────── layout = Layout(root_container) app = Application( layout=layout, key_bindings=kb, full_screen=True, refresh_interval=1.0, # keeps the clock ticking mouse_support=False, ) try: app.run() except KeyboardInterrupt: return