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)