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 AnchorPreset, 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")
38        self.set_anchor_preset(AnchorPreset.FULL_RECT)
39        # Cards ride high z bands so a dragged card clears its pile; the HUD has
40        # to clear the cards.
41        self.z_index = 5000
42
43        self.title = self.add_child(Label("Klondike Solitaire", name="Title"))
44        self.title.set_anchor_preset(AnchorPreset.TOP_WIDE)
45        self.title.margin_top, self.title.margin_bottom = 4, 34
46        self.title.font_size = 22.0
47        self.title.alignment = "center"
48        self.title.text_colour = (1.0, 1.0, 1.0, 1.0)
49
50        self.win_text = self.add_child(Label("", name="WinBanner"))
51        self.win_text.set_anchor_preset(AnchorPreset.CENTER)
52        self.win_text.margin_left, self.win_text.margin_right = -300, 300
53        self.win_text.margin_top, self.win_text.margin_bottom = -40, 40
54        self.win_text.font_size = 44.0
55        self.win_text.alignment = "center"
56        self.win_text.text_colour = (1.0, 0.95, 0.4, 1.0)
57
58        self.strip = self.add_child(Panel(name="Strip"))
59        self.strip.place_bottom_strip(STRIP_HEIGHT)
60        self.strip.bg_colour = (0.78, 0.80, 0.83, 1.0)
61
62        # Scoreboard lives in the strip: the top band is card territory.
63        self.score_text = self.strip.add_child(Label("Score: 0    Moves: 0", name="Score"))
64        self.score_text.set_anchor_preset(AnchorPreset.CENTER_LEFT)
65        self.score_text.margin_left, self.score_text.margin_right = 20, 300
66        self.score_text.margin_top, self.score_text.margin_bottom = -14, 14
67        self.score_text.font_size = 16.0
68        self.score_text.text_colour = (0.18, 0.22, 0.28, 1.0)
69
70        row_w = len(BUTTONS) * BUTTON_W + (len(BUTTONS) - 1) * BUTTON_GAP
71        row = self.strip.add_child(HBoxContainer(name="Buttons"))
72        row.set_anchor_preset(AnchorPreset.CENTER)
73        row.margin_left, row.margin_right = -row_w * 0.5, row_w * 0.5
74        row.margin_top, row.margin_bottom = -BUTTON_H * 0.5, BUTTON_H * 0.5
75        row.separation = BUTTON_GAP
76
77        for label, action in BUTTONS:
78            button = Button(label, name=f"Btn{action.title()}")
79            button.size = Vec2(BUTTON_W, BUTTON_H)
80            button.font_size = 15.0
81            button.pressed.connect(lambda a=action: self.button_pressed(a))
82            row.add_child(button)
83
84    def set_state(self, score: int, moves: int, won: bool) -> None:
85        """Refresh the readout. Label text is a Property, so this redraws itself."""
86        self.score_text.text = f"Score: {score}    Moves: {moves}"
87        self.win_text.text = "YOU WIN!" if won else ""
88
89    def covers(self, point: Vec2) -> bool:
90        """Whether ``point`` (screen space) lands on the controls strip."""
91        return self.strip.is_point_inside(point)
92
93
94__all__ = ["Hud"]