mirror of
https://github.com/rr-/pq-cli.git
synced 2026-11-12 22:40:36 -07:00
misc: replace Any with precise types
Curses screens, windows and pads are typed as curses.window, and the widgets that may get no parent window take curses.window | None. - EventHandler is generic over a ParamSpec, so each handler declares the arguments it passes, and MenuView is generic over the item its on_confirm hands out. Choice callbacks take no arguments. - Scrollable is generic over its items, which DataTable and ListBox fix to tuple[str, str] and str. - random.choice() and choice_low() are generic over the sequence, so the casts at their call sites are gone. BasicUserInterface.menu() and util.first() are generic too. - Signal callbacks and their payloads are Callable[..., object] and object, since emit() only forwards them. - JSON values in game_data are object and narrowed by isinstance, and data files are Traversable. - Signal handlers take an int and a FrameType | None, and list_players() takes a TextIO. - The focus_standout() context managers return Generator[None, None, None]. Any remains in three places: Player.__setstate__, whose pickled __dict__ holds values of every type; Focusable.__init__, a mixin that passes *args and **kwargs through to the next class; and the values load_game_data() collects, whose element type depends on the field each one fills.
This commit is contained in:
34 files changed
+219
-153
No files matched your search
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import TextIO
|
||||||
|
|
||||||
from xdg_base_dirs import xdg_config_home
|
from xdg_base_dirs import xdg_config_home
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ def parse_args() -> argparse.Namespace:
|
|||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
def list_players(roster: Roster, file: Any | None = None) -> None:
|
def list_players(roster: Roster, file: TextIO | None = None) -> None:
|
||||||
for i, player in enumerate(roster.players, start=1):
|
for i, player in enumerate(roster.players, start=1):
|
||||||
print(f"{i}. {player.name}", file=file)
|
print(f"{i}. {player.name}", file=file)
|
||||||
|
|
||||||
|
|||||||
+29
-22
@@ -4,7 +4,7 @@ from collections.abc import Callable, Sequence
|
|||||||
from dataclasses import dataclass, fields
|
from dataclasses import dataclass, fields
|
||||||
from importlib import resources
|
from importlib import resources
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, cast
|
from typing import Any
|
||||||
|
|
||||||
from pqcli.config import (
|
from pqcli.config import (
|
||||||
Class,
|
Class,
|
||||||
@@ -15,6 +15,11 @@ from pqcli.config import (
|
|||||||
StatType,
|
StatType,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 11):
|
||||||
|
from importlib.resources.abc import Traversable
|
||||||
|
else:
|
||||||
|
from importlib.abc import Traversable
|
||||||
|
|
||||||
TARGET_PLACEHOLDER = "{target}"
|
TARGET_PLACEHOLDER = "{target}"
|
||||||
|
|
||||||
|
|
||||||
@@ -56,8 +61,8 @@ class _Context:
|
|||||||
|
|
||||||
|
|
||||||
def _check_keys(
|
def _check_keys(
|
||||||
ctx: _Context, where: str, obj: Any, keys: Sequence[str]
|
ctx: _Context, where: str, obj: object, keys: Sequence[str]
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, object]:
|
||||||
if not isinstance(obj, dict):
|
if not isinstance(obj, dict):
|
||||||
raise ctx.error(f"{where} must be an object")
|
raise ctx.error(f"{where} must be an object")
|
||||||
missing = [key for key in keys if key not in obj]
|
missing = [key for key in keys if key not in obj]
|
||||||
@@ -69,39 +74,39 @@ def _check_keys(
|
|||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
def _str(ctx: _Context, where: str, value: Any) -> str:
|
def _str(ctx: _Context, where: str, value: object) -> str:
|
||||||
if not isinstance(value, str):
|
if not isinstance(value, str):
|
||||||
raise ctx.error(f"{where} must be a string")
|
raise ctx.error(f"{where} must be a string")
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def _int(ctx: _Context, where: str, value: Any) -> int:
|
def _int(ctx: _Context, where: str, value: object) -> int:
|
||||||
if isinstance(value, bool) or not isinstance(value, int):
|
if isinstance(value, bool) or not isinstance(value, int):
|
||||||
raise ctx.error(f"{where} must be an integer")
|
raise ctx.error(f"{where} must be an integer")
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def _stat(ctx: _Context, where: str, value: Any) -> StatType:
|
def _stat(ctx: _Context, where: str, value: object) -> StatType:
|
||||||
_str(ctx, where, value)
|
name = _str(ctx, where, value)
|
||||||
for stat in StatType:
|
for stat in StatType:
|
||||||
if value.lower() in (stat.value.lower(), stat.name):
|
if name.lower() in (stat.value.lower(), stat.name):
|
||||||
return stat
|
return stat
|
||||||
allowed = ", ".join(stat.value for stat in StatType)
|
allowed = ", ".join(stat.value for stat in StatType)
|
||||||
raise ctx.error(f"{where}: unknown stat {value!r} (use one of {allowed})")
|
raise ctx.error(f"{where}: unknown stat {value!r} (use one of {allowed})")
|
||||||
|
|
||||||
|
|
||||||
def _parse_str(ctx: _Context, where: str, item: Any) -> str:
|
def _parse_str(ctx: _Context, where: str, item: object) -> str:
|
||||||
return _str(ctx, where, item)
|
return _str(ctx, where, item)
|
||||||
|
|
||||||
|
|
||||||
def _parse_verb(ctx: _Context, where: str, item: Any) -> str:
|
def _parse_verb(ctx: _Context, where: str, item: object) -> str:
|
||||||
verb = _str(ctx, where, item)
|
verb = _str(ctx, where, item)
|
||||||
if TARGET_PLACEHOLDER not in verb:
|
if TARGET_PLACEHOLDER not in verb:
|
||||||
raise ctx.error(f"{where} must contain {TARGET_PLACEHOLDER}")
|
raise ctx.error(f"{where} must contain {TARGET_PLACEHOLDER}")
|
||||||
return verb
|
return verb
|
||||||
|
|
||||||
|
|
||||||
def _parse_modifier(ctx: _Context, where: str, item: Any) -> Modifier:
|
def _parse_modifier(ctx: _Context, where: str, item: object) -> Modifier:
|
||||||
obj = _check_keys(ctx, where, item, ["name", "quality"])
|
obj = _check_keys(ctx, where, item, ["name", "quality"])
|
||||||
return Modifier(
|
return Modifier(
|
||||||
_str(ctx, f"{where}.name", obj["name"]),
|
_str(ctx, f"{where}.name", obj["name"]),
|
||||||
@@ -109,7 +114,7 @@ def _parse_modifier(ctx: _Context, where: str, item: Any) -> Modifier:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _parse_preset(ctx: _Context, where: str, item: Any) -> EquipmentPreset:
|
def _parse_preset(ctx: _Context, where: str, item: object) -> EquipmentPreset:
|
||||||
obj = _check_keys(ctx, where, item, ["name", "quality"])
|
obj = _check_keys(ctx, where, item, ["name", "quality"])
|
||||||
return EquipmentPreset(
|
return EquipmentPreset(
|
||||||
_str(ctx, f"{where}.name", obj["name"]),
|
_str(ctx, f"{where}.name", obj["name"]),
|
||||||
@@ -117,7 +122,7 @@ def _parse_preset(ctx: _Context, where: str, item: Any) -> EquipmentPreset:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _parse_monster(ctx: _Context, where: str, item: Any) -> Monster:
|
def _parse_monster(ctx: _Context, where: str, item: object) -> Monster:
|
||||||
obj = _check_keys(ctx, where, item, ["name", "level", "item"])
|
obj = _check_keys(ctx, where, item, ["name", "level", "item"])
|
||||||
loot = obj["item"]
|
loot = obj["item"]
|
||||||
return Monster(
|
return Monster(
|
||||||
@@ -127,13 +132,13 @@ def _parse_monster(ctx: _Context, where: str, item: Any) -> Monster:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _parse_attrs(ctx: _Context, where: str, value: Any) -> list[StatType]:
|
def _parse_attrs(ctx: _Context, where: str, value: object) -> list[StatType]:
|
||||||
if not isinstance(value, list):
|
if not isinstance(value, list):
|
||||||
raise ctx.error(f"{where} must be a list of stats")
|
raise ctx.error(f"{where} must be a list of stats")
|
||||||
return [_stat(ctx, f"{where}[{i}]", stat) for i, stat in enumerate(value)]
|
return [_stat(ctx, f"{where}[{i}]", stat) for i, stat in enumerate(value)]
|
||||||
|
|
||||||
|
|
||||||
def _parse_race(ctx: _Context, where: str, item: Any) -> Race:
|
def _parse_race(ctx: _Context, where: str, item: object) -> Race:
|
||||||
obj = _check_keys(ctx, where, item, ["name", "attr"])
|
obj = _check_keys(ctx, where, item, ["name", "attr"])
|
||||||
return Race(
|
return Race(
|
||||||
_str(ctx, f"{where}.name", obj["name"]),
|
_str(ctx, f"{where}.name", obj["name"]),
|
||||||
@@ -141,7 +146,7 @@ def _parse_race(ctx: _Context, where: str, item: Any) -> Race:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _parse_class(ctx: _Context, where: str, item: Any) -> Class:
|
def _parse_class(ctx: _Context, where: str, item: object) -> Class:
|
||||||
obj = _check_keys(ctx, where, item, ["name", "attr"])
|
obj = _check_keys(ctx, where, item, ["name", "attr"])
|
||||||
return Class(
|
return Class(
|
||||||
_str(ctx, f"{where}.name", obj["name"]),
|
_str(ctx, f"{where}.name", obj["name"]),
|
||||||
@@ -149,7 +154,7 @@ def _parse_class(ctx: _Context, where: str, item: Any) -> Class:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
_PARSERS: dict[str, Callable[[_Context, str, Any], Any]] = {
|
_PARSERS: dict[str, Callable[[_Context, str, object], object]] = {
|
||||||
"spells": _parse_str,
|
"spells": _parse_str,
|
||||||
"offense_attrib": _parse_modifier,
|
"offense_attrib": _parse_modifier,
|
||||||
"defense_attrib": _parse_modifier,
|
"defense_attrib": _parse_modifier,
|
||||||
@@ -172,7 +177,7 @@ _PARSERS: dict[str, Callable[[_Context, str, Any], Any]] = {
|
|||||||
assert set(_PARSERS) == set(DATA_FILES)
|
assert set(_PARSERS) == set(DATA_FILES)
|
||||||
|
|
||||||
|
|
||||||
def _parse(name: str, path: str, text: str) -> tuple[Any, ...]:
|
def _parse(name: str, path: str, text: str) -> tuple[object, ...]:
|
||||||
ctx = _Context(path)
|
ctx = _Context(path)
|
||||||
try:
|
try:
|
||||||
value = json.loads(text)
|
value = json.loads(text)
|
||||||
@@ -186,13 +191,13 @@ def _parse(name: str, path: str, text: str) -> tuple[Any, ...]:
|
|||||||
return tuple(parser(ctx, f"[{i}]", item) for i, item in enumerate(value))
|
return tuple(parser(ctx, f"[{i}]", item) for i, item in enumerate(value))
|
||||||
|
|
||||||
|
|
||||||
def _builtin_dir() -> Any:
|
def _builtin_dir() -> Traversable:
|
||||||
return resources.files("pqcli") / "data"
|
return resources.files("pqcli") / "data"
|
||||||
|
|
||||||
|
|
||||||
def _read_text(path: Any, display: str) -> str:
|
def _read_text(path: Traversable, display: str) -> str:
|
||||||
try:
|
try:
|
||||||
return cast(str, path.read_text(encoding="utf-8"))
|
return path.read_text(encoding="utf-8")
|
||||||
except UnicodeDecodeError as ex:
|
except UnicodeDecodeError as ex:
|
||||||
raise DataError(f"{display}: not valid UTF-8: {ex}") from ex
|
raise DataError(f"{display}: not valid UTF-8: {ex}") from ex
|
||||||
except OSError as ex:
|
except OSError as ex:
|
||||||
@@ -219,11 +224,13 @@ def load_game_data(override_dir: Path | None = None) -> GameData:
|
|||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# The element type of each tuple depends on the GameData field it
|
||||||
|
# fills, so it is only checked when GameData receives it.
|
||||||
values: dict[str, tuple[Any, ...]] = {}
|
values: dict[str, tuple[Any, ...]] = {}
|
||||||
for name in DATA_FILES:
|
for name in DATA_FILES:
|
||||||
if name in overrides:
|
if name in overrides:
|
||||||
assert override_dir is not None
|
assert override_dir is not None
|
||||||
source: Any = override_dir / f"{name}.json"
|
source: Traversable = override_dir / f"{name}.json"
|
||||||
display = str(source)
|
display = str(source)
|
||||||
else:
|
else:
|
||||||
source = _builtin_dir() / f"{name}.json"
|
source = _builtin_dir() / f"{name}.json"
|
||||||
|
|||||||
+19
-25
@@ -7,7 +7,7 @@ import math
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from collections.abc import Callable, Generator, Iterator, Sequence
|
from collections.abc import Callable, Generator, Iterator, Sequence
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, cast
|
from typing import Any
|
||||||
|
|
||||||
from pqcli import random
|
from pqcli import random
|
||||||
from pqcli.config import (
|
from pqcli.config import (
|
||||||
@@ -37,7 +37,7 @@ from pqcli.random import Alea, RngState
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
_SIGNALS: dict[tuple["SignalMixin", str], list[Callable[..., Any]]] = (
|
_SIGNALS: dict[tuple["SignalMixin", str], list[Callable[..., object]]] = (
|
||||||
defaultdict(list)
|
defaultdict(list)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -55,17 +55,19 @@ class SignalMixin:
|
|||||||
finally:
|
finally:
|
||||||
SignalMixin._muted -= 1
|
SignalMixin._muted -= 1
|
||||||
|
|
||||||
def emit(self, signal_name: str, *user_data: Any) -> None:
|
def emit(self, signal_name: str, *user_data: object) -> None:
|
||||||
if SignalMixin._muted:
|
if SignalMixin._muted:
|
||||||
return
|
return
|
||||||
for callback in _SIGNALS[self, signal_name]:
|
for callback in _SIGNALS[self, signal_name]:
|
||||||
callback(*user_data)
|
callback(*user_data)
|
||||||
|
|
||||||
def connect(self, signal_name: str, callback: Callable[..., Any]) -> None:
|
def connect(
|
||||||
|
self, signal_name: str, callback: Callable[..., object]
|
||||||
|
) -> None:
|
||||||
_SIGNALS[self, signal_name].append(callback)
|
_SIGNALS[self, signal_name].append(callback)
|
||||||
|
|
||||||
def disconnect(
|
def disconnect(
|
||||||
self, signal_name: str, callback: Callable[..., Any]
|
self, signal_name: str, callback: Callable[..., object]
|
||||||
) -> None:
|
) -> None:
|
||||||
try:
|
try:
|
||||||
idx = _SIGNALS[self, signal_name].index(callback)
|
idx = _SIGNALS[self, signal_name].index(callback)
|
||||||
@@ -383,7 +385,7 @@ class Player(SignalMixin):
|
|||||||
self.task: BaseTask | None = None
|
self.task: BaseTask | None = None
|
||||||
self.queue: list[BaseTask] = []
|
self.queue: list[BaseTask] = []
|
||||||
|
|
||||||
def __setstate__(self, obj: Any) -> None:
|
def __setstate__(self, obj: dict[str, Any]) -> None:
|
||||||
self.__dict__.update(obj)
|
self.__dict__.update(obj)
|
||||||
self.elapsed = obj.get("elapsed", 0.0)
|
self.elapsed = obj.get("elapsed", 0.0)
|
||||||
if "rng" not in obj:
|
if "rng" not in obj:
|
||||||
@@ -853,38 +855,30 @@ class Simulation:
|
|||||||
|
|
||||||
|
|
||||||
def special_item(data: GameData) -> str:
|
def special_item(data: GameData) -> str:
|
||||||
return (
|
return interesting_item(data) + " of " + random.choice(data.item_ofs)
|
||||||
interesting_item(data)
|
|
||||||
+ " of "
|
|
||||||
+ cast(str, random.choice(data.item_ofs))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def interesting_item(data: GameData) -> str:
|
def interesting_item(data: GameData) -> str:
|
||||||
return (
|
return random.choice(data.item_attrib) + " " + random.choice(data.specials)
|
||||||
cast(str, random.choice(data.item_attrib))
|
|
||||||
+ " "
|
|
||||||
+ cast(str, random.choice(data.specials))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def boring_item(data: GameData) -> str:
|
def boring_item(data: GameData) -> str:
|
||||||
return cast(str, random.choice(data.boring_items))
|
return random.choice(data.boring_items)
|
||||||
|
|
||||||
|
|
||||||
def impressive_guy(data: GameData) -> str:
|
def impressive_guy(data: GameData) -> str:
|
||||||
if random.below(2):
|
if random.below(2):
|
||||||
title = cast(str, random.choice(data.impressive_titles))
|
title = random.choice(data.impressive_titles)
|
||||||
race = cast(Race, random.choice(data.races))
|
race = random.choice(data.races)
|
||||||
return f"the {title} of the {plural(race.name)}"
|
return f"the {title} of the {plural(race.name)}"
|
||||||
title = cast(str, random.choice(data.impressive_titles))
|
title = random.choice(data.impressive_titles)
|
||||||
return f"{title} {generate_name()} of {generate_name()}"
|
return f"{title} {generate_name()} of {generate_name()}"
|
||||||
|
|
||||||
|
|
||||||
def unnamed_monster(data: GameData, level: int, iterations: int) -> Monster:
|
def unnamed_monster(data: GameData, level: int, iterations: int) -> Monster:
|
||||||
result = cast(Monster, random.choice(data.monsters))
|
result = random.choice(data.monsters)
|
||||||
for _ in range(iterations):
|
for _ in range(iterations):
|
||||||
alternative = cast(Monster, random.choice(data.monsters))
|
alternative = random.choice(data.monsters)
|
||||||
if abs(level - alternative.level) < abs(level - result.level):
|
if abs(level - alternative.level) < abs(level - result.level):
|
||||||
result = alternative
|
result = alternative
|
||||||
return result
|
return result
|
||||||
@@ -898,9 +892,9 @@ def named_monster(data: GameData, level: int) -> str:
|
|||||||
def pick_equipment(
|
def pick_equipment(
|
||||||
source: Sequence[EquipmentPreset], goal: int
|
source: Sequence[EquipmentPreset], goal: int
|
||||||
) -> EquipmentPreset:
|
) -> EquipmentPreset:
|
||||||
result = cast(EquipmentPreset, random.choice(source))
|
result = random.choice(source)
|
||||||
for _ in range(5):
|
for _ in range(5):
|
||||||
alternative = cast(EquipmentPreset, random.choice(source))
|
alternative = random.choice(source)
|
||||||
if abs(goal - alternative.quality) < abs(goal - result.quality):
|
if abs(goal - alternative.quality) < abs(goal - result.quality):
|
||||||
result = alternative
|
result = alternative
|
||||||
return result
|
return result
|
||||||
@@ -986,7 +980,7 @@ def monster_task(
|
|||||||
verb = (
|
verb = (
|
||||||
data.combat_verbs[0]
|
data.combat_verbs[0]
|
||||||
if len(data.combat_verbs) == 1
|
if len(data.combat_verbs) == 1
|
||||||
else cast(str, random.choice(data.combat_verbs))
|
else random.choice(data.combat_verbs)
|
||||||
)
|
)
|
||||||
description = verb.replace(TARGET_PLACEHOLDER, result)
|
description = verb.replace(TARGET_PLACEHOLDER, result)
|
||||||
return KillTask(description, duration, monster=monster)
|
return KillTask(description, duration, monster=monster)
|
||||||
|
|||||||
+5
-3
@@ -1,10 +1,12 @@
|
|||||||
import contextlib
|
import contextlib
|
||||||
import time
|
import time
|
||||||
from collections.abc import Callable, Generator, Sequence
|
from collections.abc import Callable, Generator, Sequence
|
||||||
from typing import Any
|
from typing import TypeVar
|
||||||
|
|
||||||
RngState = tuple[float, float, float, int]
|
RngState = tuple[float, float, float, int]
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
_2_POW_32 = 0x100000000
|
_2_POW_32 = 0x100000000
|
||||||
_2_POW_MINUS_32 = 2.3283064365386963e-10
|
_2_POW_MINUS_32 = 2.3283064365386963e-10
|
||||||
|
|
||||||
@@ -102,11 +104,11 @@ def using(rng: Alea) -> Generator[None, None, None]:
|
|||||||
_current = previous
|
_current = previous
|
||||||
|
|
||||||
|
|
||||||
def choice(source: Sequence[Any]) -> Any:
|
def choice(source: Sequence[T]) -> T:
|
||||||
return source[below(len(source))]
|
return source[below(len(source))]
|
||||||
|
|
||||||
|
|
||||||
def choice_low(source: Sequence[Any]) -> Any:
|
def choice_low(source: Sequence[T]) -> T:
|
||||||
return source[below_low(len(source))]
|
return source[below_low(len(source))]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import sys
|
|||||||
import time
|
import time
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Any
|
from types import FrameType
|
||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
from pqcli import lingo
|
from pqcli import lingo
|
||||||
from pqcli.config import PRIME_STATS
|
from pqcli.config import PRIME_STATS
|
||||||
@@ -25,6 +26,9 @@ LOGO = r"""
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
class MainMenu(enum.IntEnum):
|
class MainMenu(enum.IntEnum):
|
||||||
create = 1
|
create = 1
|
||||||
play = 2
|
play = 2
|
||||||
@@ -43,7 +47,7 @@ class BasicUserInterface(BaseUserInterface):
|
|||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(roster, player, args, data)
|
super().__init__(roster, player, args, data)
|
||||||
|
|
||||||
def signal_handler(sig: Any, frame: Any) -> None:
|
def signal_handler(sig: int, frame: FrameType | None) -> None:
|
||||||
logging.info("Quitting")
|
logging.info("Quitting")
|
||||||
if self.args.use_saves:
|
if self.args.use_saves:
|
||||||
self.roster.save()
|
self.roster.save()
|
||||||
@@ -217,9 +221,9 @@ class BasicUserInterface(BaseUserInterface):
|
|||||||
|
|
||||||
def menu(
|
def menu(
|
||||||
self,
|
self,
|
||||||
options: Sequence[tuple[Any, str]],
|
options: Sequence[tuple[T, str]],
|
||||||
title: str | None = None,
|
title: str | None = None,
|
||||||
) -> Any:
|
) -> T:
|
||||||
print()
|
print()
|
||||||
if title:
|
if title:
|
||||||
print(f"{title}:")
|
print(f"{title}:")
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import curses
|
|||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
import time
|
import time
|
||||||
from typing import Any
|
from types import FrameType
|
||||||
|
|
||||||
from pqcli import lingo
|
from pqcli import lingo
|
||||||
from pqcli.config import Class, Race
|
from pqcli.config import Class, Race
|
||||||
@@ -68,7 +68,7 @@ class CursesUserInterface(BaseUserInterface):
|
|||||||
curses.init_pair(COLOR_PROGRESSBAR, 0, 9)
|
curses.init_pair(COLOR_PROGRESSBAR, 0, 9)
|
||||||
curses.init_pair(COLOR_HIGHLIGHT, 3, -1)
|
curses.init_pair(COLOR_HIGHLIGHT, 3, -1)
|
||||||
|
|
||||||
def signal_handler(sig: Any, frame: Any) -> None:
|
def signal_handler(sig: int, frame: FrameType | None) -> None:
|
||||||
if self.args.use_saves:
|
if self.args.use_saves:
|
||||||
self.roster.save()
|
self.roster.save()
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from typing import Any
|
from typing import Generic, ParamSpec
|
||||||
|
|
||||||
|
P = ParamSpec("P")
|
||||||
|
|
||||||
|
|
||||||
class EventHandler:
|
class EventHandler(Generic[P]):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.callbacks: list[Callable[..., Any]] = []
|
self.callbacks: list[Callable[P, object]] = []
|
||||||
|
|
||||||
def __call__(self, *args: Any, **kwargs: Any) -> None:
|
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> None:
|
||||||
for callback in self.callbacks:
|
for callback in self.callbacks:
|
||||||
callback(*args, **kwargs)
|
callback(*args, **kwargs)
|
||||||
|
|
||||||
def __iadd__(self, callback: Callable[..., Any]) -> "EventHandler":
|
def __iadd__(self, callback: Callable[P, object]) -> "EventHandler[P]":
|
||||||
self.callbacks.append(callback)
|
self.callbacks.append(callback)
|
||||||
return self
|
return self
|
||||||
@@ -2,7 +2,7 @@ import curses
|
|||||||
import curses.ascii
|
import curses.ascii
|
||||||
from collections.abc import Callable, Iterable
|
from collections.abc import Callable, Iterable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, TypeVar, overload
|
from typing import TypeVar, overload
|
||||||
|
|
||||||
KEYS_CYCLE = {curses.ascii.TAB}
|
KEYS_CYCLE = {curses.ascii.TAB}
|
||||||
KEYS_DOWN = set(map(ord, "jJ")) | {curses.KEY_DOWN}
|
KEYS_DOWN = set(map(ord, "jJ")) | {curses.KEY_DOWN}
|
||||||
@@ -16,7 +16,7 @@ KEYS_CANCEL = set(map(ord, "qQ")) | {curses.ascii.ESC}
|
|||||||
class Choice:
|
class Choice:
|
||||||
keys: list[int]
|
keys: list[int]
|
||||||
desc: str
|
desc: str
|
||||||
callback: Callable[..., Any]
|
callback: Callable[[], object]
|
||||||
|
|
||||||
|
|
||||||
ItemT = TypeVar("ItemT")
|
ItemT = TypeVar("ItemT")
|
||||||
@@ -31,5 +31,7 @@ def first(source: Iterable[ItemT]) -> ItemT | None: ...
|
|||||||
def first(source: Iterable[ItemT], default: DefaultT) -> ItemT | DefaultT: ...
|
def first(source: Iterable[ItemT], default: DefaultT) -> ItemT | DefaultT: ...
|
||||||
|
|
||||||
|
|
||||||
def first(source: Iterable[Any], default: Any = None) -> Any:
|
def first(
|
||||||
|
source: Iterable[ItemT], default: DefaultT | None = None
|
||||||
|
) -> ItemT | DefaultT | None:
|
||||||
return next(iter(source), default)
|
return next(iter(source), default)
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
from typing import Any
|
import curses
|
||||||
|
|
||||||
|
|
||||||
class BaseView:
|
class BaseView:
|
||||||
def __init__(self, screen: Any) -> None:
|
def __init__(self, screen: curses.window) -> None:
|
||||||
self.screen = screen
|
self.screen = screen
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
|
import curses
|
||||||
import functools
|
import functools
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from pqcli.lingo import act_name, to_roman
|
from pqcli.lingo import act_name, to_roman
|
||||||
|
from pqcli.mechanic import Player
|
||||||
from pqcli.roster import Roster
|
from pqcli.roster import Roster
|
||||||
from pqcli.ui.curses.util import KEYS_CANCEL, Choice
|
from pqcli.ui.curses.util import KEYS_CANCEL, Choice
|
||||||
from pqcli.ui.curses.views.menu_view import MenuView
|
from pqcli.ui.curses.views.menu_view import MenuView
|
||||||
|
|
||||||
|
|
||||||
class ChooseCharacterView(MenuView):
|
class ChooseCharacterView(MenuView[Player]):
|
||||||
def __init__(self, screen: Any, roster: Roster, title: str) -> None:
|
def __init__(
|
||||||
|
self, screen: curses.window, roster: Roster, title: str
|
||||||
|
) -> None:
|
||||||
super().__init__(screen, title)
|
super().__init__(screen, title)
|
||||||
|
|
||||||
for y, player in enumerate(roster.players, 1):
|
for y, player in enumerate(roster.players, 1):
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Any
|
import curses
|
||||||
|
|
||||||
from pqcli.ui.curses.event_handler import EventHandler
|
from pqcli.ui.curses.event_handler import EventHandler
|
||||||
from pqcli.ui.curses.util import Choice
|
from pqcli.ui.curses.util import Choice
|
||||||
@@ -8,12 +8,12 @@ from .base_view import BaseView
|
|||||||
|
|
||||||
|
|
||||||
class ConfirmView(BaseView):
|
class ConfirmView(BaseView):
|
||||||
def __init__(self, screen: Any, title: str) -> None:
|
def __init__(self, screen: curses.window, title: str) -> None:
|
||||||
super().__init__(screen)
|
super().__init__(screen)
|
||||||
|
|
||||||
self.title = f"{title}:"
|
self.title = f"{title}:"
|
||||||
self.on_cancel = EventHandler()
|
self.on_cancel = EventHandler[[]]()
|
||||||
self.on_confirm = EventHandler()
|
self.on_confirm = EventHandler[[]]()
|
||||||
|
|
||||||
self.choices: list[Choice] = [
|
self.choices: list[Choice] = [
|
||||||
Choice(
|
Choice(
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
|
import curses
|
||||||
import functools
|
import functools
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from pqcli.config import Class
|
from pqcli.config import Class
|
||||||
from pqcli.ui.curses.util import KEYS_CANCEL, Choice
|
from pqcli.ui.curses.util import KEYS_CANCEL, Choice
|
||||||
from pqcli.ui.curses.views.menu_view import MenuView
|
from pqcli.ui.curses.views.menu_view import MenuView
|
||||||
|
|
||||||
|
|
||||||
class ChooseCharacterClassView(MenuView):
|
class ChooseCharacterClassView(MenuView[Class]):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
screen: Any,
|
screen: curses.window,
|
||||||
classes: Sequence[Class],
|
classes: Sequence[Class],
|
||||||
class_: Class | None = None,
|
class_: Class | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import curses
|
import curses
|
||||||
import curses.ascii
|
import curses.ascii
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from pqcli.mechanic import generate_name
|
from pqcli.mechanic import generate_name
|
||||||
from pqcli.ui.curses.event_handler import EventHandler
|
from pqcli.ui.curses.event_handler import EventHandler
|
||||||
@@ -10,15 +9,17 @@ from pqcli.ui.curses.widgets.focusable import focus_standout
|
|||||||
|
|
||||||
|
|
||||||
class ChooseCharacterNameView(BaseView):
|
class ChooseCharacterNameView(BaseView):
|
||||||
def __init__(self, screen: Any, character_name: str | None = None) -> None:
|
def __init__(
|
||||||
|
self, screen: curses.window, character_name: str | None = None
|
||||||
|
) -> None:
|
||||||
super().__init__(screen)
|
super().__init__(screen)
|
||||||
|
|
||||||
self.on_cancel = EventHandler()
|
self.on_cancel = EventHandler[[]]()
|
||||||
self.on_confirm = EventHandler()
|
self.on_confirm = EventHandler[[str]]()
|
||||||
|
|
||||||
self._active_widget = 0
|
self._active_widget = 0
|
||||||
self._win: Any | None = None
|
self._win: curses.window | None = None
|
||||||
self._text_win: Any | None = None
|
self._text_win: curses.window | None = None
|
||||||
self._text = character_name or generate_name()
|
self._text = character_name or generate_name()
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
|
import curses
|
||||||
import functools
|
import functools
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from pqcli.config import Race
|
from pqcli.config import Race
|
||||||
from pqcli.ui.curses.util import KEYS_CANCEL, Choice
|
from pqcli.ui.curses.util import KEYS_CANCEL, Choice
|
||||||
from pqcli.ui.curses.views.menu_view import MenuView
|
from pqcli.ui.curses.views.menu_view import MenuView
|
||||||
|
|
||||||
|
|
||||||
class ChooseCharacterRaceView(MenuView):
|
class ChooseCharacterRaceView(MenuView[Race]):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
screen: Any,
|
screen: curses.window,
|
||||||
races: Sequence[Race],
|
races: Sequence[Race],
|
||||||
race: Race | None = None,
|
race: Race | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
import curses
|
||||||
import curses.ascii
|
import curses.ascii
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from pqcli.config import PRIME_STATS
|
from pqcli.config import PRIME_STATS
|
||||||
from pqcli.mechanic import StatsBuilder
|
from pqcli.mechanic import Stats, StatsBuilder
|
||||||
|
from pqcli.random import RngState
|
||||||
from pqcli.ui.curses.event_handler import EventHandler
|
from pqcli.ui.curses.event_handler import EventHandler
|
||||||
from pqcli.ui.curses.util import KEYS_CANCEL, KEYS_CYCLE, KEYS_DOWN, KEYS_UP
|
from pqcli.ui.curses.util import KEYS_CANCEL, KEYS_CYCLE, KEYS_DOWN, KEYS_UP
|
||||||
from pqcli.ui.curses.views.base_view import BaseView
|
from pqcli.ui.curses.views.base_view import BaseView
|
||||||
@@ -10,15 +11,15 @@ from pqcli.ui.curses.widgets.focusable import focus_standout
|
|||||||
|
|
||||||
|
|
||||||
class ChooseCharacterStatsView(BaseView):
|
class ChooseCharacterStatsView(BaseView):
|
||||||
def __init__(self, screen: Any) -> None:
|
def __init__(self, screen: curses.window) -> None:
|
||||||
super().__init__(screen)
|
super().__init__(screen)
|
||||||
|
|
||||||
self.on_cancel = EventHandler()
|
self.on_cancel = EventHandler[[]]()
|
||||||
self.on_confirm = EventHandler()
|
self.on_confirm = EventHandler[[Stats, RngState]]()
|
||||||
|
|
||||||
self._active_widget = 0
|
self._active_widget = 0
|
||||||
self._win: Any | None = None
|
self._win: curses.window | None = None
|
||||||
self._stats_win: Any | None = None
|
self._stats_win: curses.window | None = None
|
||||||
|
|
||||||
self._stats_builder = StatsBuilder()
|
self._stats_builder = StatsBuilder()
|
||||||
self._stats = self._stats_builder.roll()
|
self._stats = self._stats_builder.roll()
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import argparse
|
|||||||
import curses
|
import curses
|
||||||
import curses.ascii
|
import curses.ascii
|
||||||
import datetime
|
import datetime
|
||||||
from typing import Any, Protocol, runtime_checkable
|
from typing import Protocol, runtime_checkable
|
||||||
|
|
||||||
from pqcli.game_data import GameData
|
from pqcli.game_data import GameData
|
||||||
from pqcli.mechanic import Player, SignalMixin, Simulation
|
from pqcli.mechanic import Player, SignalMixin, Simulation
|
||||||
@@ -44,14 +44,14 @@ class PageScrollable(Protocol):
|
|||||||
class GameView(BaseView):
|
class GameView(BaseView):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
screen: Any,
|
screen: curses.window,
|
||||||
roster: Roster,
|
roster: Roster,
|
||||||
player: Player,
|
player: Player,
|
||||||
args: argparse.Namespace,
|
args: argparse.Namespace,
|
||||||
data: GameData,
|
data: GameData,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(screen)
|
super().__init__(screen)
|
||||||
self.on_exit = EventHandler()
|
self.on_exit = EventHandler[[]]()
|
||||||
|
|
||||||
self._roster = roster
|
self._roster = roster
|
||||||
self._player = player
|
self._player = player
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Any
|
import curses
|
||||||
|
|
||||||
from pqcli.mechanic import Player, StatType
|
from pqcli.mechanic import Player, StatType
|
||||||
|
|
||||||
@@ -7,7 +7,13 @@ from .progress_bar_window import DataTableProgressBarWindow
|
|||||||
|
|
||||||
class CharacterSheetWindow(DataTableProgressBarWindow):
|
class CharacterSheetWindow(DataTableProgressBarWindow):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, player: Player, parent: Any, h: int, w: int, y: int, x: int
|
self,
|
||||||
|
player: Player,
|
||||||
|
parent: curses.window,
|
||||||
|
h: int,
|
||||||
|
w: int,
|
||||||
|
y: int,
|
||||||
|
x: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
parent,
|
parent,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Any
|
import curses
|
||||||
|
|
||||||
from pqcli.mechanic import EquipmentType, Player
|
from pqcli.mechanic import EquipmentType, Player
|
||||||
from pqcli.ui.curses.widgets import DataTable, Focusable, WindowWrapper
|
from pqcli.ui.curses.widgets import DataTable, Focusable, WindowWrapper
|
||||||
@@ -6,7 +6,13 @@ from pqcli.ui.curses.widgets import DataTable, Focusable, WindowWrapper
|
|||||||
|
|
||||||
class EquipmentWindow(Focusable, WindowWrapper):
|
class EquipmentWindow(Focusable, WindowWrapper):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, player: Player, parent: Any, h: int, w: int, y: int, x: int
|
self,
|
||||||
|
player: Player,
|
||||||
|
parent: curses.window,
|
||||||
|
h: int,
|
||||||
|
w: int,
|
||||||
|
y: int,
|
||||||
|
x: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(parent, h, w, y, x)
|
super().__init__(parent, h, w, y, x)
|
||||||
self._on_focus_change += self._render
|
self._on_focus_change += self._render
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Any
|
import curses
|
||||||
|
|
||||||
from pqcli.mechanic import InventoryItem, Player
|
from pqcli.mechanic import InventoryItem, Player
|
||||||
|
|
||||||
@@ -7,7 +7,13 @@ from .progress_bar_window import DataTableProgressBarWindow
|
|||||||
|
|
||||||
class InventoryWindow(DataTableProgressBarWindow):
|
class InventoryWindow(DataTableProgressBarWindow):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, player: Player, parent: Any, h: int, w: int, y: int, x: int
|
self,
|
||||||
|
player: Player,
|
||||||
|
parent: curses.window,
|
||||||
|
h: int,
|
||||||
|
w: int,
|
||||||
|
y: int,
|
||||||
|
x: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
parent,
|
parent,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Any
|
import curses
|
||||||
|
|
||||||
from pqcli.lingo import act_name
|
from pqcli.lingo import act_name
|
||||||
from pqcli.mechanic import Player
|
from pqcli.mechanic import Player
|
||||||
@@ -10,7 +10,13 @@ class PlotWindow(ListBoxProgressBarWindow):
|
|||||||
cutoff = 100
|
cutoff = 100
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, player: Player, parent: Any, h: int, w: int, y: int, x: int
|
self,
|
||||||
|
player: Player,
|
||||||
|
parent: curses.window,
|
||||||
|
h: int,
|
||||||
|
w: int,
|
||||||
|
y: int,
|
||||||
|
x: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
parent, h, w, y, x, " Plot Development ", show_time=True
|
parent, h, w, y, x, " Plot Development ", show_time=True
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import curses
|
import curses
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from pqcli.ui.curses.widgets import (
|
from pqcli.ui.curses.widgets import (
|
||||||
DataTable,
|
DataTable,
|
||||||
@@ -13,7 +12,7 @@ from pqcli.ui.curses.widgets import (
|
|||||||
class ProgressBarWindow(Focusable, WindowWrapper):
|
class ProgressBarWindow(Focusable, WindowWrapper):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
parent: Any,
|
parent: curses.window,
|
||||||
h: int,
|
h: int,
|
||||||
w: int,
|
w: int,
|
||||||
y: int,
|
y: int,
|
||||||
@@ -82,7 +81,7 @@ class ProgressBarWindow(Focusable, WindowWrapper):
|
|||||||
class DataTableProgressBarWindow(ProgressBarWindow):
|
class DataTableProgressBarWindow(ProgressBarWindow):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
parent: Any,
|
parent: curses.window,
|
||||||
h: int,
|
h: int,
|
||||||
w: int,
|
w: int,
|
||||||
y: int,
|
y: int,
|
||||||
@@ -123,7 +122,7 @@ class DataTableProgressBarWindow(ProgressBarWindow):
|
|||||||
class ListBoxProgressBarWindow(ProgressBarWindow):
|
class ListBoxProgressBarWindow(ProgressBarWindow):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
parent: Any,
|
parent: curses.window,
|
||||||
h: int,
|
h: int,
|
||||||
w: int,
|
w: int,
|
||||||
y: int,
|
y: int,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Any
|
import curses
|
||||||
|
|
||||||
from pqcli.mechanic import Player
|
from pqcli.mechanic import Player
|
||||||
|
|
||||||
@@ -9,7 +9,13 @@ class QuestBookWindow(ListBoxProgressBarWindow):
|
|||||||
cutoff = 100
|
cutoff = 100
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, player: Player, parent: Any, h: int, w: int, y: int, x: int
|
self,
|
||||||
|
player: Player,
|
||||||
|
parent: curses.window,
|
||||||
|
h: int,
|
||||||
|
w: int,
|
||||||
|
y: int,
|
||||||
|
x: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(parent, h, w, y, x, " Quests ", show_time=True)
|
super().__init__(parent, h, w, y, x, " Quests ", show_time=True)
|
||||||
self._on_focus_change += self._render
|
self._on_focus_change += self._render
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Any
|
import curses
|
||||||
|
|
||||||
from pqcli.lingo import to_roman
|
from pqcli.lingo import to_roman
|
||||||
from pqcli.mechanic import Player, Spell
|
from pqcli.mechanic import Player, Spell
|
||||||
@@ -7,7 +7,13 @@ from pqcli.ui.curses.widgets import DataTable, Focusable, WindowWrapper
|
|||||||
|
|
||||||
class SpellBookWindow(Focusable, WindowWrapper):
|
class SpellBookWindow(Focusable, WindowWrapper):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, player: Player, parent: Any, h: int, w: int, y: int, x: int
|
self,
|
||||||
|
player: Player,
|
||||||
|
parent: curses.window,
|
||||||
|
h: int,
|
||||||
|
w: int,
|
||||||
|
y: int,
|
||||||
|
x: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(parent, h, w, y, x)
|
super().__init__(parent, h, w, y, x)
|
||||||
self._on_focus_change += self._render
|
self._on_focus_change += self._render
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Any
|
import curses
|
||||||
|
|
||||||
from pqcli.mechanic import Player
|
from pqcli.mechanic import Player
|
||||||
from pqcli.ui.curses.widgets import (
|
from pqcli.ui.curses.widgets import (
|
||||||
@@ -11,7 +11,13 @@ from pqcli.ui.curses.widgets import (
|
|||||||
|
|
||||||
class TaskProgressWindow(Focusable, WindowWrapper):
|
class TaskProgressWindow(Focusable, WindowWrapper):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, player: Player, parent: Any, h: int, w: int, y: int, x: int
|
self,
|
||||||
|
player: Player,
|
||||||
|
parent: curses.window,
|
||||||
|
h: int,
|
||||||
|
w: int,
|
||||||
|
y: int,
|
||||||
|
x: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(parent, h, w, y, x)
|
super().__init__(parent, h, w, y, x)
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
from typing import Any
|
import curses
|
||||||
|
from typing import Generic, TypeVar
|
||||||
|
|
||||||
from pqcli.ui.curses.event_handler import EventHandler
|
from pqcli.ui.curses.event_handler import EventHandler
|
||||||
from pqcli.ui.curses.util import Choice
|
from pqcli.ui.curses.util import Choice
|
||||||
from pqcli.ui.curses.views.base_view import BaseView
|
from pqcli.ui.curses.views.base_view import BaseView
|
||||||
from pqcli.ui.curses.widgets import Menu
|
from pqcli.ui.curses.widgets import Menu
|
||||||
|
|
||||||
|
ItemT = TypeVar("ItemT")
|
||||||
|
|
||||||
class MenuView(BaseView):
|
|
||||||
|
class MenuView(BaseView, Generic[ItemT]):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, screen: Any, title: str, active_choice: int = 0
|
self, screen: curses.window, title: str, active_choice: int = 0
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(screen)
|
super().__init__(screen)
|
||||||
|
|
||||||
self.on_cancel = EventHandler()
|
self.on_cancel = EventHandler[[]]()
|
||||||
self.on_confirm = EventHandler()
|
self.on_confirm = EventHandler[[ItemT]]()
|
||||||
|
|
||||||
self._title = f"{title}:"
|
self._title = f"{title}:"
|
||||||
self._choices: list[Choice] = []
|
self._choices: list[Choice] = []
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import curses
|
import curses
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from pqcli.ui.curses.colors import COLOR_LOGO, COLOR_LOGO_ALT, has_colors
|
from pqcli.ui.curses.colors import COLOR_LOGO, COLOR_LOGO_ALT, has_colors
|
||||||
from pqcli.ui.curses.event_handler import EventHandler
|
from pqcli.ui.curses.event_handler import EventHandler
|
||||||
@@ -52,13 +51,13 @@ class MainMenu(Menu):
|
|||||||
|
|
||||||
|
|
||||||
class RosterView(BaseView):
|
class RosterView(BaseView):
|
||||||
def __init__(self, screen: Any) -> None:
|
def __init__(self, screen: curses.window) -> None:
|
||||||
super().__init__(screen)
|
super().__init__(screen)
|
||||||
|
|
||||||
self.on_create = EventHandler()
|
self.on_create = EventHandler[[]]()
|
||||||
self.on_play = EventHandler()
|
self.on_play = EventHandler[[]]()
|
||||||
self.on_delete = EventHandler()
|
self.on_delete = EventHandler[[]]()
|
||||||
self.on_quit = EventHandler()
|
self.on_quit = EventHandler[[]]()
|
||||||
|
|
||||||
self.choices: list[Choice] = [
|
self.choices: list[Choice] = [
|
||||||
Choice(
|
Choice(
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import curses
|
import curses
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
|
|
||||||
class Widget:
|
class Widget:
|
||||||
@@ -12,10 +11,10 @@ class Widget:
|
|||||||
|
|
||||||
class WindowWrapper(Widget):
|
class WindowWrapper(Widget):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, parent: Any | None, h: int, w: int, y: int, x: int
|
self, parent: curses.window | None, h: int, w: int, y: int, x: int
|
||||||
) -> None:
|
) -> None:
|
||||||
self._parent = parent
|
self._parent = parent
|
||||||
self._win: Any | None
|
self._win: curses.window | None
|
||||||
|
|
||||||
if not parent:
|
if not parent:
|
||||||
self._win = None
|
self._win = None
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import curses
|
import curses
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from pqcli.ui.curses.colors import COLOR_HIGHLIGHT, has_colors
|
from pqcli.ui.curses.colors import COLOR_HIGHLIGHT, has_colors
|
||||||
|
|
||||||
from .scrollable import Scrollable
|
from .scrollable import Scrollable
|
||||||
|
|
||||||
|
|
||||||
class DataTable(Scrollable):
|
class DataTable(Scrollable[tuple[str, str]]):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
parent: Any,
|
parent: curses.window | None,
|
||||||
h: int,
|
h: int,
|
||||||
w: int,
|
w: int,
|
||||||
y: int,
|
y: int,
|
||||||
@@ -19,7 +18,6 @@ class DataTable(Scrollable):
|
|||||||
super().__init__(parent, h, w, y, x)
|
super().__init__(parent, h, w, y, x)
|
||||||
|
|
||||||
self._align_right = align_right
|
self._align_right = align_right
|
||||||
self._items: list[tuple[str, str]]
|
|
||||||
self._selected: int | None = None
|
self._selected: int | None = None
|
||||||
|
|
||||||
def add(self, text: str, value: str) -> None:
|
def add(self, text: str, value: str) -> None:
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ from pqcli.ui.curses.event_handler import EventHandler
|
|||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def focus_standout(focused: bool, win: Any) -> Generator:
|
def focus_standout(
|
||||||
|
focused: bool, win: curses.window
|
||||||
|
) -> Generator[None, None, None]:
|
||||||
if focused:
|
if focused:
|
||||||
if has_colors():
|
if has_colors():
|
||||||
win.attron(curses.color_pair(COLOR_FOCUSED))
|
win.attron(curses.color_pair(COLOR_FOCUSED))
|
||||||
@@ -25,7 +27,7 @@ def focus_standout(focused: bool, win: Any) -> Generator:
|
|||||||
class Focusable:
|
class Focusable:
|
||||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
self._focused = False
|
self._focused = False
|
||||||
self._on_focus_change = EventHandler()
|
self._on_focus_change = EventHandler[[]]()
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -38,6 +40,8 @@ class Focusable:
|
|||||||
self._on_focus_change()
|
self._on_focus_change()
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def focus_standout(self, win: Any) -> Generator:
|
def focus_standout(
|
||||||
|
self, win: curses.window
|
||||||
|
) -> Generator[None, None, None]:
|
||||||
with focus_standout(self._focused, win):
|
with focus_standout(self._focused, win):
|
||||||
yield
|
yield
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
import curses
|
import curses
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from pqcli.ui.curses.colors import COLOR_HIGHLIGHT, has_colors
|
from pqcli.ui.curses.colors import COLOR_HIGHLIGHT, has_colors
|
||||||
|
|
||||||
from .scrollable import Scrollable
|
from .scrollable import Scrollable
|
||||||
|
|
||||||
|
|
||||||
class ListBox(Scrollable):
|
class ListBox(Scrollable[str]):
|
||||||
_items: list[str]
|
def __init__(
|
||||||
|
self, parent: curses.window | None, h: int, w: int, y: int, x: int
|
||||||
def __init__(self, parent: Any, h: int, w: int, y: int, x: int) -> None:
|
) -> None:
|
||||||
super().__init__(parent, h, w, y, x)
|
super().__init__(parent, h, w, y, x)
|
||||||
self._selected: int | None = None
|
self._selected: int | None = None
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import curses
|
import curses
|
||||||
import curses.ascii
|
import curses.ascii
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from pqcli.ui.curses.util import KEYS_CYCLE, KEYS_DOWN, KEYS_UP, Choice
|
from pqcli.ui.curses.util import KEYS_CYCLE, KEYS_DOWN, KEYS_UP, Choice
|
||||||
from pqcli.ui.curses.widgets.focusable import focus_standout
|
from pqcli.ui.curses.widgets.focusable import focus_standout
|
||||||
@@ -29,7 +28,7 @@ class Menu(Widget):
|
|||||||
w = max(map(len, all_lines)) + 1
|
w = max(map(len, all_lines)) + 1
|
||||||
h = len(all_lines) + 1
|
h = len(all_lines) + 1
|
||||||
pad = curses.newpad(h, w)
|
pad = curses.newpad(h, w)
|
||||||
self._pad: Any | None = pad
|
self._pad: curses.window | None = pad
|
||||||
|
|
||||||
for y, line in enumerate(self._header_lines):
|
for y, line in enumerate(self._header_lines):
|
||||||
pad.move(y, 0)
|
pad.move(y, 0)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import curses
|
import curses
|
||||||
import datetime
|
import datetime
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
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.colors import COLOR_PROGRESSBAR, has_colors
|
||||||
@@ -9,7 +8,13 @@ from pqcli.ui.curses.widgets.base import WindowWrapper
|
|||||||
|
|
||||||
class ProgressBar(WindowWrapper):
|
class ProgressBar(WindowWrapper):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, parent: Any, h: int, w: int, y: int, x: int, show_time: bool
|
self,
|
||||||
|
parent: curses.window | None,
|
||||||
|
h: int,
|
||||||
|
w: int,
|
||||||
|
y: int,
|
||||||
|
x: int,
|
||||||
|
show_time: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(parent, h, w, y, x)
|
super().__init__(parent, h, w, y, x)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import curses
|
import curses
|
||||||
from typing import Any
|
from typing import Generic, TypeVar
|
||||||
|
|
||||||
from pqcli.ui.curses.colors import (
|
from pqcli.ui.curses.colors import (
|
||||||
COLOR_SCROLLBAR_THUMB,
|
COLOR_SCROLLBAR_THUMB,
|
||||||
@@ -9,13 +9,17 @@ from pqcli.ui.curses.colors import (
|
|||||||
|
|
||||||
from .base import WindowWrapper
|
from .base import WindowWrapper
|
||||||
|
|
||||||
|
ItemT = TypeVar("ItemT")
|
||||||
|
|
||||||
class Scrollable(WindowWrapper):
|
|
||||||
def __init__(self, parent: Any, h: int, w: int, y: int, x: int) -> None:
|
class Scrollable(WindowWrapper, Generic[ItemT]):
|
||||||
|
def __init__(
|
||||||
|
self, parent: curses.window | None, h: int, w: int, y: int, x: int
|
||||||
|
) -> None:
|
||||||
super().__init__(parent, h, w, y, x)
|
super().__init__(parent, h, w, y, x)
|
||||||
self._scroll_pos = 0
|
self._scroll_pos = 0
|
||||||
self._pad: Any | None = curses.newpad(1, 1)
|
self._pad: curses.window | None = curses.newpad(1, 1)
|
||||||
self._items: list[Any] = []
|
self._items: list[ItemT] = []
|
||||||
|
|
||||||
def clear(self) -> None:
|
def clear(self) -> None:
|
||||||
self._items = []
|
self._items = []
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import datetime
|
|||||||
import json
|
import json
|
||||||
import pickle
|
import pickle
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -18,7 +17,7 @@ from pqcli.game_data import (
|
|||||||
from pqcli.mechanic import Player, Stats, monster_task
|
from pqcli.mechanic import Player, Stats, monster_task
|
||||||
|
|
||||||
|
|
||||||
def write(directory: Path, name: str, value: Any) -> Path:
|
def write(directory: Path, name: str, value: object) -> Path:
|
||||||
path = directory / f"{name}.json"
|
path = directory / f"{name}.json"
|
||||||
path.write_text(json.dumps(value), encoding="utf-8")
|
path.write_text(json.dumps(value), encoding="utf-8")
|
||||||
return path
|
return path
|
||||||
@@ -98,7 +97,7 @@ def test_unknown_file_warns(
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_invalid_override_raises(
|
def test_invalid_override_raises(
|
||||||
tmp_path: Path, name: str, value: Any, message: str
|
tmp_path: Path, name: str, value: object, message: str
|
||||||
) -> None:
|
) -> None:
|
||||||
write(tmp_path, name, value)
|
write(tmp_path, name, value)
|
||||||
with pytest.raises(DataError) as excinfo:
|
with pytest.raises(DataError) as excinfo:
|
||||||
|
|||||||
Reference in new issue
Block a user