(sophia) improved ui

This commit is contained in:
mia 2026-06-17 11:00:32 +02:00
parent b2d5832de0
commit 13ac3cbead

View file

@ -15,24 +15,30 @@ Note: This module is a minimal TUI stub. Many features are not implemented
yet (station lookup, route rendering). Add TODOs where appropriate. yet (station lookup, route rendering). Add TODOs where appropriate.
""" """
import datetime import datetime
import time from prompt_toolkit import Application, prompt
import json
from prompt_toolkit import Application
from prompt_toolkit.buffer import Buffer from prompt_toolkit.buffer import Buffer
from prompt_toolkit.layout.containers import HSplit, VSplit, Window from prompt_toolkit.layout.containers import HSplit, VSplit, Window
from prompt_toolkit.formatted_text import HTML from prompt_toolkit.formatted_text import HTML
from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl
from prompt_toolkit.layout.layout import Layout from prompt_toolkit.layout.layout import Layout
from prompt_toolkit.key_binding import KeyBindings from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.shortcuts import message_dialog
from prompt_toolkit.shortcuts import yes_no_dialog from prompt_toolkit.shortcuts import yes_no_dialog
from prompt_toolkit.shortcuts import input_dialog
from prompt_toolkit import prompt
import backend 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): def run_route_planning(hafas: backend.HafasClient | None = None):
"""Run the route planning TUI. """Run the route planning TUI.
@ -43,84 +49,168 @@ def run_route_planning(hafas: backend.HafasClient | None = None):
if hafas is None: if hafas is None:
hafas = backend.HafasClient() hafas = backend.HafasClient()
# TODO: integrate actual station search + routing using `hafas`.
kb = KeyBindings() kb = KeyBindings()
start = prompt("Start: ") # ── Input phase (before TUI) ──────────────────────────────────────────────
end = prompt("Ende: ") start_input = prompt("Von: ")
end_input = prompt("Nach: ")
station1 = hafas.getStationNames(start) station1 = hafas.getStationNames(start_input)
station2 = hafas.getStationNames(end) station2 = hafas.getStationNames(end_input)
startBuffer = Buffer() # Resolved station names (fall back to raw input if lookup fails)
startBuffer.text = start 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`.
#departure = #current time, muss noch abgesprochen werden. # ── Buffers ───────────────────────────────────────────────────────────────
start_buf = Buffer()
start_buf.text = start_name
etd_buf = Buffer()
etd_buf.text = "──:──" # TODO: real departure time
etdBuffer = Buffer() end_buf = Buffer()
etdBuffer.text = "etd" #muss noch alles definiert werden end_buf.text = end_name
eta_buf = Buffer()
eta_buf.text = "──:──" # TODO: real arrival time
# ── Dynamic text helpers ──────────────────────────────────────────────────
endBuffer = Buffer() def clock_text():
endBuffer.text = end
etaBuffer = Buffer()
etaBuffer.text = "eta" #--//--
drivetimeBuffer = Buffer()
#drivetimeBuffer.text = Fahrzeit von Hafas.
infoBuffer = Buffer()
infoBuffer.text = f"Routenplanung von {station1[0][0]} nach {station2[0][0]}"
def get_clock_text():
now = datetime.datetime.now() now = datetime.datetime.now()
return HTML( return HTML(
f'<b> 🕐 {now.strftime("%H:%M:%S")} </b>' f'<style bg="{BLUE_DARK}" fg="{WHITE}"> 🕐 <b>{now.strftime("%H:%M:%S")}</b> </style>'
f'<style fg="#aaddff"> {now.strftime("%A, %d. %B %Y")} </style>' f'<style bg="{BLUE_DARK}" fg="{BLUE_LIGHT}">│ {now.strftime("%A, %d. %B %Y")} </style>'
) )
root_container = HSplit(children=[ def header_text():
return HTML(
f'<style bg="{BLUE_DARK}" fg="{WHITE}"> <b>haTerm</b> </style>'
f'<style bg="{BLUE_DARK}" fg="{BLUE_LIGHT}"> · Routenplanung </style>'
)
HSplit(children=[ def hint_text():
Window(height=2, content=BufferControl(buffer=infoBuffer, focusable=False), style="fg:#2A71D5"), #informationen über Route return HTML(
Window(width=1, height = 1, char='-', style="fg:#2A71D5"), #Trennlinie f'<style bg="{BLUE_DARK}" fg="{GREY}"> Ctrl+E</style>'
Window(content=BufferControl(buffer=startBuffer, focusable=True)), #start Station f'<style bg="{BLUE_DARK}" fg="{BLUE_LIGHT}"> Beenden </style>'
Window(content=BufferControl(buffer=etdBuffer, focusable=False)), #abfahrtszeit )
Window(width = 1, height = 2, char= ".", style= "fg:#A86FD6"), #Trennlinie
Window(content=BufferControl(buffer=endBuffer, focusable=True)), #end Station
Window(content=BufferControl(buffer=etaBuffer, focusable=False)), #ankunftszeit
]), def label(text):
Window(height=1, char=' ', style="bg:#2A71D5 fg:black"), """Left-aligned label in the narrow column."""
return FormattedTextControl(
HTML(f'<style fg="{BLUE_LIGHT}"> {text} </style>')
)
def sublabel(text):
"""Muted sublabel for times."""
return FormattedTextControl(
HTML(f'<style fg="{GREY}"> {text} </style>')
)
# ── Layout ────────────────────────────────────────────────────────────────
LABEL_W = 6 # width of the label column
root_container = HSplit([
# ── Header bar ────────────────────────────────────────────────────────
Window( Window(
content=FormattedTextControl(get_clock_text), height=1,
height=1, content=FormattedTextControl(header_text),
style="bg:#163D7A fg:#aaddff",), 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 fg="{PURPLE}"> {"· " * 18}</style>')
),
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') @kb.add("c-e")
def _(event): def _(event):
result = yes_no_dialog( confirmed = yes_no_dialog(
title='Programm beenden', title="Programm beenden",
text='Fenster schließen?').run() text="haTerm schließen?",
if result == True: ).run()
if confirmed:
event.app.exit() event.app.exit()
# ── Run ───────────────────────────────────────────────────────────────────
layout = Layout(root_container) layout = Layout(root_container)
app = Application(layout=layout, key_bindings=kb, full_screen=True) app = Application(
layout=layout,
key_bindings=kb,
full_screen=True,
refresh_interval=1.0, # keeps the clock ticking
mouse_support=False,
)
try: try:
app.run() app.run()
except KeyboardInterrupt: except KeyboardInterrupt:
# graceful exit on Ctrl-C
return return