nodes/hud.pyΒΆ

Part of Klondike Solitaire.

 1"""Bottom controls strip, scoreboard, and win banner.
 2
 3Built from ``simvx.core.ui`` widgets: a full-rect ``Control`` holding an
 4anchored ``Panel`` strip along the bottom edge, an ``HBoxContainer`` of
 5``Button`` widgets centred inside it, and ``Label`` widgets for the title,
 6the score/move readout, and the win banner. Anchors do the layout, so the
 7whole HUD tracks a window resize without any per-frame arithmetic.
 8"""
 9
10from __future__ import annotations
11
12from simvx.core import Signal
13from simvx.core.math.types import Vec2
14from simvx.core.ui import Anchor, Button, Control, HBoxContainer, Label, Panel
15
16# Strip metrics
17STRIP_HEIGHT = 70
18BUTTON_W = 130
19BUTTON_H = 44
20BUTTON_GAP = 18
21
22BUTTONS = [
23    ("New game (N)", "new_game"),
24    ("Undo (U)", "undo"),
25    ("Save (S)", "save"),
26    ("Load (L)", "load"),
27    ("Quit", "quit"),
28]
29
30
31class Hud(Control):
32    """Bottom-of-window controls strip + scoreboard + win banner."""
33
34    button_pressed = Signal()  # (action: str)
35
36    def __init__(self) -> None:
37        super().__init__(name="Hud", anchor=Anchor.FULL_RECT)
38        # Cards ride high z bands so a dragged card clears its pile; the HUD has
39        # to clear the cards.
40        self.z_index = 5000
41
42        self.title = self.add_child(
43            Label("Klondike Solitaire", name="Title", anchor=Anchor.TOP_WIDE, size=(0, 30), inset=(0, 4, 0, 0))
44        )
45        self.title.font_size = 22.0
46        self.title.alignment = "centre"
47        self.title.text_colour = (1.0, 1.0, 1.0, 1.0)
48
49        self.win_text = self.add_child(Label("", name="WinBanner", anchor=Anchor.CENTER, size=(600, 80)))
50        self.win_text.font_size = 44.0
51        self.win_text.alignment = "centre"
52        self.win_text.text_colour = (1.0, 0.95, 0.4, 1.0)
53
54        self.strip = self.add_child(Panel(name="Strip", anchor=Anchor.BOTTOM_WIDE, size=(0, STRIP_HEIGHT)))
55        self.strip.bg_colour = (0.78, 0.80, 0.83, 1.0)
56
57        # Scoreboard lives in the strip: the top band is card territory.
58        self.score_text = self.strip.add_child(
59            Label("Score: 0    Moves: 0", name="Score", anchor=Anchor.CENTER_LEFT, size=(280, 28), inset=(20, 0))
60        )
61        self.score_text.font_size = 16.0
62        self.score_text.text_colour = (0.18, 0.22, 0.28, 1.0)
63
64        row_w = len(BUTTONS) * BUTTON_W + (len(BUTTONS) - 1) * BUTTON_GAP
65        row = self.strip.add_child(HBoxContainer(name="Buttons", anchor=Anchor.CENTER, size=(row_w, BUTTON_H)))
66        row.separation = BUTTON_GAP
67
68        for label, action in BUTTONS:
69            button = Button(label, name=f"Btn{action.title()}")
70            button.size = Vec2(BUTTON_W, BUTTON_H)
71            button.font_size = 15.0
72            button.pressed.connect(lambda a=action: self.button_pressed.emit(a))
73            row.add_child(button)
74
75    def set_state(self, score: int, moves: int, won: bool) -> None:
76        """Refresh the readout. Label text is a Property, so this redraws itself."""
77        self.score_text.text = f"Score: {score}    Moves: {moves}"
78        self.win_text.text = "YOU WIN!" if won else ""
79
80    def covers(self, point: Vec2) -> bool:
81        """Whether ``point`` (screen space) lands on the controls strip."""
82        return self.strip.is_point_inside(point)
83
84
85__all__ = ["Hud"]