nodes/menu.pyΒΆ
Part of Q1K3.
1"""Title screen: the port's entry point and its pause screen.
2
3The game grabs the pointer for mouse-look, so it must never do that behind the
4player's back. The title screen owns that decision: it lists the controls,
5offers a keyboard route and a pointer-only route, and captures the pointer only
6once the player has picked one. Escape during play brings it back, which is
7also how the pointer is released.
8"""
9
10from __future__ import annotations
11
12from typing import TYPE_CHECKING
13
14from simvx.core import (
15 AnchorPreset,
16 Button,
17 Control,
18 Label,
19 Panel,
20 VBoxContainer,
21)
22
23if TYPE_CHECKING: # pragma: no cover
24 from .root import Q1K3Root
25
26CONTROLS = [
27 ("MOVE", "W A S D or ARROWS"),
28 ("LOOK", "MOUSE"),
29 ("FIRE", "LEFT MOUSE"),
30 ("JUMP", "SPACE or RIGHT MOUSE"),
31 ("WEAPON", "Q / E or MOUSEWHEEL"),
32 ("MENU", "ESC"),
33]
34
35
36class TitleScreen(Control):
37 """Full-rect menu shown at boot and whenever Escape leaves the game.
38
39 Purely button-driven: UI events are routed even while the tree is paused,
40 which is what lets this screen un-pause the game it is covering.
41 """
42
43 def __init__(self, game: Q1K3Root) -> None:
44 super().__init__()
45 self.game = game
46 self.set_anchor_preset(AnchorPreset.FULL_RECT)
47
48 backdrop = Panel()
49 backdrop.set_anchor_preset(AnchorPreset.FULL_RECT)
50 backdrop.bg_colour = (0.02, 0.02, 0.03, 0.85)
51 self.add_child(backdrop)
52
53 title = Label(text="Q1K3", font_size=72.0)
54 title.set_anchor_preset(AnchorPreset.TOP_WIDE)
55 title.alignment = "center"
56 title.margin_top = 48
57 title.margin_bottom = -136
58 self.add_child(title)
59
60 tagline = Label(text="A SIMVX PORT OF THE 13 KB QUAKE", font_size=20.0)
61 tagline.set_anchor_preset(AnchorPreset.TOP_WIDE)
62 tagline.alignment = "center"
63 tagline.margin_top = 136
64 tagline.margin_bottom = -168
65 self.add_child(tagline)
66
67 rows = VBoxContainer()
68 rows.alignment = "center"
69 rows.separation = 6.0
70 rows.set_anchor_preset(AnchorPreset.CENTER)
71 rows.margin_left = -260
72 rows.margin_right = 260
73 rows.margin_top = -120
74 rows.margin_bottom = 60
75 # Every row is padded to the same width, so centring a monospaced font
76 # lines the two columns up without a grid container.
77 for action, keys in CONTROLS:
78 row = Label(text=f"{action:>6} {keys:<24}", font_size=18.0)
79 row.alignment = "center"
80 rows.add_child(row)
81 self.add_child(rows)
82
83 buttons = VBoxContainer()
84 buttons.alignment = "center"
85 buttons.separation = 12.0
86 buttons.set_anchor_preset(AnchorPreset.CENTER_BOTTOM)
87 buttons.margin_left = -220
88 buttons.margin_right = 220
89 buttons.margin_top = -180
90 buttons.margin_bottom = -48
91 play = Button(text="PLAY (mouse + keyboard)")
92 play.pressed.connect(lambda: self.game.start_game(pointer_only=False))
93 pointer = Button(text="TOUCH / MOUSE ONLY (on-screen controls)")
94 pointer.pressed.connect(lambda: self.game.start_game(pointer_only=True))
95 quit_button = Button(text="QUIT")
96 quit_button.pressed.connect(self.game.quit_game)
97 for button in (play, pointer, quit_button):
98 buttons.add_child(button)
99 self.add_child(buttons)