diff --git a/pqcli/mechanic.py b/pqcli/mechanic.py index 38a0b72..6ff0597 100644 --- a/pqcli/mechanic.py +++ b/pqcli/mechanic.py @@ -13,7 +13,9 @@ from pqcli.lingo import * logger = logging.getLogger(__name__) -_SIGNALS: T.Dict[T.Tuple[str, str], T.List[T.Callable]] = defaultdict(list) +_SIGNALS: T.Dict[ + T.Tuple["SignalMixin", str], T.List[T.Callable[..., T.Any]] +] = defaultdict(list) class SignalMixin: @@ -21,10 +23,14 @@ class SignalMixin: for callback in _SIGNALS[self, signal_name]: callback(*user_data) - def connect(self, signal_name: str, callback: T.Callable) -> None: + def connect( + self, signal_name: str, callback: T.Callable[..., T.Any] + ) -> None: _SIGNALS[self, signal_name].append(callback) - def disconnect(self, signal_name: str, callback: T.Callable) -> None: + def disconnect( + self, signal_name: str, callback: T.Callable[..., T.Any] + ) -> None: try: idx = _SIGNALS[self, signal_name].index(callback) except ValueError: diff --git a/pqcli/roster.py b/pqcli/roster.py index eecceee..1833ab5 100644 --- a/pqcli/roster.py +++ b/pqcli/roster.py @@ -32,7 +32,7 @@ class Roster: self.path.rename(old_path) tmp_path.rename(self.path) - def save_periodically(self): + def save_periodically(self) -> None: if (datetime.now() - self._last_save).total_seconds() >= 300: logging.info("Saving...") self.save() diff --git a/pqcli/ui/basic/__init__.py b/pqcli/ui/basic/__init__.py index bb13daa..aade811 100644 --- a/pqcli/ui/basic/__init__.py +++ b/pqcli/ui/basic/__init__.py @@ -36,8 +36,8 @@ class BasicUserInterface(BaseUserInterface): def __init__(self, roster: Roster, args: argparse.Namespace) -> None: super().__init__(roster, args) - def signal_handler(sig, frame): - print("Quitting") + def signal_handler(sig: T.Any, frame: T.Any) -> None: + logging.info("Quitting") if self.args.use_saves: self.roster.save() sys.exit(0) @@ -57,7 +57,7 @@ class BasicUserInterface(BaseUserInterface): def logo(self) -> None: print(LOGO) - def main_menu(self): + def main_menu(self) -> None: while True: choice = self.menu( [ @@ -168,7 +168,9 @@ class BasicUserInterface(BaseUserInterface): print("--- Plot ---") print(f"Current act: {lingo.to_roman(player.quest_book.act)}") print(f"Current quest: {player.quest_book.current_quest or '?'}") - print(f"Current task: {player.task.description}") + print( + f"Current task: {player.task.description if player.task else '?'}" + ) def delete_player(self, player: Player) -> None: if self.confirm(lingo.terminate_message(player.name)): diff --git a/pqcli/ui/curses/__init__.py b/pqcli/ui/curses/__init__.py index 51285e9..f1a0f97 100644 --- a/pqcli/ui/curses/__init__.py +++ b/pqcli/ui/curses/__init__.py @@ -38,7 +38,7 @@ class CursesUserInterface(BaseUserInterface): curses.noecho() curses.curs_set(0) - def signal_handler(sig, frame): + def signal_handler(sig: T.Any, frame: T.Any) -> None: if self.args.use_saves: self.roster.save() exit(0) diff --git a/pqcli/ui/curses/views/create_character_view.py b/pqcli/ui/curses/views/create_character_view.py index 6cb63cf..4fdef8b 100644 --- a/pqcli/ui/curses/views/create_character_view.py +++ b/pqcli/ui/curses/views/create_character_view.py @@ -20,6 +20,7 @@ class ChooseCharacterNameView(BaseView): self.on_cancel = EventHandler() self.on_confirm = EventHandler() + self._win: T.Optional[T.Any] = None self._text = character_name or generate_name() def start(self) -> None: @@ -143,6 +144,9 @@ class ChooseCharacterStatsView(BaseView): self.on_cancel = EventHandler() self.on_confirm = EventHandler() + self._win: T.Optional[T.Any] = None + self._stats_win: T.Optional[T.Any] = None + self._stats_builder = StatsBuilder() self._stats = self._stats_builder.roll() @@ -184,7 +188,7 @@ class ChooseCharacterStatsView(BaseView): self._render() def _render(self) -> None: - if not self._win: + if not self._win or not self._stats_win: return y, x = self._win.getbegyx() diff --git a/pqcli/ui/curses/views/game_view/__init__.py b/pqcli/ui/curses/views/game_view/__init__.py index 97f7d9d..58221a1 100644 --- a/pqcli/ui/curses/views/game_view/__init__.py +++ b/pqcli/ui/curses/views/game_view/__init__.py @@ -7,11 +7,12 @@ from pqcli.mechanic import Player, Simulation from pqcli.roster import Roster from pqcli.ui.curses.event_handler import EventHandler from pqcli.ui.curses.util import KEYS_DOWN, KEYS_LEFT, KEYS_RIGHT, KEYS_UP +from pqcli.ui.curses.views.base_view import BaseView from pqcli.ui.curses.widgets import Scrollable, Widget -from ..base_view import BaseView from .character_sheet_window import CharacterSheetWindow from .equipment_window import EquipmentWindow +from .focusable import Focusable from .inventory_window import InventoryWindow from .plot_window import PlotWindow from .quest_book_window import QuestBookWindow @@ -186,14 +187,16 @@ class GameView(BaseView): x=0, ) - self._children: T.List[Widget] = [ + self._focusable_children: T.List[Focusable] = [ self._char_sheet_win, self._spell_book_win, self._equipment_win, self._inventory_win, self._plot_win, self._quest_book_win, - self._task_win, + ] + self._children: T.List[Widget] = self._focusable_children + [ + self._task_win ] def stop(self) -> None: @@ -213,12 +216,12 @@ class GameView(BaseView): curses.doupdate() @property - def focused(self) -> Widget: - for widget in self._children: + def focused(self) -> Focusable: + for widget in self._focusable_children: if widget.focused: return widget - return None + raise AssertionError - def focus(self, widget: Widget): + def focus(self, widget: Focusable) -> None: self.focused.focused = False widget.focused = True diff --git a/pqcli/ui/curses/views/game_view/equipment_window.py b/pqcli/ui/curses/views/game_view/equipment_window.py index e891b58..4c5bd63 100644 --- a/pqcli/ui/curses/views/game_view/equipment_window.py +++ b/pqcli/ui/curses/views/game_view/equipment_window.py @@ -47,7 +47,7 @@ class EquipmentWindow(Focusable, WindowWrapper): if not self._win: return - with self._focus_standout(self._win): + with self.focus_standout(self._win): self._win.box() text = " Equipment " x = max(0, (self.getmaxyx()[1] - len(text)) // 2) diff --git a/pqcli/ui/curses/views/game_view/focusable.py b/pqcli/ui/curses/views/game_view/focusable.py index 0c7d13b..f8ade7a 100644 --- a/pqcli/ui/curses/views/game_view/focusable.py +++ b/pqcli/ui/curses/views/game_view/focusable.py @@ -20,9 +20,9 @@ class Focusable: self._on_focus_change() @contextlib.contextmanager - def _focus_standout(self, win: T.Any) -> T.Generator: + def focus_standout(self, win: T.Any) -> T.Generator: if self._focused: - self._win.standout() + win.standout() yield if self._focused: - self._win.standend() + win.standend() diff --git a/pqcli/ui/curses/views/game_view/progress_bar_window.py b/pqcli/ui/curses/views/game_view/progress_bar_window.py index 46e59a1..aec527b 100644 --- a/pqcli/ui/curses/views/game_view/progress_bar_window.py +++ b/pqcli/ui/curses/views/game_view/progress_bar_window.py @@ -1,6 +1,7 @@ import curses import typing as T +from pqcli.ui.curses.views.game_view.focusable import Focusable from pqcli.ui.curses.widgets import ( DataTable, ListBox, @@ -8,8 +9,6 @@ from pqcli.ui.curses.widgets import ( WindowWrapper, ) -from .focusable import Focusable - class ProgressBarWindow(WindowWrapper): def __init__( @@ -25,8 +24,8 @@ class ProgressBarWindow(WindowWrapper): super().__init__(parent, h, w, y, x) self._title = title - self._cur_pos = 0 - self._max_pos = 1 + self._cur_pos = 0.0 + self._max_pos = 1.0 self._progress_title = "" try: @@ -52,11 +51,7 @@ class ProgressBarWindow(WindowWrapper): self._progress_bar_win.erase() - if self._focused: - # with self._focus_standout(self._progress_bar_win): - self._progress_bar_win.standout() - - if True: + with Focusable.focus_standout(self, self._progress_bar_win): self._progress_bar_win.border( curses.ACS_VLINE, curses.ACS_VLINE, @@ -77,9 +72,6 @@ class ProgressBarWindow(WindowWrapper): min(len(text), self._progress_bar_win.getmaxyx()[1]), ) - if self._focused: - self._progress_bar_win.standend() - self._progress_bar.set_position(self._cur_pos, self._max_pos) self._progress_bar_win.noutrefresh() @@ -110,7 +102,7 @@ class DataTableProgressBarWindow(ProgressBarWindow): if not self._win: return - with self._focus_standout(self._win): + with Focusable.focus_standout(self, self._win): self._win.box() x = max(0, (self.getmaxyx()[1] - len(self._title)) // 2) self._win.addnstr( @@ -148,7 +140,7 @@ class ListBoxProgressBarWindow(ProgressBarWindow): if not self._win: return - with self._focus_standout(self._win): + with Focusable.focus_standout(self, self._win): self._win.box() x = max(0, (self.getmaxyx()[1] - len(self._title)) // 2) self._win.addnstr( diff --git a/pqcli/ui/curses/views/game_view/spell_book_window.py b/pqcli/ui/curses/views/game_view/spell_book_window.py index 8146063..ccf755b 100644 --- a/pqcli/ui/curses/views/game_view/spell_book_window.py +++ b/pqcli/ui/curses/views/game_view/spell_book_window.py @@ -57,7 +57,7 @@ class SpellBookWindow(Focusable, WindowWrapper): if not self._win: return - with self._focus_standout(self._win): + with self.focus_standout(self._win): self._win.box() text = " Spell Book " x = max(0, (self.getmaxyx()[1] - len(text)) // 2) diff --git a/pqcli/ui/curses/widgets/menu.py b/pqcli/ui/curses/widgets/menu.py index 68beb63..e016502 100644 --- a/pqcli/ui/curses/widgets/menu.py +++ b/pqcli/ui/curses/widgets/menu.py @@ -1,4 +1,5 @@ import curses +import curses.ascii import typing as T from pqcli.ui.curses.util import KEYS_DOWN, KEYS_UP diff --git a/pqcli/ui/curses/widgets/scrollable.py b/pqcli/ui/curses/widgets/scrollable.py index 3057cf0..1a91e6f 100644 --- a/pqcli/ui/curses/widgets/scrollable.py +++ b/pqcli/ui/curses/widgets/scrollable.py @@ -92,5 +92,5 @@ class Scrollable(WindowWrapper): except curses.error: pass - def _render_impl(self) -> None: + def _render_impl(self, h: int, w: int) -> None: raise NotImplementedError("not implemented")