nodes/level_up_ui.pyΒΆ

Part of Dungeon Explorer.

  1"""Stat point allocation screen shown on level up."""
  2
  3from simvx.core import Control, Property
  4from simvx.core.input import MouseButton
  5from simvx.core.ui.enums import AnchorPreset
  6
  7from ._back_button import back_button_hit, check_menu_click, draw_back_button
  8
  9
 10class LevelUpUI(Control):
 11    """Stat point allocation overlay."""
 12
 13    visible = Property(
 14        False,
 15        coerce=bool,
 16        hint="Whether this node and its subtree are drawn",
 17        on_change="_on_visible_changed",
 18    )
 19
 20    STAT_NAMES = ["Strength", "Dexterity", "Vitality", "Intelligence"]
 21    STAT_KEYS = ["strength", "dexterity", "vitality", "intelligence"]
 22
 23    def __init__(self, **kwargs):
 24        super().__init__(name="LevelUpUI", **kwargs)
 25        self.set_anchor_preset(AnchorPreset.FULL_RECT)
 26
 27        self._selected = 0
 28        self._player = None
 29
 30    def set_player(self, player) -> None:
 31        self._player = player
 32
 33    def show(self):
 34        self._selected = 0
 35        self.show_overlay("blocking", dismiss=True)
 36
 37    # -- Input --
 38
 39    def _on_gui_input(self, event):
 40        if not self.visible:
 41            return
 42        if event.button == MouseButton.LEFT and event.pressed:
 43            self._handle_click(float(event.position[0]), float(event.position[1]), event)
 44            return
 45        if event.key and event.pressed:
 46            if event.key in ("w", "up"):
 47                self._selected = (self._selected - 1) % len(self.STAT_NAMES)
 48                event.handled = True
 49            elif event.key in ("s", "down"):
 50                self._selected = (self._selected + 1) % len(self.STAT_NAMES)
 51                event.handled = True
 52            elif event.key in ("space", "enter", "return", "e"):
 53                self._allocate()
 54                if self._player is None or self._player.stat_points <= 0:
 55                    self.cancel_requested.emit()
 56                    self.close_overlay()
 57                event.handled = True
 58
 59    def _handle_click(self, mx: float, my: float, event):
 60        if back_button_hit(mx, my):
 61            self.cancel_requested.emit()
 62            self.close_overlay()
 63            event.handled = True
 64            return
 65        sw, _ = self._screen_size()
 66        cx = sw / 2
 67        w = 300
 68        x = cx - w / 2
 69        sy = 280
 70        idx = check_menu_click(mx, my, len(self.STAT_NAMES), x + 20, sy, w - 40, 24, 30)
 71        if idx is not None:
 72            self._selected = idx
 73            self._allocate()
 74            if self._player is None or self._player.stat_points <= 0:
 75                self.cancel_requested.emit()
 76                self.close_overlay()
 77            event.handled = True
 78
 79    def _allocate(self):
 80        if self._player is None or self._player.stat_points <= 0:
 81            return
 82        key = self.STAT_KEYS[self._selected]
 83        current = getattr(self._player, key, 0)
 84        setattr(self._player, key, current + 1)
 85        self._player.stat_points -= 1
 86
 87    # -- Rendering --
 88
 89    def _screen_size(self) -> tuple[float, float]:
 90        if self.tree is not None:
 91            sw, sh = self.tree.screen_size
 92            return float(sw), float(sh)
 93        return 1280.0, 720.0
 94
 95    def on_draw(self, renderer):
 96        if not self.visible or self._player is None:
 97            return
 98        p = self._player
 99        sw, sh = self._screen_size()
100        cx = sw / 2
101        w, h = 300, 260
102        x, y = cx - w / 2, 200
103
104        renderer.draw_rect((0, 0), (sw, sh), colour=(0.0, 0.0, 0.0, 0.5), filled=True)
105        renderer.draw_rect((x, y), (w, h), colour=(0.1, 0.1, 0.15, 0.95), filled=True)
106        renderer.draw_rect((x, y), (w, 2), colour=(1.0, 0.9, 0.3, 1.0), filled=True)
107        renderer.draw_text("LEVEL UP!", (cx - 40, y + 15), scale=1.8, colour=(1.0, 0.9, 0.3, 1.0))
108        renderer.draw_text(f"Points: {p.stat_points}", (cx - 30, y + 45), scale=1.0, colour=(0.8, 0.8, 0.8))
109
110        sy = y + 80
111        for i, (name, key) in enumerate(zip(self.STAT_NAMES, self.STAT_KEYS, strict=False)):
112            val = getattr(p, key, 0)
113            colour = (1.0, 0.9, 0.3, 1.0) if i == self._selected else (0.7, 0.7, 0.7, 1.0)
114            prefix = "> " if i == self._selected else "  "
115            renderer.draw_text(f"{prefix}{name}: {val}", (x + 20, sy + i * 30), scale=1.2, colour=colour)
116
117        renderer.draw_text("Space: Allocate   Esc: Close", (x + 20, y + h - 25), scale=0.8, colour=(0.5, 0.5, 0.5, 1.0))
118        draw_back_button(renderer)