From e27f3470a43e21aff22e96e82316f69fa989accd Mon Sep 17 00:00:00 2001 From: rr- Date: Thu, 13 Dec 2018 23:24:52 +0100 Subject: [PATCH] ui: rename Ui class to UrwidUserInterface --- pqcli/__main__.py | 4 +- pqcli/ui/__init__.py | 167 -------------------- pqcli/ui/urwid/__init__.py | 167 ++++++++++++++++++++ pqcli/ui/{ => urwid}/confirm_dialog.py | 6 +- pqcli/ui/{ => urwid}/custom_button.py | 0 pqcli/ui/{ => urwid}/custom_line_box.py | 0 pqcli/ui/{ => urwid}/custom_progress_bar.py | 0 pqcli/ui/{ => urwid}/data_table.py | 2 +- pqcli/ui/{ => urwid}/double_line_box.py | 2 +- pqcli/ui/{ => urwid}/game_view.py | 14 +- pqcli/ui/{ => urwid}/layout.py | 0 pqcli/ui/{ => urwid}/new_game_view.py | 6 +- pqcli/ui/{ => urwid}/read_only_check_box.py | 0 pqcli/ui/{ => urwid}/roster_view.py | 4 +- pqcli/ui/{ => urwid}/scrollable.py | 0 15 files changed, 186 insertions(+), 186 deletions(-) create mode 100644 pqcli/ui/urwid/__init__.py rename pqcli/ui/{ => urwid}/confirm_dialog.py (88%) rename pqcli/ui/{ => urwid}/custom_button.py (100%) rename pqcli/ui/{ => urwid}/custom_line_box.py (100%) rename pqcli/ui/{ => urwid}/custom_progress_bar.py (100%) rename pqcli/ui/{ => urwid}/data_table.py (97%) rename pqcli/ui/{ => urwid}/double_line_box.py (95%) rename pqcli/ui/{ => urwid}/game_view.py (97%) rename pqcli/ui/{ => urwid}/layout.py (100%) rename pqcli/ui/{ => urwid}/new_game_view.py (97%) rename pqcli/ui/{ => urwid}/read_only_check_box.py (100%) rename pqcli/ui/{ => urwid}/roster_view.py (96%) rename pqcli/ui/{ => urwid}/scrollable.py (100%) diff --git a/pqcli/__main__.py b/pqcli/__main__.py index ce7cb14..6de76cb 100644 --- a/pqcli/__main__.py +++ b/pqcli/__main__.py @@ -4,7 +4,7 @@ from pathlib import Path import xdg from pqcli.roster import Roster -from pqcli.ui import Ui +from pqcli.ui.urwid import UrwidUserInterface SAVE_PATH = Path(xdg.XDG_CONFIG_HOME) / "pqcli" / "save.dat" @@ -35,7 +35,7 @@ def main() -> None: roster = Roster.load(SAVE_PATH) try: - Ui(roster, args).run() + UrwidUserInterface(roster, args).run() finally: if args.use_saves: roster.save() diff --git a/pqcli/ui/__init__.py b/pqcli/ui/__init__.py index 316401d..e69de29 100644 --- a/pqcli/ui/__init__.py +++ b/pqcli/ui/__init__.py @@ -1,167 +0,0 @@ -import argparse -import json -import typing as T -from pathlib import Path - -import urwid -import xdg - -from pqcli import random -from pqcli.mechanic import Player -from pqcli.roster import Roster -from pqcli.ui.confirm_dialog import ConfirmDialog -from pqcli.ui.game_view import GameView -from pqcli.ui.new_game_view import NewGameView -from pqcli.ui.roster_view import RosterView - - -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(ConfirmDialog): - 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 Ui: - def __init__(self, roster: Roster, args: argparse.Namespace) -> None: - bind_commands() - - if args.use_config: - if not load_palette(): - save_palette() # create a file for the user to edit - - self.roster = roster - self.args = args - 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, player, self.args) - self._connect("cancel", self.switch_to_roster_view) - - def switch_to_delete_player_view(self, player_idx: int) -> None: - adjective = random.choice(["faithful", "noble", "loyal", "brave"]) - player_name = self.roster.players[player_idx].name - - self.old_views.append(self.loop.widget) - self.loop.widget = ConfirmDialog( - f"Terminate {adjective} {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) diff --git a/pqcli/ui/urwid/__init__.py b/pqcli/ui/urwid/__init__.py new file mode 100644 index 0000000..adcffec --- /dev/null +++ b/pqcli/ui/urwid/__init__.py @@ -0,0 +1,167 @@ +import argparse +import json +import typing as T +from pathlib import Path + +import urwid +import xdg + +from pqcli import random +from pqcli.mechanic import Player +from pqcli.roster import Roster +from pqcli.ui.urwid.confirm_dialog import ConfirmDialog +from pqcli.ui.urwid.game_view import GameView +from pqcli.ui.urwid.new_game_view import NewGameView +from pqcli.ui.urwid.roster_view import RosterView + + +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(ConfirmDialog): + 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: + def __init__(self, roster: Roster, args: argparse.Namespace) -> None: + bind_commands() + + if args.use_config: + if not load_palette(): + save_palette() # create a file for the user to edit + + self.roster = roster + self.args = args + 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, player, self.args) + self._connect("cancel", self.switch_to_roster_view) + + def switch_to_delete_player_view(self, player_idx: int) -> None: + adjective = random.choice(["faithful", "noble", "loyal", "brave"]) + player_name = self.roster.players[player_idx].name + + self.old_views.append(self.loop.widget) + self.loop.widget = ConfirmDialog( + f"Terminate {adjective} {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) diff --git a/pqcli/ui/confirm_dialog.py b/pqcli/ui/urwid/confirm_dialog.py similarity index 88% rename from pqcli/ui/confirm_dialog.py rename to pqcli/ui/urwid/confirm_dialog.py index 5f619b6..d68cf8e 100644 --- a/pqcli/ui/confirm_dialog.py +++ b/pqcli/ui/urwid/confirm_dialog.py @@ -2,9 +2,9 @@ import typing as T import urwid -from pqcli.ui.custom_button import CustomButton -from pqcli.ui.custom_line_box import CustomLineBox -from pqcli.ui.layout import NPile +from pqcli.ui.urwid.custom_button import CustomButton +from pqcli.ui.urwid.custom_line_box import CustomLineBox +from pqcli.ui.urwid.layout import NPile class ConfirmDialog(urwid.Overlay): diff --git a/pqcli/ui/custom_button.py b/pqcli/ui/urwid/custom_button.py similarity index 100% rename from pqcli/ui/custom_button.py rename to pqcli/ui/urwid/custom_button.py diff --git a/pqcli/ui/custom_line_box.py b/pqcli/ui/urwid/custom_line_box.py similarity index 100% rename from pqcli/ui/custom_line_box.py rename to pqcli/ui/urwid/custom_line_box.py diff --git a/pqcli/ui/custom_progress_bar.py b/pqcli/ui/urwid/custom_progress_bar.py similarity index 100% rename from pqcli/ui/custom_progress_bar.py rename to pqcli/ui/urwid/custom_progress_bar.py diff --git a/pqcli/ui/data_table.py b/pqcli/ui/urwid/data_table.py similarity index 97% rename from pqcli/ui/data_table.py rename to pqcli/ui/urwid/data_table.py index a05995c..7d74f51 100644 --- a/pqcli/ui/data_table.py +++ b/pqcli/ui/urwid/data_table.py @@ -2,7 +2,7 @@ import typing as T import urwid -from pqcli.ui.layout import NColumns, NPile +from pqcli.ui.urwid.layout import NColumns, NPile class DataTable(NPile): diff --git a/pqcli/ui/double_line_box.py b/pqcli/ui/urwid/double_line_box.py similarity index 95% rename from pqcli/ui/double_line_box.py rename to pqcli/ui/urwid/double_line_box.py index 95caf3e..a25c8fb 100644 --- a/pqcli/ui/double_line_box.py +++ b/pqcli/ui/urwid/double_line_box.py @@ -1,6 +1,6 @@ import urwid -from pqcli.ui.layout import NPile +from pqcli.ui.urwid.layout import NPile class DoubleLineBox(urwid.AttrMap): diff --git a/pqcli/ui/game_view.py b/pqcli/ui/urwid/game_view.py similarity index 97% rename from pqcli/ui/game_view.py rename to pqcli/ui/urwid/game_view.py index 0e22152..7403e1e 100644 --- a/pqcli/ui/game_view.py +++ b/pqcli/ui/urwid/game_view.py @@ -14,13 +14,13 @@ from pqcli.mechanic import ( Simulation, Spell, ) -from pqcli.ui.custom_line_box import CustomLineBox -from pqcli.ui.custom_progress_bar import CustomProgressBar -from pqcli.ui.data_table import DataTable -from pqcli.ui.double_line_box import DoubleLineBox -from pqcli.ui.layout import NColumns, NPile -from pqcli.ui.read_only_check_box import ReadOnlyCheckBox -from pqcli.ui.scrollable import Scrollable, ScrollBar +from pqcli.ui.urwid.custom_line_box import CustomLineBox +from pqcli.ui.urwid.custom_progress_bar import CustomProgressBar +from pqcli.ui.urwid.data_table import DataTable +from pqcli.ui.urwid.double_line_box import DoubleLineBox +from pqcli.ui.urwid.layout import NColumns, NPile +from pqcli.ui.urwid.read_only_check_box import ReadOnlyCheckBox +from pqcli.ui.urwid.scrollable import Scrollable, ScrollBar class CharacterSheetView(DoubleLineBox): diff --git a/pqcli/ui/layout.py b/pqcli/ui/urwid/layout.py similarity index 100% rename from pqcli/ui/layout.py rename to pqcli/ui/urwid/layout.py diff --git a/pqcli/ui/new_game_view.py b/pqcli/ui/urwid/new_game_view.py similarity index 97% rename from pqcli/ui/new_game_view.py rename to pqcli/ui/urwid/new_game_view.py index a0dbfbd..45816a1 100644 --- a/pqcli/ui/new_game_view.py +++ b/pqcli/ui/urwid/new_game_view.py @@ -6,9 +6,9 @@ import urwid_readline from pqcli.config import CLASSES, PRIME_STATS, RACES from pqcli.mechanic import StatsBuilder, create_player, generate_name -from pqcli.ui.custom_button import CustomButton -from pqcli.ui.custom_line_box import CustomLineBox -from pqcli.ui.layout import NColumns, NPile +from pqcli.ui.urwid.custom_button import CustomButton +from pqcli.ui.urwid.custom_line_box import CustomLineBox +from pqcli.ui.urwid.layout import NColumns, NPile class StatsBox(CustomLineBox): diff --git a/pqcli/ui/read_only_check_box.py b/pqcli/ui/urwid/read_only_check_box.py similarity index 100% rename from pqcli/ui/read_only_check_box.py rename to pqcli/ui/urwid/read_only_check_box.py diff --git a/pqcli/ui/roster_view.py b/pqcli/ui/urwid/roster_view.py similarity index 96% rename from pqcli/ui/roster_view.py rename to pqcli/ui/urwid/roster_view.py index 7f7eb64..eb112db 100644 --- a/pqcli/ui/roster_view.py +++ b/pqcli/ui/urwid/roster_view.py @@ -4,8 +4,8 @@ import urwid from pqcli.lingo import act_name, to_roman from pqcli.roster import Roster -from pqcli.ui.custom_button import CustomButton -from pqcli.ui.layout import NColumns, NPile +from pqcli.ui.urwid.custom_button import CustomButton +from pqcli.ui.urwid.layout import NColumns, NPile class RosterView(urwid.Filler): diff --git a/pqcli/ui/scrollable.py b/pqcli/ui/urwid/scrollable.py similarity index 100% rename from pqcli/ui/scrollable.py rename to pqcli/ui/urwid/scrollable.py