Pause Menu¶
a full-screen modal overlay with stacked buttons, toggled by a key.
▶ Run in browserTags: ui menu pause modal anchors
A classic pause screen layered over the running game: pressing P (or ESC) drops
a dimmed full-rect scrim across the whole viewport and pops a centred panel with
Resume / Restart / Quit buttons stacked vertically. The scrim fills the screen
via AnchorPreset.FULL_RECT, the panel sits dead-centre via AnchorPreset.CENTER
with symmetric margins, and both stay correctly placed at any window size. The
background keeps a simple animated marker so it is obvious the overlay sits on
top of live content.
What it demonstrates¶
A modal overlay built from a
FULL_RECTscrim Panel plus aCENTER-anchored panel, both top-level Controls using anchors and margins, never absolute position.Toggling the whole overlay’s
visibleflag from a key press to show / hide.A vertical
VBoxContainerofButtons wired withButton.pressed.connect()(Resume hides the menu, Restart resets state, Quit closes the app).The centred panel staying centred and the scrim staying full-screen on resize.
Controls: P / ESC - Toggle the pause overlay Mouse - Click Resume / Restart / Quit
Source¶
1"""Pause Menu: a full-screen modal overlay with stacked buttons, toggled by a key.
2
3A classic pause screen layered over the running game: pressing P (or ESC) drops
4a dimmed full-rect scrim across the whole viewport and pops a centred panel with
5Resume / Restart / Quit buttons stacked vertically. The scrim fills the screen
6via `AnchorPreset.FULL_RECT`, the panel sits dead-centre via `AnchorPreset.CENTER`
7with symmetric margins, and both stay correctly placed at any window size. The
8background keeps a simple animated marker so it is obvious the overlay sits on
9top of live content.
10
11# /// simvx
12# tags = ["ui", "menu", "pause", "modal", "anchors"]
13# web = { root = "PauseMenuDemo", width = 800, height = 600, responsive = true }
14# ///
15
16## What it demonstrates
17
18- A modal overlay built from a `FULL_RECT` scrim Panel plus a `CENTER`-anchored
19 panel, both top-level Controls using anchors and margins, never absolute
20 position.
21- Toggling the whole overlay's `visible` flag from a key press to show / hide.
22- A vertical `VBoxContainer` of `Button`s wired with `Button.pressed.connect()`
23 (Resume hides the menu, Restart resets state, Quit closes the app).
24- The centred panel staying centred and the scrim staying full-screen on resize.
25
26Controls:
27 P / ESC - Toggle the pause overlay
28 Mouse - Click Resume / Restart / Quit
29"""
30
31from simvx.core import (
32 AnchorPreset,
33 Button,
34 Colour,
35 Control,
36 Input,
37 InputMap,
38 Key,
39 Label,
40 Node2D,
41 Panel,
42 VBoxContainer,
43 Vec2,
44)
45from simvx.graphics import App
46
47WIDTH, HEIGHT = 800, 600
48
49
50class PauseOverlay(Control):
51 """Full-screen modal: dimmed scrim + centred button panel.
52
53 Itself a FULL_RECT top-level Control so it covers the viewport; toggling
54 `self.visible` shows or hides the whole overlay at once.
55 """
56
57 def __init__(self, on_resume, on_restart, on_quit, **kwargs):
58 super().__init__(**kwargs)
59 self.set_anchor_preset(AnchorPreset.FULL_RECT)
60
61 # Dimmed scrim across the entire viewport (semi-transparent black).
62 scrim = Panel(name="Scrim")
63 scrim.set_anchor_preset(AnchorPreset.FULL_RECT)
64 scrim.bg_colour = Colour.rgba(0.0, 0.0, 0.0, 0.6)
65 self.add_child(scrim)
66
67 # Centred panel: CENTER anchor + symmetric margins make a fixed-size
68 # box that stays centred at any window size.
69 panel = Panel(name="MenuPanel")
70 panel.set_anchor_preset(AnchorPreset.CENTER)
71 panel.margin_left = -160
72 panel.margin_right = 160
73 panel.margin_top = -150
74 panel.margin_bottom = 150
75 panel.bg_colour = Colour.hex("#1A1A2E")
76 self.add_child(panel)
77
78 title = Label("Paused")
79 title.set_anchor_preset(AnchorPreset.TOP_WIDE)
80 title.margin_top = 16
81 title.size_y = 36
82 title.font_size = 24.0
83 title.text_colour = Colour.WHITE
84 title.alignment = "center"
85 panel.add_child(title)
86
87 # Stacked buttons in a vertical container.
88 buttons = VBoxContainer(name="Buttons")
89 buttons.set_anchor_preset(AnchorPreset.CENTER)
90 buttons.margin_left = -110
91 buttons.margin_right = 110
92 buttons.margin_top = -50
93 buttons.margin_bottom = 90
94 buttons.separation = 12.0
95 panel.add_child(buttons)
96
97 for label, handler in (("Resume", on_resume), ("Restart", on_restart), ("Quit", on_quit)):
98 btn = Button(label, name=f"{label}Button")
99 btn.size = Vec2(220, 36)
100 btn.pressed.connect(handler)
101 buttons.add_child(btn)
102
103
104class PauseMenuDemo(Node2D):
105 """Root: animated background content with a toggleable pause overlay on top."""
106
107 def on_ready(self):
108 InputMap.add_action("pause", [Key.P, Key.ESCAPE])
109
110 self._t = 0.0
111 self._marker = Vec2(WIDTH / 2, HEIGHT / 2)
112
113 hint = Label("Press P or ESC to pause")
114 hint.set_anchor_preset(AnchorPreset.CENTER_TOP)
115 hint.margin_left = -160
116 hint.margin_right = 160
117 hint.margin_top = 20
118 hint.font_size = 16.0
119 hint.text_colour = Colour.LIGHT_GRAY
120 hint.alignment = "center"
121 self.add_child(hint)
122
123 self._overlay = PauseOverlay(
124 self._resume, self._restart, self._quit, name="PauseOverlay",
125 )
126 self._overlay.visible = False # hidden until paused
127 self.add_child(self._overlay)
128
129 @property
130 def paused(self) -> bool:
131 return self._overlay.visible
132
133 def _resume(self):
134 self._overlay.visible = False
135
136 def _restart(self):
137 self._t = 0.0
138 self._marker = Vec2(WIDTH / 2, HEIGHT / 2)
139 self._overlay.visible = False
140
141 def _quit(self):
142 self.app.quit()
143
144 def on_update(self, dt: float):
145 if Input.is_action_just_pressed("pause"):
146 self._overlay.visible = not self._overlay.visible
147
148 # Background only advances while not paused, proving the overlay gates it.
149 if not self.paused:
150 self._t += dt
151
152 def on_draw(self, renderer):
153 # Simple live background: a marker orbiting the centre, so the dimmed
154 # overlay is visibly layered over moving content.
155 import math
156
157 cx, cy = WIDTH / 2, HEIGHT / 2
158 x = cx + math.cos(self._t * 1.5) * 160
159 y = cy + math.sin(self._t * 1.5) * 120
160 renderer.draw_rect((x - 18, y - 18), (36, 36), colour=Colour.hex("#4FC3F7"), filled=True)
161
162
163if __name__ == "__main__":
164 app = App(title="SimVX Pause Menu", width=WIDTH, height=HEIGHT)
165 app.run(PauseMenuDemo())