mirror of
https://github.com/rr-/pq-cli.git
synced 2026-09-29 06:44:00 -06:00
ui/curses: add colors
This commit is contained in:
@@ -38,6 +38,13 @@ def parse_args() -> argparse.Namespace:
|
|||||||
help="Use curses user interface (slow, but rich colors output)",
|
help="Use curses user interface (slow, but rich colors output)",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-colors",
|
||||||
|
dest="colors",
|
||||||
|
action="store_false",
|
||||||
|
help="Disable color highlighting in curses interface",
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--no-config",
|
"--no-config",
|
||||||
dest="use_config",
|
dest="use_config",
|
||||||
|
|||||||
@@ -9,6 +9,15 @@ from pqcli import lingo
|
|||||||
from pqcli.config import Class, Race
|
from pqcli.config import Class, Race
|
||||||
from pqcli.mechanic import Player, Stats, create_player
|
from pqcli.mechanic import Player, Stats, create_player
|
||||||
from pqcli.roster import Roster
|
from pqcli.roster import Roster
|
||||||
|
from pqcli.ui.curses.colors import (
|
||||||
|
COLOR_FOCUSED,
|
||||||
|
COLOR_LOGO,
|
||||||
|
COLOR_PROGRESSBAR,
|
||||||
|
COLOR_SCROLLBAR_THUMB,
|
||||||
|
COLOR_SCROLLBAR_TRACK,
|
||||||
|
has_colors,
|
||||||
|
set_colors,
|
||||||
|
)
|
||||||
from pqcli.ui.curses.views import (
|
from pqcli.ui.curses.views import (
|
||||||
BaseView,
|
BaseView,
|
||||||
ChooseCharacterClassView,
|
ChooseCharacterClassView,
|
||||||
@@ -38,6 +47,16 @@ class CursesUserInterface(BaseUserInterface):
|
|||||||
curses.noecho()
|
curses.noecho()
|
||||||
curses.curs_set(0)
|
curses.curs_set(0)
|
||||||
|
|
||||||
|
set_colors(args.colors)
|
||||||
|
if has_colors():
|
||||||
|
curses.start_color()
|
||||||
|
curses.use_default_colors()
|
||||||
|
curses.init_pair(COLOR_LOGO, 10, -1)
|
||||||
|
curses.init_pair(COLOR_FOCUSED, -1, 8)
|
||||||
|
curses.init_pair(COLOR_SCROLLBAR_THUMB, -1, 0)
|
||||||
|
curses.init_pair(COLOR_SCROLLBAR_TRACK, -1, -1)
|
||||||
|
curses.init_pair(COLOR_PROGRESSBAR, 0, 9)
|
||||||
|
|
||||||
def signal_handler(sig: T.Any, frame: T.Any) -> None:
|
def signal_handler(sig: T.Any, frame: T.Any) -> None:
|
||||||
if self.args.use_saves:
|
if self.args.use_saves:
|
||||||
self.roster.save()
|
self.roster.save()
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
_HAS_COLORS = False
|
||||||
|
|
||||||
|
COLOR_LOGO = 1
|
||||||
|
COLOR_FOCUSED = 2
|
||||||
|
COLOR_SCROLLBAR_TRACK = 3
|
||||||
|
COLOR_SCROLLBAR_THUMB = 4
|
||||||
|
COLOR_PROGRESSBAR = 5
|
||||||
|
|
||||||
|
|
||||||
|
def has_colors() -> bool:
|
||||||
|
return _HAS_COLORS
|
||||||
|
|
||||||
|
|
||||||
|
def set_colors(has_colors: bool) -> None:
|
||||||
|
global _HAS_COLORS
|
||||||
|
_HAS_COLORS = has_colors
|
||||||
@@ -8,11 +8,10 @@ from pqcli.roster import Roster
|
|||||||
from pqcli.ui.curses.event_handler import EventHandler
|
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.util import KEYS_DOWN, KEYS_LEFT, KEYS_RIGHT, KEYS_UP
|
||||||
from pqcli.ui.curses.views.base_view import BaseView
|
from pqcli.ui.curses.views.base_view import BaseView
|
||||||
from pqcli.ui.curses.widgets import Scrollable, Widget
|
from pqcli.ui.curses.widgets import Focusable, Scrollable, Widget
|
||||||
|
|
||||||
from .character_sheet_window import CharacterSheetWindow
|
from .character_sheet_window import CharacterSheetWindow
|
||||||
from .equipment_window import EquipmentWindow
|
from .equipment_window import EquipmentWindow
|
||||||
from .focusable import Focusable
|
|
||||||
from .inventory_window import InventoryWindow
|
from .inventory_window import InventoryWindow
|
||||||
from .plot_window import PlotWindow
|
from .plot_window import PlotWindow
|
||||||
from .quest_book_window import QuestBookWindow
|
from .quest_book_window import QuestBookWindow
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import typing as T
|
import typing as T
|
||||||
|
|
||||||
from pqcli.mechanic import Player, StatType
|
from pqcli.mechanic import Player, StatType
|
||||||
|
from pqcli.ui.curses.widgets import Focusable
|
||||||
|
|
||||||
from .focusable import Focusable
|
|
||||||
from .progress_bar_window import DataTableProgressBarWindow
|
from .progress_bar_window import DataTableProgressBarWindow
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import typing as T
|
import typing as T
|
||||||
|
|
||||||
from pqcli.mechanic import EquipmentType, Player
|
from pqcli.mechanic import EquipmentType, Player
|
||||||
from pqcli.ui.curses.widgets import DataTable, WindowWrapper
|
from pqcli.ui.curses.widgets import DataTable, Focusable, WindowWrapper
|
||||||
|
|
||||||
from .focusable import Focusable
|
|
||||||
|
|
||||||
|
|
||||||
class EquipmentWindow(Focusable, WindowWrapper):
|
class EquipmentWindow(Focusable, WindowWrapper):
|
||||||
|
|||||||
@@ -3,9 +3,13 @@ import typing as T
|
|||||||
|
|
||||||
from pqcli.lingo import to_roman
|
from pqcli.lingo import to_roman
|
||||||
from pqcli.mechanic import InventoryItem, Player
|
from pqcli.mechanic import InventoryItem, Player
|
||||||
from pqcli.ui.curses.widgets import DataTable, ProgressBar, WindowWrapper
|
from pqcli.ui.curses.widgets import (
|
||||||
|
DataTable,
|
||||||
|
Focusable,
|
||||||
|
ProgressBar,
|
||||||
|
WindowWrapper,
|
||||||
|
)
|
||||||
|
|
||||||
from .focusable import Focusable
|
|
||||||
from .progress_bar_window import DataTableProgressBarWindow
|
from .progress_bar_window import DataTableProgressBarWindow
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,8 @@ import typing as T
|
|||||||
|
|
||||||
from pqcli.lingo import act_name
|
from pqcli.lingo import act_name
|
||||||
from pqcli.mechanic import Player
|
from pqcli.mechanic import Player
|
||||||
from pqcli.ui.curses.widgets import ListBox, WindowWrapper
|
from pqcli.ui.curses.widgets import Focusable, ListBox, WindowWrapper
|
||||||
|
|
||||||
from .focusable import Focusable
|
|
||||||
from .progress_bar_window import ListBoxProgressBarWindow
|
from .progress_bar_window import ListBoxProgressBarWindow
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import curses
|
import curses
|
||||||
import typing as T
|
import typing as T
|
||||||
|
|
||||||
from pqcli.ui.curses.views.game_view.focusable import Focusable
|
|
||||||
from pqcli.ui.curses.widgets import (
|
from pqcli.ui.curses.widgets import (
|
||||||
DataTable,
|
DataTable,
|
||||||
|
Focusable,
|
||||||
ListBox,
|
ListBox,
|
||||||
ProgressBar,
|
ProgressBar,
|
||||||
WindowWrapper,
|
WindowWrapper,
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import typing as T
|
import typing as T
|
||||||
|
|
||||||
from pqcli.mechanic import Player
|
from pqcli.mechanic import Player
|
||||||
from pqcli.ui.curses.widgets import ListBox, WindowWrapper
|
from pqcli.ui.curses.widgets import Focusable, ListBox, WindowWrapper
|
||||||
|
|
||||||
from .focusable import Focusable
|
|
||||||
from .progress_bar_window import ListBoxProgressBarWindow
|
from .progress_bar_window import ListBoxProgressBarWindow
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ import typing as T
|
|||||||
|
|
||||||
from pqcli.lingo import to_roman
|
from pqcli.lingo import to_roman
|
||||||
from pqcli.mechanic import Player, Spell
|
from pqcli.mechanic import Player, Spell
|
||||||
from pqcli.ui.curses.widgets import DataTable, WindowWrapper
|
from pqcli.ui.curses.widgets import DataTable, Focusable, WindowWrapper
|
||||||
|
|
||||||
from .focusable import Focusable
|
|
||||||
|
|
||||||
|
|
||||||
class SpellBookWindow(Focusable, WindowWrapper):
|
class SpellBookWindow(Focusable, WindowWrapper):
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import typing as T
|
import typing as T
|
||||||
|
|
||||||
from pqcli.mechanic import Player
|
from pqcli.mechanic import Player
|
||||||
from pqcli.ui.curses.widgets import Label, ProgressBar, WindowWrapper
|
from pqcli.ui.curses.widgets import (
|
||||||
|
Focusable,
|
||||||
from .focusable import Focusable
|
Label,
|
||||||
|
ProgressBar,
|
||||||
|
WindowWrapper,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TaskProgressWindow(Focusable, WindowWrapper):
|
class TaskProgressWindow(Focusable, WindowWrapper):
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import curses
|
||||||
import typing as T
|
import typing as T
|
||||||
|
|
||||||
from ..event_handler import EventHandler
|
from pqcli.ui.curses.colors import COLOR_LOGO, has_colors
|
||||||
from ..util import Choice
|
from pqcli.ui.curses.event_handler import EventHandler
|
||||||
from ..widgets import Menu
|
from pqcli.ui.curses.util import Choice
|
||||||
|
from pqcli.ui.curses.widgets import Menu
|
||||||
|
|
||||||
from .base_view import BaseView
|
from .base_view import BaseView
|
||||||
|
|
||||||
LOGO = """
|
LOGO = """
|
||||||
@@ -13,6 +16,22 @@ LOGO = """
|
|||||||
""".strip()
|
""".strip()
|
||||||
|
|
||||||
|
|
||||||
|
class MainMenu(Menu):
|
||||||
|
def __init__(
|
||||||
|
self, choices: T.List[Choice], scr_height: int, scr_width: int
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
header=LOGO,
|
||||||
|
choices=choices,
|
||||||
|
active_choice=0,
|
||||||
|
scr_height=scr_height,
|
||||||
|
scr_width=scr_width,
|
||||||
|
)
|
||||||
|
if has_colors():
|
||||||
|
for y in range(len(self._header_lines)):
|
||||||
|
self._pad.chgat(y, 0, curses.color_pair(COLOR_LOGO))
|
||||||
|
|
||||||
|
|
||||||
class RosterView(BaseView):
|
class RosterView(BaseView):
|
||||||
def __init__(self, screen: T.Any) -> None:
|
def __init__(self, screen: T.Any) -> None:
|
||||||
super().__init__(screen)
|
super().__init__(screen)
|
||||||
@@ -50,12 +69,8 @@ class RosterView(BaseView):
|
|||||||
self.screen.erase()
|
self.screen.erase()
|
||||||
self.screen.noutrefresh()
|
self.screen.noutrefresh()
|
||||||
|
|
||||||
self.main_menu = Menu(
|
self.main_menu = MainMenu(
|
||||||
header=LOGO,
|
choices=self.choices, scr_height=scr_height, scr_width=scr_width
|
||||||
choices=self.choices,
|
|
||||||
active_choice=0,
|
|
||||||
scr_height=scr_height,
|
|
||||||
scr_width=scr_width,
|
|
||||||
)
|
)
|
||||||
self.main_menu.render()
|
self.main_menu.render()
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from .base import Widget, WindowWrapper
|
from .base import Widget, WindowWrapper
|
||||||
from .data_table import DataTable
|
from .data_table import DataTable
|
||||||
|
from .focusable import Focusable
|
||||||
from .label import Label
|
from .label import Label
|
||||||
from .list_box import ListBox
|
from .list_box import ListBox
|
||||||
from .menu import Menu
|
from .menu import Menu
|
||||||
|
|||||||
+18
-4
@@ -1,9 +1,26 @@
|
|||||||
import contextlib
|
import contextlib
|
||||||
|
import curses
|
||||||
import typing as T
|
import typing as T
|
||||||
|
|
||||||
|
from pqcli.ui.curses.colors import COLOR_FOCUSED, has_colors
|
||||||
from pqcli.ui.curses.event_handler import EventHandler
|
from pqcli.ui.curses.event_handler import EventHandler
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def focus_standout(focused: bool, win: T.Any) -> T.Generator:
|
||||||
|
if focused:
|
||||||
|
if has_colors():
|
||||||
|
win.attron(curses.color_pair(COLOR_FOCUSED))
|
||||||
|
else:
|
||||||
|
win.standout()
|
||||||
|
yield
|
||||||
|
if focused:
|
||||||
|
if has_colors():
|
||||||
|
win.attroff(curses.color_pair(COLOR_FOCUSED))
|
||||||
|
else:
|
||||||
|
win.standend()
|
||||||
|
|
||||||
|
|
||||||
class Focusable:
|
class Focusable:
|
||||||
def __init__(self, *args: T.Any, **kwargs: T.Any) -> None:
|
def __init__(self, *args: T.Any, **kwargs: T.Any) -> None:
|
||||||
self._focused = False
|
self._focused = False
|
||||||
@@ -21,8 +38,5 @@ class Focusable:
|
|||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def focus_standout(self, win: T.Any) -> T.Generator:
|
def focus_standout(self, win: T.Any) -> T.Generator:
|
||||||
if self._focused:
|
with focus_standout(self._focused, win):
|
||||||
win.standout()
|
|
||||||
yield
|
yield
|
||||||
if self._focused:
|
|
||||||
win.standend()
|
|
||||||
@@ -3,6 +3,7 @@ import curses.ascii
|
|||||||
import typing as T
|
import typing as T
|
||||||
|
|
||||||
from pqcli.ui.curses.util import KEYS_DOWN, KEYS_UP
|
from pqcli.ui.curses.util import KEYS_DOWN, KEYS_UP
|
||||||
|
from pqcli.ui.curses.widgets.focusable import focus_standout
|
||||||
|
|
||||||
from ..util import Choice
|
from ..util import Choice
|
||||||
from .base import Widget
|
from .base import Widget
|
||||||
@@ -73,11 +74,8 @@ class Menu(Widget):
|
|||||||
|
|
||||||
for y, choice in enumerate(self._choices):
|
for y, choice in enumerate(self._choices):
|
||||||
self._pad.move(len(self._header_lines) + 1 + y, 0)
|
self._pad.move(len(self._header_lines) + 1 + y, 0)
|
||||||
if y == self._active_choice:
|
with focus_standout(y == self._active_choice, self._pad):
|
||||||
self._pad.standout()
|
|
||||||
self._pad.addstr(choice.desc)
|
self._pad.addstr(choice.desc)
|
||||||
if y == self._active_choice:
|
|
||||||
self._pad.standend()
|
|
||||||
|
|
||||||
self._pad.refresh(
|
self._pad.refresh(
|
||||||
0,
|
0,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import datetime
|
|||||||
import typing as T
|
import typing as T
|
||||||
|
|
||||||
from pqcli.lingo import format_timespan
|
from pqcli.lingo import format_timespan
|
||||||
|
from pqcli.ui.curses.colors import COLOR_PROGRESSBAR, has_colors
|
||||||
from pqcli.ui.curses.widgets.base import WindowWrapper
|
from pqcli.ui.curses.widgets.base import WindowWrapper
|
||||||
|
|
||||||
|
|
||||||
@@ -47,7 +48,13 @@ class ProgressBar(WindowWrapper):
|
|||||||
self._win.addnstr(0, x, text, min(len(text), self.getmaxyx()[1] - 1))
|
self._win.addnstr(0, x, text, min(len(text), self.getmaxyx()[1] - 1))
|
||||||
x = int(cur_pos * self.getmaxyx()[1] // max_pos)
|
x = int(cur_pos * self.getmaxyx()[1] // max_pos)
|
||||||
if x > 0:
|
if x > 0:
|
||||||
self._win.chgat(0, 0, curses.A_REVERSE)
|
self._win.chgat(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
curses.color_pair(COLOR_PROGRESSBAR)
|
||||||
|
if has_colors()
|
||||||
|
else curses.A_REVERSE,
|
||||||
|
)
|
||||||
if x < self.getmaxyx()[1]:
|
if x < self.getmaxyx()[1]:
|
||||||
self._win.chgat(0, x, curses.A_NORMAL)
|
self._win.chgat(0, x, curses.A_NORMAL)
|
||||||
self._win.noutrefresh()
|
self._win.noutrefresh()
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import curses
|
import curses
|
||||||
import typing as T
|
import typing as T
|
||||||
|
|
||||||
|
from pqcli.ui.curses.colors import (
|
||||||
|
COLOR_SCROLLBAR_THUMB,
|
||||||
|
COLOR_SCROLLBAR_TRACK,
|
||||||
|
has_colors,
|
||||||
|
)
|
||||||
|
|
||||||
from .base import WindowWrapper
|
from .base import WindowWrapper
|
||||||
|
|
||||||
|
|
||||||
@@ -69,13 +75,25 @@ class Scrollable(WindowWrapper):
|
|||||||
y2 = self._scroll_pos + h
|
y2 = self._scroll_pos + h
|
||||||
thumb_y1 = int(y1 * h // len(self._items))
|
thumb_y1 = int(y1 * h // len(self._items))
|
||||||
thumb_y2 = int(y2 * h // len(self._items))
|
thumb_y2 = int(y2 * h // len(self._items))
|
||||||
|
|
||||||
|
attr_thumb = (
|
||||||
|
curses.color_pair(COLOR_SCROLLBAR_THUMB)
|
||||||
|
if has_colors()
|
||||||
|
else curses.A_REVERSE
|
||||||
|
)
|
||||||
|
attr_track = (
|
||||||
|
curses.color_pair(COLOR_SCROLLBAR_TRACK)
|
||||||
|
if has_colors()
|
||||||
|
else curses.A_NORMAL
|
||||||
|
)
|
||||||
|
|
||||||
for win_y in range(h):
|
for win_y in range(h):
|
||||||
self._win.chgat(
|
self._win.chgat(
|
||||||
win_y,
|
win_y,
|
||||||
w - 1,
|
w - 1,
|
||||||
curses.A_REVERSE
|
attr_thumb
|
||||||
if thumb_y1 <= win_y <= thumb_y2
|
if thumb_y1 <= win_y <= thumb_y2
|
||||||
else curses.A_NORMAL,
|
else attr_track,
|
||||||
)
|
)
|
||||||
self._win.noutrefresh()
|
self._win.noutrefresh()
|
||||||
|
|
||||||
|
|||||||
@@ -5,4 +5,4 @@ from .data_table import DataTable
|
|||||||
from .double_line_box import DoubleLineBox
|
from .double_line_box import DoubleLineBox
|
||||||
from .layout import NColumns, NPile
|
from .layout import NColumns, NPile
|
||||||
from .read_only_check_box import ReadOnlyCheckBox
|
from .read_only_check_box import ReadOnlyCheckBox
|
||||||
from .scrollable import ScrollBar, Scrollable
|
from .scrollable import Scrollable, ScrollBar
|
||||||
|
|||||||
Reference in New Issue
Block a user