TUI: Statusleiste mit Akkustand und Uhrzeit

Neues diora_tui/statusbar.py: StatusBar-Widget, unten rechts auf Bibliotheks-
und Reader-Screen, zeigt Akkustand (psutil.sensors_battery(), degradiert
sauber zu reiner Uhrzeit auf Geräten ohne Akku) + aktuelle Uhrzeit,
sekündlich aktualisiert.

Getestet: Anzeige und Aktualisierung auf beiden Screens verifiziert.
This commit is contained in:
marwin 2026-08-15 20:36:54 +02:00
parent b1a04d2a65
commit 20d04361bc
6 changed files with 61 additions and 0 deletions

View file

@ -44,6 +44,9 @@ Tastenkürzel:
Der Fließtext ist auf 120 Zeichen Breite begrenzt und horizontal zentriert (lesbarer als Der Fließtext ist auf 120 Zeichen Breite begrenzt und horizontal zentriert (lesbarer als
volle Terminalbreite bei breiten Fenstern). volle Terminalbreite bei breiten Fenstern).
Unten rechts zeigt eine Statusleiste Akkustand (falls vorhanden, via `psutil`) und Uhrzeit,
sekündlich aktualisiert, auf Bibliotheks- und Reader-Ansicht.
Die Bibliotheksansicht ist nach zuletzt geöffnetem Buch sortiert (neueste zuerst; anhand Die Bibliotheksansicht ist nach zuletzt geöffnetem Buch sortiert (neueste zuerst; anhand
des Zeitstempels der zuletzt gespeicherten Position), und blendet gelesene Bücher des Zeitstempels der zuletzt gespeicherten Position), und blendet gelesene Bücher
standardmäßig aus (`EBook.is_read` aus dem Sync-Snapshot, lokal in `library.json` standardmäßig aus (`EBook.is_read` aus dem Sync-Snapshot, lokal in `library.json`

View file

@ -17,6 +17,7 @@ from textual.widgets import Footer, Header, Label, ListItem, ListView
from . import config as config_mod from . import config as config_mod
from . import crypto, epub, library_meta, progress, remote from . import crypto, epub, library_meta, progress, remote
from .reader_screen import ReaderScreen from .reader_screen import ReaderScreen
from .statusbar import StatusBar
DEFAULT_LIBRARY = Path.home() / "Books" DEFAULT_LIBRARY = Path.home() / "Books"
@ -38,6 +39,7 @@ class LibraryScreen(Screen):
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield Header() yield Header()
yield ListView(id="library-list") yield ListView(id="library-list")
yield StatusBar()
yield Footer() yield Footer()
def on_mount(self) -> None: def on_mount(self) -> None:

View file

@ -2,6 +2,15 @@
height: 1fr; height: 1fr;
} }
StatusBar {
dock: bottom;
height: 1;
padding: 0 1;
background: $panel;
color: $text-muted;
text-align: right;
}
#reader-scroll { #reader-scroll {
height: 1fr; height: 1fr;
padding: 1 2; padding: 1 2;

View file

@ -25,6 +25,7 @@ from textual.widgets import Footer, Header, Label, LoadingIndicator, Static
from . import api, blocks, cache, config as config_mod, layout, progress from . import api, blocks, cache, config as config_mod, layout, progress
from .book_view import ContinuousBookView from .book_view import ContinuousBookView
from .statusbar import StatusBar
MAX_LINE_WIDTH = 120 MAX_LINE_WIDTH = 120
AUTOSAVE_INTERVAL = 5.0 AUTOSAVE_INTERVAL = 5.0
@ -88,6 +89,7 @@ class ReaderScreen(Screen):
# nothing to clamp against and silently resets to 0. # nothing to clamp against and silently resets to 0.
self._book_view = ContinuousBookView(_EMPTY_LAYOUT) self._book_view = ContinuousBookView(_EMPTY_LAYOUT)
yield self._book_view yield self._book_view
yield StatusBar()
yield Footer() yield Footer()
def on_mount(self) -> None: def on_mount(self) -> None:

View file

@ -0,0 +1,44 @@
"""Bottom status bar: battery level + current time, refreshed every second.
Battery is read via psutil (cross-platform); on a desktop machine with no
battery, psutil.sensors_battery() returns None and the bar just shows time.
"""
from __future__ import annotations
from datetime import datetime
from textual.widgets import Static
try:
import psutil
except ImportError:
psutil = None # battery display degrades gracefully to time-only
class StatusBar(Static):
def on_mount(self) -> None:
self._refresh()
self.set_interval(1.0, self._refresh)
def _refresh(self) -> None:
self.update(self._render_text())
def _render_text(self) -> str:
parts = []
battery = self._battery_text()
if battery:
parts.append(battery)
parts.append(datetime.now().strftime("%H:%M:%S"))
return " ".join(parts)
def _battery_text(self) -> str | None:
if psutil is None:
return None
try:
battery = psutil.sensors_battery()
except Exception:
return None
if battery is None:
return None
state = "lädt" if battery.power_plugged else "Akku"
return f"{state} {round(battery.percent)}%"

View file

@ -11,6 +11,7 @@ dependencies = [
"platformdirs>=4.0", "platformdirs>=4.0",
"requests>=2.31", "requests>=2.31",
"cryptography>=42.0", "cryptography>=42.0",
"psutil>=5.9",
] ]
[project.scripts] [project.scripts]