nodes/title.pyΒΆ

Part of Procedural Planets.

 1"""TitleOverlay: the title card the port opens on, before the planet is interactive.
 2
 3The overlay is a plain anchored `Control` tree: a full-rect dimming `Panel`
 4holding a centred `VBoxContainer` with the title, a one-line blurb, the control
 5list and a start button. Anchors (not absolute positions) keep it centred when
 6the window resizes.
 7"""
 8
 9from __future__ import annotations
10
11from collections.abc import Callable, Sequence
12
13from simvx.core import Vec2
14from simvx.core.ui import AnchorPreset, Button, Control, Label, Panel, SizeFlags, VBoxContainer
15
16BLOCK_WIDTH = 640.0
17
18
19def _line(text: str, font_size: float, colour: tuple[float, float, float, float]) -> Label:
20    """Build a full-width, centred, single-line label of the given size."""
21    label = Label(text=text)
22    label.font_size = font_size
23    label.alignment = "center"
24    label.text_colour = colour
25    label.size = Vec2(BLOCK_WIDTH, font_size * 1.6)
26    return label
27
28
29class TitleOverlay(Panel):
30    """Dimmed title card listing the controls, dismissed by the start button.
31
32    `on_start` fires when the button is pressed. The owner is expected to
33    remove the overlay from the tree at that point; the overlay keeps no state
34    of its own.
35    """
36
37    def __init__(self, on_start: Callable[[], None], controls: Sequence[str] = (), **kwargs):
38        super().__init__(**kwargs)
39        self.set_anchor_preset(AnchorPreset.FULL_RECT)
40        self.bg_colour = (0.02, 0.03, 0.07, 0.86)
41
42        rows: list[Control] = [
43            _line("Procedural Planets", 40.0, (0.96, 0.97, 1.0, 1.0)),
44            _line("A quad-sphere world rebuilt from noise, live.", 18.0, (0.72, 0.78, 0.92, 1.0)),
45        ]
46        rows.extend(_line(text, 16.0, (0.62, 0.68, 0.82, 1.0)) for text in controls)
47
48        self.start_button = Button(text="Explore the planet", on_press=on_start)
49        self.start_button.font_size = 18.0
50        self.start_button.size = Vec2(260.0, 46.0)
51        # Labels fill the block width so their centred text lines up; the
52        # button keeps its own width and centres itself instead.
53        self.start_button.size_flags_horizontal = SizeFlags.SHRINK_CENTER
54        rows.append(self.start_button)
55
56        separation = 8.0
57        height = sum(row.size.y for row in rows) + separation * (len(rows) - 1)
58
59        self._vbox = VBoxContainer()
60        self._vbox.separation = separation
61        self._vbox.alignment = "center"
62        self._vbox.size = Vec2(BLOCK_WIDTH, height)
63        # CENTER anchors all four corners at the parent's midpoint, so the
64        # margins below spell out a BLOCK_WIDTH x height box around it.
65        self._vbox.set_anchor_preset(AnchorPreset.CENTER)
66        self._vbox.margin_left = -BLOCK_WIDTH / 2
67        self._vbox.margin_right = BLOCK_WIDTH / 2
68        self._vbox.margin_top = -height / 2
69        self._vbox.margin_bottom = height / 2
70        self.add_child(self._vbox)
71
72        for row in rows:
73            self._vbox.add_child(row)