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"]