From 9db61f71918758b21f0030cecaa9e417a29c601a Mon Sep 17 00:00:00 2001 From: rr- Date: Fri, 4 Jan 2019 00:21:27 +0100 Subject: [PATCH] ui/curses: implement game view widget focusing --- pqcli/ui/curses/views/game_view/__init__.py | 70 ++++++++++++++-- .../views/game_view/character_sheet_window.py | 16 ++-- .../views/game_view/equipment_window.py | 15 ++-- pqcli/ui/curses/views/game_view/focusable.py | 28 +++++++ .../views/game_view/inventory_window.py | 14 ++-- .../ui/curses/views/game_view/plot_window.py | 12 +-- .../views/game_view/progress_bar_window.py | 83 +++++++++++++------ .../views/game_view/quest_book_window.py | 12 +-- .../views/game_view/spell_book_window.py | 15 ++-- .../views/game_view/task_progress_window.py | 4 +- pqcli/ui/curses/widgets/__init__.py | 2 +- 11 files changed, 204 insertions(+), 67 deletions(-) create mode 100644 pqcli/ui/curses/views/game_view/focusable.py diff --git a/pqcli/ui/curses/views/game_view/__init__.py b/pqcli/ui/curses/views/game_view/__init__.py index 9a4640c..821167d 100644 --- a/pqcli/ui/curses/views/game_view/__init__.py +++ b/pqcli/ui/curses/views/game_view/__init__.py @@ -6,6 +6,7 @@ import typing as T from pqcli.mechanic import Player, Simulation from pqcli.roster import Roster from pqcli.ui.curses.event_handler import EventHandler +from pqcli.ui.curses.widgets import Widget from ..base_view import BaseView from .character_sheet_window import CharacterSheetWindow @@ -36,8 +37,47 @@ class GameView(BaseView): self._last_tick = datetime.datetime.now() def keypress(self, key: int) -> None: + focused = self.focused + if key in map(ord, "qQ"): self.on_exit() + + elif key in map(ord, "jJ"): + if focused == self._char_sheet_win: + self.focus(self._spell_book_win) + elif focused == self._equipment_win: + self.focus(self._inventory_win) + elif focused == self._plot_win: + self.focus(self._quest_book_win) + + elif key in map(ord, "kK"): + if focused == self._spell_book_win: + self.focus(self._char_sheet_win) + elif focused == self._inventory_win: + self.focus(self._equipment_win) + elif focused == self._quest_book_win: + self.focus(self._plot_win) + + elif key in map(ord, "hH"): + if focused == self._equipment_win: + self.focus(self._char_sheet_win) + elif focused == self._inventory_win: + self.focus(self._spell_book_win) + elif focused == self._plot_win: + self.focus(self._equipment_win) + elif focused == self._quest_book_win: + self.focus(self._inventory_win) + + elif key in map(ord, "lL"): + if focused == self._char_sheet_win: + self.focus(self._equipment_win) + elif focused == self._spell_book_win: + self.focus(self._inventory_win) + elif focused == self._equipment_win: + self.focus(self._plot_win) + elif focused == self._inventory_win: + self.focus(self._quest_book_win) + else: super().keypress(key) @@ -135,14 +175,19 @@ class GameView(BaseView): x=0, ) + self._children: T.List[Widget] = [ + self._char_sheet_win, + self._spell_book_win, + self._equipment_win, + self._inventory_win, + self._plot_win, + self._quest_book_win, + self._task_win, + ] + def stop(self) -> None: - self._char_sheet_win.stop() - self._spell_book_win.stop() - self._equipment_win.stop() - self._inventory_win.stop() - self._plot_win.stop() - self._quest_book_win.stop() - self._task_win.stop() + for child in self._children: + child.stop() del self._col1_win del self._col2_win del self._col3_win @@ -154,3 +199,14 @@ class GameView(BaseView): self._last_tick = datetime.datetime.now() if self._args.use_saves: self._roster.save_periodically() + + @property + def focused(self) -> Widget: + for widget in self._children: + if widget.focused: + return widget + return None + + def focus(self, widget: Widget): + self.focused.focused = False + widget.focused = True diff --git a/pqcli/ui/curses/views/game_view/character_sheet_window.py b/pqcli/ui/curses/views/game_view/character_sheet_window.py index 99fbefe..afeb3d4 100644 --- a/pqcli/ui/curses/views/game_view/character_sheet_window.py +++ b/pqcli/ui/curses/views/game_view/character_sheet_window.py @@ -2,16 +2,20 @@ import typing as T from pqcli.mechanic import Player, StatType +from .focusable import Focusable from .progress_bar_window import DataTableProgressBarWindow -class CharacterSheetWindow(DataTableProgressBarWindow): +class CharacterSheetWindow(Focusable, DataTableProgressBarWindow): def __init__( self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int ) -> None: super().__init__( parent, h, w, y, x, " Character Sheet ", align_right=False ) + self._on_focus_change += self._render + + self._focused = True self._player = player self._player.connect("level_up", self._sync_traits) @@ -47,7 +51,9 @@ class CharacterSheetWindow(DataTableProgressBarWindow): self._render_data_table() def _sync_exp(self) -> None: - cur = self._player.exp_bar.position - max_ = self._player.exp_bar.max_ - text = f"Experience ({max_-cur:.0f} XP to go)" - self._render_progress_bar(cur, max_, text) + self._cur_pos = self._player.exp_bar.position + self._max_pos = self._player.exp_bar.max_ + self._progress_title = ( + f"Experience ({self._max_pos-self._cur_pos:.0f} XP to go)" + ) + self._render_progress_bar() diff --git a/pqcli/ui/curses/views/game_view/equipment_window.py b/pqcli/ui/curses/views/game_view/equipment_window.py index 3f517fa..e748c51 100644 --- a/pqcli/ui/curses/views/game_view/equipment_window.py +++ b/pqcli/ui/curses/views/game_view/equipment_window.py @@ -3,12 +3,15 @@ import typing as T from pqcli.mechanic import EquipmentType, Player from pqcli.ui.curses.widgets import DataTable, WindowWrapper +from .focusable import Focusable -class EquipmentWindow(WindowWrapper): + +class EquipmentWindow(Focusable, WindowWrapper): def __init__( self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int ) -> None: super().__init__(parent, h, w, y, x) + self._on_focus_change += self._render self._data_table = DataTable( self._win, h - 2, w - 2, 1, 1, align_right=False @@ -44,9 +47,11 @@ class EquipmentWindow(WindowWrapper): if not self._win: return - self._win.box() - text = " Equipment " - x = max(0, (self.getmaxyx()[1] - len(text)) // 2) - self._win.addnstr(0, x, text, min(len(text), self.getmaxyx()[1])) + with self._focus_standout(self._win): + self._win.box() + text = " Equipment " + x = max(0, (self.getmaxyx()[1] - len(text)) // 2) + self._win.addnstr(0, x, text, min(len(text), self.getmaxyx()[1])) + self._win.refresh() self._data_table.render() diff --git a/pqcli/ui/curses/views/game_view/focusable.py b/pqcli/ui/curses/views/game_view/focusable.py new file mode 100644 index 0000000..0c7d13b --- /dev/null +++ b/pqcli/ui/curses/views/game_view/focusable.py @@ -0,0 +1,28 @@ +import contextlib +import typing as T + +from pqcli.ui.curses.event_handler import EventHandler + + +class Focusable: + def __init__(self, *args: T.Any, **kwargs: T.Any) -> None: + self._focused = False + self._on_focus_change = EventHandler() + super().__init__(*args, **kwargs) + + @property + def focused(self) -> bool: + return self._focused + + @focused.setter + def focused(self, focused: bool) -> None: + self._focused = focused + self._on_focus_change() + + @contextlib.contextmanager + def _focus_standout(self, win: T.Any) -> T.Generator: + if self._focused: + self._win.standout() + yield + if self._focused: + self._win.standend() diff --git a/pqcli/ui/curses/views/game_view/inventory_window.py b/pqcli/ui/curses/views/game_view/inventory_window.py index 2f213e7..ff545a6 100644 --- a/pqcli/ui/curses/views/game_view/inventory_window.py +++ b/pqcli/ui/curses/views/game_view/inventory_window.py @@ -5,14 +5,16 @@ from pqcli.lingo import to_roman from pqcli.mechanic import InventoryItem, Player from pqcli.ui.curses.widgets import DataTable, ProgressBar, WindowWrapper +from .focusable import Focusable from .progress_bar_window import DataTableProgressBarWindow -class InventoryWindow(DataTableProgressBarWindow): +class InventoryWindow(Focusable, DataTableProgressBarWindow): def __init__( self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int ) -> None: super().__init__(parent, h, w, y, x, " Inventory ", align_right=True) + self._on_focus_change += self._render self._player = player self._player.connect("new_task", self._on_new_task) @@ -46,10 +48,12 @@ class InventoryWindow(DataTableProgressBarWindow): self._sync_encumbrance() def _sync_encumbrance(self) -> None: - cur = self._player.inventory.encum_bar.position - max_ = self._player.inventory.encum_bar.max_ - text = f"Encumbrance ({cur:.0f}/{max_} cubits)" - self._render_progress_bar(cur, max_, text) + self._cur_pos = self._player.inventory.encum_bar.position + self._max_pos = self._player.inventory.encum_bar.max_ + self._progress_title = ( + f"Encumbrance ({self._cur_pos:.0f}/{self._max_pos} cubits)" + ) + self._render_progress_bar() def _sync_gold(self) -> None: self._data_table.set("Gold", str(self._player.inventory.gold)) diff --git a/pqcli/ui/curses/views/game_view/plot_window.py b/pqcli/ui/curses/views/game_view/plot_window.py index 743f345..1a4e7e4 100644 --- a/pqcli/ui/curses/views/game_view/plot_window.py +++ b/pqcli/ui/curses/views/game_view/plot_window.py @@ -4,16 +4,18 @@ from pqcli.lingo import act_name from pqcli.mechanic import Player from pqcli.ui.curses.widgets import ListBox, WindowWrapper +from .focusable import Focusable from .progress_bar_window import ListBoxProgressBarWindow -class PlotWindow(ListBoxProgressBarWindow): +class PlotWindow(Focusable, ListBoxProgressBarWindow): cutoff = 100 def __init__( self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int ) -> None: super().__init__(parent, h, w, y, x, " Plot Development ") + self._on_focus_change += self._render self._player = player self._player.quest_book.connect("start_act", self._sync_act_add) @@ -51,8 +53,6 @@ class PlotWindow(ListBoxProgressBarWindow): self._list_box.add("[ ] " + act_name(act_number)) def _sync_position(self) -> None: - self._render_progress_bar( - self._player.quest_book.plot_bar.position, - self._player.quest_book.plot_bar.max_, - "", - ) + self._cur_pos = self._player.quest_book.plot_bar.position + self._max_pos = self._player.quest_book.plot_bar.max_ + self._render_progress_bar() 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 e2ac636..50bfeb1 100644 --- a/pqcli/ui/curses/views/game_view/progress_bar_window.py +++ b/pqcli/ui/curses/views/game_view/progress_bar_window.py @@ -8,6 +8,8 @@ from pqcli.ui.curses.widgets import ( WindowWrapper, ) +from .focusable import Focusable + class ProgressBarWindow(WindowWrapper): def __init__( @@ -16,6 +18,9 @@ class ProgressBarWindow(WindowWrapper): super().__init__(parent, h, w, y, x) self._title = title + self._cur_pos = 0 + self._max_pos = 1 + self._progress_title = "" try: self._progress_bar_win = ( @@ -34,27 +39,41 @@ class ProgressBarWindow(WindowWrapper): del self._progress_bar_win self._progress_bar_win = None - def _render_progress_bar(self, cur: float, max_: float, text: str) -> None: + def _render_progress_bar(self) -> None: if not self._progress_bar_win: return self._progress_bar_win.erase() - self._progress_bar_win.border( - curses.ACS_VLINE, - curses.ACS_VLINE, - curses.ACS_HLINE, - curses.ACS_HLINE, - curses.ACS_LTEE, - curses.ACS_RTEE, - curses.ACS_LLCORNER, - curses.ACS_LRCORNER, - ) - x = max(0, (self.getmaxyx()[1] - len(text)) // 2) - self._progress_bar_win.addnstr( - 0, x, text, min(len(text), self._progress_bar_win.getmaxyx()[1]) - ) - self._progress_bar.set_position(cur, max_) + if self._focused: + # with self._focus_standout(self._progress_bar_win): + self._progress_bar_win.standout() + + if True: + self._progress_bar_win.border( + curses.ACS_VLINE, + curses.ACS_VLINE, + curses.ACS_HLINE, + curses.ACS_HLINE, + curses.ACS_LTEE, + curses.ACS_RTEE, + curses.ACS_LLCORNER, + curses.ACS_LRCORNER, + ) + + text = self._progress_title + x = max(0, (self.getmaxyx()[1] - len(text)) // 2) + self._progress_bar_win.addnstr( + 0, + x, + text, + 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.refresh() @@ -83,14 +102,20 @@ class DataTableProgressBarWindow(ProgressBarWindow): if not self._win: return - self._win.box() - x = max(0, (self.getmaxyx()[1] - len(self._title)) // 2) - self._win.addnstr( - 0, x, self._title, min(len(self._title), self.getmaxyx()[1]) - ) + with self._focus_standout(self._win): + self._win.box() + x = max(0, (self.getmaxyx()[1] - len(self._title)) // 2) + self._win.addnstr( + 0, x, self._title, min(len(self._title), self.getmaxyx()[1]) + ) + self._win.refresh() self._data_table.render() + def _render(self) -> None: + self._render_data_table() + self._render_progress_bar() + class ListBoxProgressBarWindow(ProgressBarWindow): def __init__( @@ -108,10 +133,16 @@ class ListBoxProgressBarWindow(ProgressBarWindow): if not self._win: return - self._win.box() - x = max(0, (self.getmaxyx()[1] - len(self._title)) // 2) - self._win.addnstr( - 0, x, self._title, min(len(self._title), self.getmaxyx()[1]) - ) + with self._focus_standout(self._win): + self._win.box() + x = max(0, (self.getmaxyx()[1] - len(self._title)) // 2) + self._win.addnstr( + 0, x, self._title, min(len(self._title), self.getmaxyx()[1]) + ) + self._win.refresh() self._list_box.render() + + def _render(self) -> None: + self._render_list_box() + self._render_progress_bar() diff --git a/pqcli/ui/curses/views/game_view/quest_book_window.py b/pqcli/ui/curses/views/game_view/quest_book_window.py index 53ac3e4..ed6facd 100644 --- a/pqcli/ui/curses/views/game_view/quest_book_window.py +++ b/pqcli/ui/curses/views/game_view/quest_book_window.py @@ -3,16 +3,18 @@ import typing as T from pqcli.mechanic import Player from pqcli.ui.curses.widgets import ListBox, WindowWrapper +from .focusable import Focusable from .progress_bar_window import ListBoxProgressBarWindow -class QuestBookWindow(ListBoxProgressBarWindow): +class QuestBookWindow(Focusable, ListBoxProgressBarWindow): cutoff = 100 def __init__( self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int ) -> None: super().__init__(parent, h, w, y, x, " Quests ") + self._on_focus_change += self._render self._player = player self._player.quest_book.connect("start_quest", self._sync_quest_add) @@ -49,8 +51,6 @@ class QuestBookWindow(ListBoxProgressBarWindow): self._list_box.add("[ ] " + quest_name) def _sync_position(self) -> None: - self._render_progress_bar( - self._player.quest_book.quest_bar.position, - self._player.quest_book.quest_bar.max_, - "", - ) + self._cur_pos = self._player.quest_book.quest_bar.position + self._max_pos = self._player.quest_book.quest_bar.max_ + self._render_progress_bar() 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 5a71928..29d0c7b 100644 --- a/pqcli/ui/curses/views/game_view/spell_book_window.py +++ b/pqcli/ui/curses/views/game_view/spell_book_window.py @@ -4,12 +4,15 @@ from pqcli.lingo import to_roman from pqcli.mechanic import Player, Spell from pqcli.ui.curses.widgets import DataTable, WindowWrapper +from .focusable import Focusable -class SpellBookWindow(WindowWrapper): + +class SpellBookWindow(Focusable, WindowWrapper): def __init__( self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int ) -> None: super().__init__(parent, h, w, y, x) + self._on_focus_change += self._render self._data_table = DataTable( self._win, h - 2, w - 2, 1, 1, align_right=True @@ -46,9 +49,11 @@ class SpellBookWindow(WindowWrapper): if not self._win: return - self._win.box() - text = " Spell Book " - x = max(0, (self.getmaxyx()[1] - len(text)) // 2) - self._win.addnstr(0, x, text, min(len(text), self.getmaxyx()[1])) + with self._focus_standout(self._win): + self._win.box() + text = " Spell Book " + x = max(0, (self.getmaxyx()[1] - len(text)) // 2) + self._win.addnstr(0, x, text, min(len(text), self.getmaxyx()[1])) + self._win.refresh() self._data_table.render() diff --git a/pqcli/ui/curses/views/game_view/task_progress_window.py b/pqcli/ui/curses/views/game_view/task_progress_window.py index 8e26754..4517ef9 100644 --- a/pqcli/ui/curses/views/game_view/task_progress_window.py +++ b/pqcli/ui/curses/views/game_view/task_progress_window.py @@ -3,8 +3,10 @@ import typing as T from pqcli.mechanic import Player from pqcli.ui.curses.widgets import Label, ProgressBar, WindowWrapper +from .focusable import Focusable -class TaskProgressWindow(WindowWrapper): + +class TaskProgressWindow(Focusable, WindowWrapper): def __init__( self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int ) -> None: diff --git a/pqcli/ui/curses/widgets/__init__.py b/pqcli/ui/curses/widgets/__init__.py index 8f301cb..f2c86f1 100644 --- a/pqcli/ui/curses/widgets/__init__.py +++ b/pqcli/ui/curses/widgets/__init__.py @@ -1,4 +1,4 @@ -from .base import WindowWrapper +from .base import Widget, WindowWrapper from .data_table import DataTable from .label import Label from .list_box import ListBox