Mi2dRPGamEng/engine/controls_menu.py

53 lines
2.0 KiB
Python

from __future__ import annotations
from dataclasses import dataclass, field
from engine.config import DEFAULT_KEYBINDINGS, KeyBindings
# Keys a rebind capture should never accept - they're the menu's own navigation/escape keys,
# so binding a game action to one of them would make the controls menu itself unusable.
RESERVED_CAPTURE_KEYS = {"Escape", "ArrowUp", "ArrowDown", "Enter"}
@dataclass
class ControlsMenu:
"""Pure interaction state for the "configure controls" screen: list every bindable action,
navigate up/down, and rebind the highlighted action's primary key to whatever key is
pressed next. Reads/writes through the same KeyBindings the game itself uses (engine/
config.py) - a rebind takes effect immediately (live) and can be written back to disk via
save(). No rendering here - same boundary as the rest of engine/ui.py.
"""
keybindings: KeyBindings
actions: list[str] = field(default_factory=lambda: list(DEFAULT_KEYBINDINGS))
selected_index: int = 0
awaiting_key: bool = False
def move(self, delta: int) -> None:
self.selected_index = (self.selected_index + delta) % len(self.actions)
def selected_action(self) -> str:
return self.actions[self.selected_index]
def begin_rebind(self) -> None:
self.awaiting_key = True
def cancel_rebind(self) -> None:
self.awaiting_key = False
def capture_key(self, key: str) -> bool:
"""Feeds the next raw keypress into the pending rebind, if one is in progress (see
begin_rebind). Returns whether it was actually applied - a reserved menu key is ignored
(capture mode stays open so the player can try again) rather than rebinding onto it.
"""
if not self.awaiting_key:
return False
if key in RESERVED_CAPTURE_KEYS:
return False
self.keybindings.rebind(self.selected_action(), key)
self.awaiting_key = False
return True
def save(self, path) -> None:
self.keybindings.save(path)