mini-arcade-core 0.9.0__py3-none-any.whl → 0.9.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mini_arcade_core/__init__.py +3 -0
- mini_arcade_core/backend.py +18 -2
- mini_arcade_core/keymaps/__init__.py +0 -0
- mini_arcade_core/keymaps/sdl.py +98 -0
- mini_arcade_core/keys.py +160 -0
- mini_arcade_core/ui/menu.py +243 -7
- {mini_arcade_core-0.9.0.dist-info → mini_arcade_core-0.9.2.dist-info}/METADATA +1 -1
- {mini_arcade_core-0.9.0.dist-info → mini_arcade_core-0.9.2.dist-info}/RECORD +10 -7
- {mini_arcade_core-0.9.0.dist-info → mini_arcade_core-0.9.2.dist-info}/WHEEL +0 -0
- {mini_arcade_core-0.9.0.dist-info → mini_arcade_core-0.9.2.dist-info}/licenses/LICENSE +0 -0
mini_arcade_core/__init__.py
CHANGED
|
@@ -19,6 +19,7 @@ from .collision2d import RectCollider
|
|
|
19
19
|
from .entity import Entity, KinematicEntity, SpriteEntity
|
|
20
20
|
from .game import Game, GameConfig
|
|
21
21
|
from .geometry2d import Bounds2D, Position2D, Size2D
|
|
22
|
+
from .keys import Key, keymap
|
|
22
23
|
from .kinematics2d import KinematicData
|
|
23
24
|
from .physics2d import Velocity2D
|
|
24
25
|
from .scene import Scene
|
|
@@ -69,6 +70,8 @@ __all__ = [
|
|
|
69
70
|
"VerticalWrap",
|
|
70
71
|
"RectSprite",
|
|
71
72
|
"RectKinematic",
|
|
73
|
+
"Key",
|
|
74
|
+
"keymap",
|
|
72
75
|
]
|
|
73
76
|
|
|
74
77
|
PACKAGE_NAME = "mini-arcade-core" # or whatever is in your pyproject.toml
|
mini_arcade_core/backend.py
CHANGED
|
@@ -9,6 +9,8 @@ from dataclasses import dataclass
|
|
|
9
9
|
from enum import Enum, auto
|
|
10
10
|
from typing import Iterable, Optional, Protocol, Tuple, Union
|
|
11
11
|
|
|
12
|
+
from .keys import Key
|
|
13
|
+
|
|
12
14
|
Color = Union[Tuple[int, int, int], Tuple[int, int, int, int]]
|
|
13
15
|
|
|
14
16
|
|
|
@@ -57,7 +59,8 @@ class Event:
|
|
|
57
59
|
- key: integer key code (e.g. ESC = 27), or None if not applicable
|
|
58
60
|
|
|
59
61
|
:ivar type (EventType): The type of event.
|
|
60
|
-
:ivar key (
|
|
62
|
+
:ivar key (Key | None): The key associated with the event, if any.
|
|
63
|
+
:ivar key_code (int | None): The key code associated with the event, if any.
|
|
61
64
|
:ivar scancode (int | None): The hardware scancode of the key, if any.
|
|
62
65
|
:ivar mod (int | None): Modifier keys bitmask, if any.
|
|
63
66
|
:ivar repeat (bool | None): Whether this key event is a repeat, if any.
|
|
@@ -72,7 +75,8 @@ class Event:
|
|
|
72
75
|
"""
|
|
73
76
|
|
|
74
77
|
type: EventType
|
|
75
|
-
key:
|
|
78
|
+
key: Key | None = None # Use Key enum for better clarity
|
|
79
|
+
key_code: Optional[int] = None
|
|
76
80
|
|
|
77
81
|
# Keyboard extras (optional)
|
|
78
82
|
scancode: Optional[int] = None
|
|
@@ -210,6 +214,18 @@ class Backend(Protocol):
|
|
|
210
214
|
:type color: Color
|
|
211
215
|
"""
|
|
212
216
|
|
|
217
|
+
def measure_text(self, text: str) -> tuple[int, int]:
|
|
218
|
+
"""
|
|
219
|
+
Measure the width and height of the given text string in pixels.
|
|
220
|
+
|
|
221
|
+
:param text: The text string to measure.
|
|
222
|
+
:type text: str
|
|
223
|
+
|
|
224
|
+
:return: A tuple (width, height) in pixels.
|
|
225
|
+
:rtype: tuple[int, int]
|
|
226
|
+
"""
|
|
227
|
+
raise NotImplementedError
|
|
228
|
+
|
|
213
229
|
def capture_frame(self, path: str | None = None) -> bytes | None:
|
|
214
230
|
"""
|
|
215
231
|
Capture the current frame.
|
|
File without changes
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SDL keymap for mini arcade core.
|
|
3
|
+
Maps SDL keycodes to mini arcade core Key enums.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from mini_arcade_core.keys import Key
|
|
9
|
+
|
|
10
|
+
# SDL keycodes you need (minimal set)
|
|
11
|
+
SDLK_ESCAPE = 27
|
|
12
|
+
SDLK_RETURN = 13
|
|
13
|
+
SDLK_SPACE = 32
|
|
14
|
+
SDLK_TAB = 9
|
|
15
|
+
SDLK_BACKSPACE = 8
|
|
16
|
+
|
|
17
|
+
SDLK_UP = 1073741906
|
|
18
|
+
SDLK_DOWN = 1073741905
|
|
19
|
+
SDLK_LEFT = 1073741904
|
|
20
|
+
SDLK_RIGHT = 1073741903
|
|
21
|
+
|
|
22
|
+
F1 = 1073741882
|
|
23
|
+
F2 = 1073741883
|
|
24
|
+
F3 = 1073741884
|
|
25
|
+
F4 = 1073741885
|
|
26
|
+
F5 = 1073741886
|
|
27
|
+
F6 = 1073741887
|
|
28
|
+
F7 = 1073741888
|
|
29
|
+
F8 = 1073741889
|
|
30
|
+
F9 = 1073741890
|
|
31
|
+
F10 = 1073741891
|
|
32
|
+
F11 = 1073741892
|
|
33
|
+
F12 = 1073741893
|
|
34
|
+
|
|
35
|
+
SDL_KEYCODE_TO_KEY: dict[int, Key] = {
|
|
36
|
+
# Letters
|
|
37
|
+
ord("a"): Key.A,
|
|
38
|
+
ord("b"): Key.B,
|
|
39
|
+
ord("c"): Key.C,
|
|
40
|
+
ord("d"): Key.D,
|
|
41
|
+
ord("e"): Key.E,
|
|
42
|
+
ord("f"): Key.F,
|
|
43
|
+
ord("g"): Key.G,
|
|
44
|
+
ord("h"): Key.H,
|
|
45
|
+
ord("i"): Key.I,
|
|
46
|
+
ord("j"): Key.J,
|
|
47
|
+
ord("k"): Key.K,
|
|
48
|
+
ord("l"): Key.L,
|
|
49
|
+
ord("m"): Key.M,
|
|
50
|
+
ord("n"): Key.N,
|
|
51
|
+
ord("o"): Key.O,
|
|
52
|
+
ord("p"): Key.P,
|
|
53
|
+
ord("q"): Key.Q,
|
|
54
|
+
ord("r"): Key.R,
|
|
55
|
+
ord("s"): Key.S,
|
|
56
|
+
ord("t"): Key.T,
|
|
57
|
+
ord("u"): Key.U,
|
|
58
|
+
ord("v"): Key.V,
|
|
59
|
+
ord("w"): Key.W,
|
|
60
|
+
ord("x"): Key.X,
|
|
61
|
+
ord("y"): Key.Y,
|
|
62
|
+
ord("z"): Key.Z,
|
|
63
|
+
# Arrows
|
|
64
|
+
SDLK_UP: Key.UP,
|
|
65
|
+
SDLK_DOWN: Key.DOWN,
|
|
66
|
+
SDLK_LEFT: Key.LEFT,
|
|
67
|
+
SDLK_RIGHT: Key.RIGHT,
|
|
68
|
+
# Common
|
|
69
|
+
SDLK_ESCAPE: Key.ESCAPE,
|
|
70
|
+
SDLK_SPACE: Key.SPACE,
|
|
71
|
+
SDLK_RETURN: Key.ENTER,
|
|
72
|
+
SDLK_TAB: Key.TAB,
|
|
73
|
+
SDLK_BACKSPACE: Key.BACKSPACE,
|
|
74
|
+
# Numbers
|
|
75
|
+
ord("0"): Key.NUM_0,
|
|
76
|
+
ord("1"): Key.NUM_1,
|
|
77
|
+
ord("2"): Key.NUM_2,
|
|
78
|
+
ord("3"): Key.NUM_3,
|
|
79
|
+
ord("4"): Key.NUM_4,
|
|
80
|
+
ord("5"): Key.NUM_5,
|
|
81
|
+
ord("6"): Key.NUM_6,
|
|
82
|
+
ord("7"): Key.NUM_7,
|
|
83
|
+
ord("8"): Key.NUM_8,
|
|
84
|
+
ord("9"): Key.NUM_9,
|
|
85
|
+
# Function keys
|
|
86
|
+
F1: Key.F1,
|
|
87
|
+
F2: Key.F2,
|
|
88
|
+
F3: Key.F3,
|
|
89
|
+
F4: Key.F4,
|
|
90
|
+
F5: Key.F5,
|
|
91
|
+
F6: Key.F6,
|
|
92
|
+
F7: Key.F7,
|
|
93
|
+
F8: Key.F8,
|
|
94
|
+
F9: Key.F9,
|
|
95
|
+
F10: Key.F10,
|
|
96
|
+
F11: Key.F11,
|
|
97
|
+
F12: Key.F12,
|
|
98
|
+
}
|
mini_arcade_core/keys.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Mini Arcade Core key definitions.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from enum import Enum, auto
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Key(Enum):
|
|
12
|
+
"""
|
|
13
|
+
Enumeration of common keyboard keys.
|
|
14
|
+
|
|
15
|
+
:ivar A-Z: Letter keys.
|
|
16
|
+
:ivar arrow_up, arrow_down, arrow_left, arrow_right: Arrow keys.
|
|
17
|
+
:ivar escape, space, enter, tab, backspace: Common control keys.
|
|
18
|
+
:ivar num_0 - num_9: Number keys.
|
|
19
|
+
:ivar f1 - f12: Function keys.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
# Letters
|
|
23
|
+
A = auto()
|
|
24
|
+
B = auto()
|
|
25
|
+
C = auto()
|
|
26
|
+
D = auto()
|
|
27
|
+
E = auto()
|
|
28
|
+
F = auto()
|
|
29
|
+
G = auto()
|
|
30
|
+
H = auto()
|
|
31
|
+
I = auto()
|
|
32
|
+
J = auto()
|
|
33
|
+
K = auto()
|
|
34
|
+
L = auto()
|
|
35
|
+
M = auto()
|
|
36
|
+
N = auto()
|
|
37
|
+
O = auto()
|
|
38
|
+
P = auto()
|
|
39
|
+
Q = auto()
|
|
40
|
+
R = auto()
|
|
41
|
+
S = auto()
|
|
42
|
+
T = auto()
|
|
43
|
+
U = auto()
|
|
44
|
+
V = auto()
|
|
45
|
+
W = auto()
|
|
46
|
+
X = auto()
|
|
47
|
+
Y = auto()
|
|
48
|
+
Z = auto()
|
|
49
|
+
|
|
50
|
+
# Arrows
|
|
51
|
+
UP = auto()
|
|
52
|
+
DOWN = auto()
|
|
53
|
+
LEFT = auto()
|
|
54
|
+
RIGHT = auto()
|
|
55
|
+
|
|
56
|
+
# Common
|
|
57
|
+
ESCAPE = auto()
|
|
58
|
+
SPACE = auto()
|
|
59
|
+
ENTER = auto()
|
|
60
|
+
TAB = auto()
|
|
61
|
+
BACKSPACE = auto()
|
|
62
|
+
|
|
63
|
+
# Numbers
|
|
64
|
+
NUM_0 = auto()
|
|
65
|
+
NUM_1 = auto()
|
|
66
|
+
NUM_2 = auto()
|
|
67
|
+
NUM_3 = auto()
|
|
68
|
+
NUM_4 = auto()
|
|
69
|
+
NUM_5 = auto()
|
|
70
|
+
NUM_6 = auto()
|
|
71
|
+
NUM_7 = auto()
|
|
72
|
+
NUM_8 = auto()
|
|
73
|
+
NUM_9 = auto()
|
|
74
|
+
|
|
75
|
+
# Function keys
|
|
76
|
+
F1 = auto()
|
|
77
|
+
F2 = auto()
|
|
78
|
+
F3 = auto()
|
|
79
|
+
F4 = auto()
|
|
80
|
+
F5 = auto()
|
|
81
|
+
F6 = auto()
|
|
82
|
+
F7 = auto()
|
|
83
|
+
F8 = auto()
|
|
84
|
+
F9 = auto()
|
|
85
|
+
F10 = auto()
|
|
86
|
+
F11 = auto()
|
|
87
|
+
F12 = auto()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# Justification: Simple alias object for keys
|
|
91
|
+
# pylint: disable=too-many-instance-attributes
|
|
92
|
+
@dataclass(frozen=True)
|
|
93
|
+
class _Keys:
|
|
94
|
+
# alias object so user code can do keys.w, keys.arrow_up, etc.
|
|
95
|
+
a: Key = Key.A
|
|
96
|
+
b: Key = Key.B
|
|
97
|
+
c: Key = Key.C
|
|
98
|
+
d: Key = Key.D
|
|
99
|
+
e: Key = Key.E
|
|
100
|
+
f: Key = Key.F
|
|
101
|
+
g: Key = Key.G
|
|
102
|
+
h: Key = Key.H
|
|
103
|
+
i: Key = Key.I
|
|
104
|
+
j: Key = Key.J
|
|
105
|
+
k: Key = Key.K
|
|
106
|
+
l: Key = Key.L
|
|
107
|
+
m: Key = Key.M
|
|
108
|
+
n: Key = Key.N
|
|
109
|
+
o: Key = Key.O
|
|
110
|
+
p: Key = Key.P
|
|
111
|
+
q: Key = Key.Q
|
|
112
|
+
r: Key = Key.R
|
|
113
|
+
s: Key = Key.S
|
|
114
|
+
t: Key = Key.T
|
|
115
|
+
u: Key = Key.U
|
|
116
|
+
v: Key = Key.V
|
|
117
|
+
w: Key = Key.W
|
|
118
|
+
x: Key = Key.X
|
|
119
|
+
y: Key = Key.Y
|
|
120
|
+
z: Key = Key.Z
|
|
121
|
+
|
|
122
|
+
up: Key = Key.UP
|
|
123
|
+
down: Key = Key.DOWN
|
|
124
|
+
left: Key = Key.LEFT
|
|
125
|
+
right: Key = Key.RIGHT
|
|
126
|
+
|
|
127
|
+
escape: Key = Key.ESCAPE
|
|
128
|
+
space: Key = Key.SPACE
|
|
129
|
+
enter: Key = Key.ENTER
|
|
130
|
+
tab: Key = Key.TAB
|
|
131
|
+
backspace: Key = Key.BACKSPACE
|
|
132
|
+
|
|
133
|
+
num_0: Key = Key.NUM_0
|
|
134
|
+
num_1: Key = Key.NUM_1
|
|
135
|
+
num_2: Key = Key.NUM_2
|
|
136
|
+
num_3: Key = Key.NUM_3
|
|
137
|
+
num_4: Key = Key.NUM_4
|
|
138
|
+
num_5: Key = Key.NUM_5
|
|
139
|
+
num_6: Key = Key.NUM_6
|
|
140
|
+
num_7: Key = Key.NUM_7
|
|
141
|
+
num_8: Key = Key.NUM_8
|
|
142
|
+
num_9: Key = Key.NUM_9
|
|
143
|
+
|
|
144
|
+
f1: Key = Key.F1
|
|
145
|
+
f2: Key = Key.F2
|
|
146
|
+
f3: Key = Key.F3
|
|
147
|
+
f4: Key = Key.F4
|
|
148
|
+
f5: Key = Key.F5
|
|
149
|
+
f6: Key = Key.F6
|
|
150
|
+
f7: Key = Key.F7
|
|
151
|
+
f8: Key = Key.F8
|
|
152
|
+
f9: Key = Key.F9
|
|
153
|
+
f10: Key = Key.F10
|
|
154
|
+
f11: Key = Key.F11
|
|
155
|
+
f12: Key = Key.F12
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# pylint: enable=too-many-instance-attributes
|
|
159
|
+
|
|
160
|
+
keymap = _Keys()
|
mini_arcade_core/ui/menu.py
CHANGED
|
@@ -8,6 +8,7 @@ from dataclasses import dataclass
|
|
|
8
8
|
from typing import Callable, Sequence
|
|
9
9
|
|
|
10
10
|
from mini_arcade_core.backend import Backend, Color, Event, EventType
|
|
11
|
+
from mini_arcade_core.geometry2d import Size2D
|
|
11
12
|
|
|
12
13
|
MenuAction = Callable[[], None]
|
|
13
14
|
|
|
@@ -25,6 +26,9 @@ class MenuItem:
|
|
|
25
26
|
on_select: MenuAction
|
|
26
27
|
|
|
27
28
|
|
|
29
|
+
# Justification: Data container for styling options needs
|
|
30
|
+
# some attributes.
|
|
31
|
+
# pylint: disable=too-many-instance-attributes
|
|
28
32
|
@dataclass
|
|
29
33
|
class MenuStyle:
|
|
30
34
|
"""
|
|
@@ -37,7 +41,42 @@ class MenuStyle:
|
|
|
37
41
|
|
|
38
42
|
normal: Color = (220, 220, 220)
|
|
39
43
|
selected: Color = (255, 255, 0)
|
|
44
|
+
|
|
45
|
+
# Layout
|
|
40
46
|
line_height: int = 28
|
|
47
|
+
title_color: Color = (255, 255, 255)
|
|
48
|
+
title_spacing: int = 18
|
|
49
|
+
|
|
50
|
+
# Scene background (solid)
|
|
51
|
+
background_color: Color | None = None # e.g. BACKGROUND
|
|
52
|
+
|
|
53
|
+
# Optional full-screen overlay (dim)
|
|
54
|
+
overlay_color: Color | None = None # e.g. (0,0,0,0.5) for pause
|
|
55
|
+
|
|
56
|
+
# Panel behind content (optional)
|
|
57
|
+
panel_color: Color | None = None
|
|
58
|
+
panel_padding_x: int = 24
|
|
59
|
+
panel_padding_y: int = 18
|
|
60
|
+
|
|
61
|
+
# Button rendering (optional)
|
|
62
|
+
button_enabled: bool = False
|
|
63
|
+
button_fill: Color = (30, 30, 30, 1.0)
|
|
64
|
+
button_border: Color = (120, 120, 120, 1.0)
|
|
65
|
+
button_selected_border: Color = (255, 255, 0, 1.0)
|
|
66
|
+
button_width: int | None = (
|
|
67
|
+
None # if None -> auto-fit to longest label + padding
|
|
68
|
+
)
|
|
69
|
+
button_height: int = 40
|
|
70
|
+
button_gap: int = 20
|
|
71
|
+
button_padding_x: int = 20 # used for auto-fit + text centering
|
|
72
|
+
|
|
73
|
+
# Hint footer (optional)
|
|
74
|
+
hint: str | None = None
|
|
75
|
+
hint_color: Color = (200, 200, 200)
|
|
76
|
+
hint_margin_bottom: int = 50
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# pylint: enable=too-many-instance-attributes
|
|
41
80
|
|
|
42
81
|
|
|
43
82
|
class Menu:
|
|
@@ -47,8 +86,8 @@ class Menu:
|
|
|
47
86
|
self,
|
|
48
87
|
items: Sequence[MenuItem],
|
|
49
88
|
*,
|
|
50
|
-
|
|
51
|
-
|
|
89
|
+
viewport: Size2D | None = None,
|
|
90
|
+
title: str | None = None,
|
|
52
91
|
style: MenuStyle | None = None,
|
|
53
92
|
):
|
|
54
93
|
"""
|
|
@@ -62,8 +101,8 @@ class Menu:
|
|
|
62
101
|
:type style: MenuStyle | None
|
|
63
102
|
"""
|
|
64
103
|
self.items = list(items)
|
|
65
|
-
self.
|
|
66
|
-
self.
|
|
104
|
+
self.viewport = viewport
|
|
105
|
+
self.title = title
|
|
67
106
|
self.style = style or MenuStyle()
|
|
68
107
|
self.selected_index = 0
|
|
69
108
|
|
|
@@ -114,6 +153,36 @@ class Menu:
|
|
|
114
153
|
elif event.key == select_key:
|
|
115
154
|
self.select()
|
|
116
155
|
|
|
156
|
+
def _measure_content(self, surface: Backend) -> tuple[int, int, int]:
|
|
157
|
+
"""
|
|
158
|
+
Returns (content_width, content_height, title_height)
|
|
159
|
+
where content is items-only (no padding).
|
|
160
|
+
"""
|
|
161
|
+
if not self.items and not self.title:
|
|
162
|
+
return 0, 0, 0
|
|
163
|
+
|
|
164
|
+
max_w = 0
|
|
165
|
+
title_h = 0
|
|
166
|
+
|
|
167
|
+
if self.title:
|
|
168
|
+
tw, th = surface.measure_text(self.title)
|
|
169
|
+
max_w = max(max_w, tw)
|
|
170
|
+
title_h = th
|
|
171
|
+
|
|
172
|
+
# Items
|
|
173
|
+
for it in self.items:
|
|
174
|
+
w, _ = surface.measure_text(it.label)
|
|
175
|
+
max_w = max(max_w, w)
|
|
176
|
+
|
|
177
|
+
items_h = len(self.items) * self.style.line_height
|
|
178
|
+
|
|
179
|
+
# Total content height includes title block if present
|
|
180
|
+
content_h = items_h
|
|
181
|
+
if self.title:
|
|
182
|
+
content_h += title_h + self.style.title_spacing
|
|
183
|
+
|
|
184
|
+
return max_w, content_h, title_h
|
|
185
|
+
|
|
117
186
|
def draw(self, surface: Backend):
|
|
118
187
|
"""
|
|
119
188
|
Draw the menu onto the given backend surface.
|
|
@@ -121,15 +190,182 @@ class Menu:
|
|
|
121
190
|
:param surface: The backend surface to draw on.
|
|
122
191
|
:type surface: Backend
|
|
123
192
|
"""
|
|
193
|
+
if self.viewport is None:
|
|
194
|
+
raise ValueError(
|
|
195
|
+
"Menu requires viewport=Size2D for centering/layout"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
vw, vh = self.viewport.width, self.viewport.height
|
|
199
|
+
|
|
200
|
+
# 0) Solid background (for main menus)
|
|
201
|
+
if self.style.background_color is not None:
|
|
202
|
+
surface.draw_rect(0, 0, vw, vh, color=self.style.background_color)
|
|
203
|
+
|
|
204
|
+
# 1) Overlay (for pause, etc.)
|
|
205
|
+
if self.style.overlay_color is not None:
|
|
206
|
+
surface.draw_rect(0, 0, vw, vh, color=self.style.overlay_color)
|
|
207
|
+
|
|
208
|
+
# 2) Compute menu content bounds (panel area)
|
|
209
|
+
content_w, content_h, title_h = self._measure_content(surface)
|
|
210
|
+
|
|
211
|
+
pad_x, pad_y = self.style.panel_padding_x, self.style.panel_padding_y
|
|
212
|
+
panel_w = content_w + pad_x * 2
|
|
213
|
+
panel_h = content_h + pad_y * 2
|
|
214
|
+
|
|
215
|
+
x0 = (vw - panel_w) // 2
|
|
216
|
+
y0 = (vh - panel_h) // 2
|
|
217
|
+
|
|
218
|
+
# Optional vertical offset if you add it later:
|
|
219
|
+
# y0 += self.style.center_offset_y
|
|
220
|
+
|
|
221
|
+
# 3) Panel (optional)
|
|
222
|
+
if self.style.panel_color is not None:
|
|
223
|
+
surface.draw_rect(
|
|
224
|
+
x0, y0, panel_w, panel_h, color=self.style.panel_color
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# 4) Draw title + items
|
|
228
|
+
cursor_y = y0 + pad_y
|
|
229
|
+
x_center = x0 + (panel_w // 2)
|
|
230
|
+
|
|
231
|
+
if self.title:
|
|
232
|
+
self._draw_text_center_x(
|
|
233
|
+
surface,
|
|
234
|
+
x_center,
|
|
235
|
+
cursor_y,
|
|
236
|
+
self.title,
|
|
237
|
+
color=self.style.title_color,
|
|
238
|
+
)
|
|
239
|
+
cursor_y += title_h + self.style.title_spacing
|
|
240
|
+
|
|
241
|
+
if self.style.button_enabled:
|
|
242
|
+
self._draw_buttons(surface, x_center, cursor_y)
|
|
243
|
+
else:
|
|
244
|
+
self._draw_text_items(surface, x_center, cursor_y)
|
|
245
|
+
|
|
246
|
+
# 5) Hint footer (optional)
|
|
247
|
+
if self.style.hint:
|
|
248
|
+
self._draw_text_center_x(
|
|
249
|
+
surface,
|
|
250
|
+
vw // 2,
|
|
251
|
+
vh - self.style.hint_margin_bottom,
|
|
252
|
+
self.style.hint,
|
|
253
|
+
color=self.style.hint_color,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
def _draw_text_items(
|
|
257
|
+
self, surface: Backend, x_center: int, cursor_y: int
|
|
258
|
+
) -> None:
|
|
124
259
|
for i, item in enumerate(self.items):
|
|
125
260
|
color = (
|
|
126
261
|
self.style.selected
|
|
127
262
|
if i == self.selected_index
|
|
128
263
|
else self.style.normal
|
|
129
264
|
)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
265
|
+
self._draw_text_center_x(
|
|
266
|
+
surface,
|
|
267
|
+
x_center,
|
|
268
|
+
cursor_y + i * self.style.line_height,
|
|
133
269
|
item.label,
|
|
134
270
|
color=color,
|
|
135
271
|
)
|
|
272
|
+
|
|
273
|
+
# Justification: Local variables for layout calculations
|
|
274
|
+
# pylint: disable=too-many-locals
|
|
275
|
+
def _draw_buttons(
|
|
276
|
+
self, surface: Backend, x_center: int, cursor_y: int
|
|
277
|
+
) -> None:
|
|
278
|
+
# Determine button width: fixed or auto-fit
|
|
279
|
+
if self.style.button_width is not None:
|
|
280
|
+
bw = self.style.button_width
|
|
281
|
+
else:
|
|
282
|
+
max_label_w = 0
|
|
283
|
+
for it in self.items:
|
|
284
|
+
w, _ = surface.measure_text(it.label)
|
|
285
|
+
max_label_w = max(max_label_w, w)
|
|
286
|
+
bw = max_label_w + self.style.button_padding_x * 2
|
|
287
|
+
|
|
288
|
+
bh = self.style.button_height
|
|
289
|
+
gap = self.style.button_gap
|
|
290
|
+
|
|
291
|
+
# We treat cursor_y as “top of first button”
|
|
292
|
+
for i, item in enumerate(self.items):
|
|
293
|
+
y = cursor_y + i * (bh + gap)
|
|
294
|
+
x = x_center - bw // 2
|
|
295
|
+
|
|
296
|
+
selected = i == self.selected_index
|
|
297
|
+
border = (
|
|
298
|
+
self.style.button_selected_border
|
|
299
|
+
if selected
|
|
300
|
+
else self.style.button_border
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# Border rect
|
|
304
|
+
surface.draw_rect(x - 4, y - 4, bw + 8, bh + 8, color=border)
|
|
305
|
+
# Fill rect
|
|
306
|
+
surface.draw_rect(x, y, bw, bh, color=self.style.button_fill)
|
|
307
|
+
|
|
308
|
+
# Label color
|
|
309
|
+
text_color = self.style.selected if selected else self.style.normal
|
|
310
|
+
tw, th = surface.measure_text(item.label)
|
|
311
|
+
tx = x + (bw - tw) // 2
|
|
312
|
+
ty = y + (bh - th) // 2
|
|
313
|
+
surface.draw_text(tx, ty, item.label, color=text_color)
|
|
314
|
+
|
|
315
|
+
# pylint: enable=too-many-locals
|
|
316
|
+
|
|
317
|
+
def _measure_content(self, surface: Backend) -> tuple[int, int, int]:
|
|
318
|
+
# If button mode: content height differs (button_height + gaps)
|
|
319
|
+
max_w = 0
|
|
320
|
+
title_h = 0
|
|
321
|
+
|
|
322
|
+
if self.title:
|
|
323
|
+
tw, th = surface.measure_text(self.title)
|
|
324
|
+
max_w = max(max_w, tw)
|
|
325
|
+
title_h = th
|
|
326
|
+
|
|
327
|
+
if not self.items:
|
|
328
|
+
content_h = 0
|
|
329
|
+
if self.title:
|
|
330
|
+
content_h = title_h
|
|
331
|
+
return max_w, content_h, title_h
|
|
332
|
+
|
|
333
|
+
if self.style.button_enabled:
|
|
334
|
+
# width: either fixed or auto-fit
|
|
335
|
+
if self.style.button_width is not None:
|
|
336
|
+
items_w = self.style.button_width
|
|
337
|
+
else:
|
|
338
|
+
max_label_w = 0
|
|
339
|
+
for it in self.items:
|
|
340
|
+
w, _ = surface.measure_text(it.label)
|
|
341
|
+
max_label_w = max(max_label_w, w)
|
|
342
|
+
items_w = max_label_w + self.style.button_padding_x * 2
|
|
343
|
+
|
|
344
|
+
max_w = max(max_w, items_w)
|
|
345
|
+
|
|
346
|
+
bh = self.style.button_height
|
|
347
|
+
gap = self.style.button_gap
|
|
348
|
+
items_h = len(self.items) * bh + (len(self.items) - 1) * gap
|
|
349
|
+
else:
|
|
350
|
+
for it in self.items:
|
|
351
|
+
w, _ = surface.measure_text(it.label)
|
|
352
|
+
max_w = max(max_w, w)
|
|
353
|
+
items_h = len(self.items) * self.style.line_height
|
|
354
|
+
|
|
355
|
+
content_h = items_h
|
|
356
|
+
if self.title:
|
|
357
|
+
content_h += title_h + self.style.title_spacing
|
|
358
|
+
|
|
359
|
+
return max_w, content_h, title_h
|
|
360
|
+
|
|
361
|
+
@staticmethod
|
|
362
|
+
def _draw_text_center_x(
|
|
363
|
+
surface: Backend,
|
|
364
|
+
x_center: int,
|
|
365
|
+
y: int,
|
|
366
|
+
text: str,
|
|
367
|
+
*,
|
|
368
|
+
color: Color,
|
|
369
|
+
) -> None:
|
|
370
|
+
w, _ = surface.measure_text(text)
|
|
371
|
+
surface.draw_text(x_center - (w // 2), y, text, color=color)
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
mini_arcade_core/__init__.py,sha256=
|
|
2
|
-
mini_arcade_core/backend.py,sha256=
|
|
1
|
+
mini_arcade_core/__init__.py,sha256=7Ioji-MpfSQq4Ok3agVzP4mvcTNiThrY-DTDmVYFKo0,2609
|
|
2
|
+
mini_arcade_core/backend.py,sha256=kB666RiMSpFaxVEi0KIOPGjh4cdCPWDs5vz8MdY37G4,6854
|
|
3
3
|
mini_arcade_core/boundaries2d.py,sha256=H1HkCR1422MkQIEve2DFKvnav4RpvtLx-qTMxzmdDMQ,2610
|
|
4
4
|
mini_arcade_core/collision2d.py,sha256=iq800wsoYQNft3SA-W4jeY1wXhbpz2E7IkIorZBI238,1718
|
|
5
5
|
mini_arcade_core/entity.py,sha256=1AuE-_iMJYHxSyG783fsyByKK8Gx4vWWKMCEPjtgHXw,1776
|
|
6
6
|
mini_arcade_core/game.py,sha256=2DaFRdu7ogrwukh2MRnTh0Hy2r1XcaGSHEpLBNxfqIU,7273
|
|
7
7
|
mini_arcade_core/geometry2d.py,sha256=js791mMpsE_YbEoqtTOsOxNSflvKgSk6VeVKhj9N318,1282
|
|
8
|
+
mini_arcade_core/keymaps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
mini_arcade_core/keymaps/sdl.py,sha256=lRBvgTfdT7enN0qV7S2ZoLsPAWpaiSHzsyVGDlyTzQA,1924
|
|
10
|
+
mini_arcade_core/keys.py,sha256=LTg20SwLBI3kpPIiTNpq2yBft_QUGj-iNFSNm9M-Fus,3010
|
|
8
11
|
mini_arcade_core/kinematics2d.py,sha256=twBx-1jEwdknIJEyYBUg4VZyAvw1d7pGHaGFd36TPbo,2085
|
|
9
12
|
mini_arcade_core/physics2d.py,sha256=qIq86qWVuvcnLIMEPH6xx7XQO4tQhfiMZY6FseuVOR8,1636
|
|
10
13
|
mini_arcade_core/scene.py,sha256=e0E81aHt6saYiGfA-1V2II1yWwcZfvqGTRAv8pZnQtY,3865
|
|
11
14
|
mini_arcade_core/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
mini_arcade_core/ui/menu.py,sha256=
|
|
13
|
-
mini_arcade_core-0.9.
|
|
14
|
-
mini_arcade_core-0.9.
|
|
15
|
-
mini_arcade_core-0.9.
|
|
16
|
-
mini_arcade_core-0.9.
|
|
15
|
+
mini_arcade_core/ui/menu.py,sha256=79krwtWkGXKLSxG37PZsIIeFLayx9OTDN6esJepthlE,11343
|
|
16
|
+
mini_arcade_core-0.9.2.dist-info/METADATA,sha256=6NW5an292zZXvaDVoLUKayukctCRzizdbHqM0a9vZ5g,8188
|
|
17
|
+
mini_arcade_core-0.9.2.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
18
|
+
mini_arcade_core-0.9.2.dist-info/licenses/LICENSE,sha256=3lHAuV0584cVS5vAqi2uC6GcsVgxUijvwvtZckyvaZ4,1096
|
|
19
|
+
mini_arcade_core-0.9.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|