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