Files
ProgressQuest-cli/pqcli/ui/urwid/__init__.py
T
2019-01-13 16:33:07 +01:00

165 lines
5.2 KiB
Python

import argparse
import json
import typing as T
from pathlib import Path
import urwid
import xdg
from pqcli import lingo, random
from pqcli.mechanic import Player
from pqcli.roster import Roster
from pqcli.ui.urwid.views import ConfirmView, GameView, NewGameView, RosterView
from ..base import BaseUserInterface
TICK_FREQ = 0.1
SAVE_FREQ = 60.0
PALETTE: T.List[T.Tuple[str, ...]] = [
("button", "", ""),
("button-focus", "light red", "black"),
("linebox", "", ""),
("linebox-focus", "light red", "black"),
("linebox-content", "", ""),
("linebox-content-focus", "", ""),
("progressbar-normal", "", "", "standout"),
("progressbar-done", "black", "light red"),
("progressbar-smooth", "light red", ""),
("scrollbar", "light gray", "black"),
("scrollbar-focus", "light red", "black"),
("scrollable", "", ""),
("scrollable-focus", "", ""),
]
PALETTE_PATH = Path(xdg.XDG_CONFIG_HOME) / "pqcli" / "palette.json"
def load_palette() -> bool:
global PALETTE
if PALETTE_PATH.exists():
PALETTE = json.loads(PALETTE_PATH.read_text())
return True
return False
def save_palette() -> None:
global PALETTE
PALETTE_PATH.parent.mkdir(exist_ok=True, parents=True)
PALETTE_PATH.write_text(json.dumps(PALETTE, indent=4))
class ConfirmExitDialog(ConfirmView):
def __init__(self, old_view: urwid.Widget) -> None:
super().__init__("Really quit?", old_view)
def bind_commands() -> None:
for key, direction in {
"k": "up",
"j": "down",
"h": "left",
"l": "right",
}.items():
for key_variant in {key, key.upper()}:
urwid.command_map[key_variant] = f"cursor {direction}"
class UrwidUserInterface(BaseUserInterface):
def __init__(self, roster: Roster, args: argparse.Namespace) -> None:
super().__init__(roster, args)
bind_commands()
if args.use_config:
if not load_palette():
save_palette() # create a file for the user to edit
self.loop = urwid.MainLoop(
None, PALETTE, unhandled_input=self.unhandled_input
)
self.old_views: T.List[urwid.Widget] = []
self.switch_to_roster_view()
def run(self) -> None:
self.loop.set_alarm_in(TICK_FREQ, self.on_tick)
self.loop.set_alarm_in(SAVE_FREQ, self.on_save)
self.loop.run()
def unhandled_input(self, key: str) -> bool:
callback = getattr(self.loop.widget, "unhandled_input", None)
if callback and callback(key):
return True
if key == "ctrl q":
self.switch_to_exit_view()
return True
return False
def switch_to_roster_view(self) -> None:
self.loop.widget = RosterView(self.roster)
self._connect("load_game", self.switch_to_game_view)
self._connect("new_game", self.switch_to_new_game_view)
self._connect("delete_game", self.switch_to_delete_player_view)
self._connect("exit", self.switch_to_exit_view)
def switch_to_exit_view(self) -> None:
if isinstance(self.loop.widget, ConfirmExitDialog):
return
self.old_views.append(self.loop.widget)
self.loop.widget = ConfirmExitDialog(self.old_views[-1])
self._connect("confirm", self.exit)
self._connect("cancel", self.cancel_dialog)
def switch_to_new_game_view(self) -> None:
self.loop.widget = NewGameView()
self._connect("confirm", self.create_player)
self._connect("cancel", self.switch_to_roster_view)
def create_player(self, player: Player) -> None:
self.roster.players.append(player)
self.switch_to_roster_view()
def delete_player(self, player_idx: int) -> None:
del self.roster.players[player_idx]
self.switch_to_roster_view()
def switch_to_game_view(self, player_idx: int) -> None:
player = self.roster.players[player_idx]
self.loop.widget = GameView(self.loop, self.roster, player, self.args)
self._connect("cancel", self.switch_to_roster_view)
def switch_to_delete_player_view(self, player_idx: int) -> None:
player_name = self.roster.players[player_idx].name
self.old_views.append(self.loop.widget)
self.loop.widget = ConfirmView(
lingo.terminate_message(player_name), self.old_views[-1]
)
self._connect("confirm", lambda: self.delete_player(player_idx))
self._connect("cancel", self.cancel_dialog)
def exit(self) -> None:
raise urwid.ExitMainLoop()
def cancel_dialog(self) -> None:
assert len(self.old_views)
self.loop.widget = self.old_views.pop()
def _connect(self, signal_name: str, callback: T.Callable) -> None:
urwid.signals.connect_signal(
self.loop.widget,
signal_name,
lambda _sender, *args: callback(*args),
)
def on_tick(self, _loop: urwid.MainLoop, _user_data: T.Any) -> None:
if hasattr(self.loop.widget, "tick"):
self.loop.widget.tick()
self.loop.set_alarm_in(TICK_FREQ, self.on_tick)
def on_save(self, _loop: urwid.MainLoop, _user_data: T.Any) -> None:
if self.args.use_saves:
self.roster.save()
self.loop.set_alarm_in(SAVE_FREQ, self.on_save)