mini-arcade-core 0.10.0__py3-none-any.whl → 1.0.1__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 +51 -63
- mini_arcade_core/backend/__init__.py +2 -6
- mini_arcade_core/backend/backend.py +148 -8
- mini_arcade_core/backend/events.py +1 -1
- mini_arcade_core/{keymaps/sdl.py → backend/sdl_map.py} +1 -1
- mini_arcade_core/engine/__init__.py +0 -0
- mini_arcade_core/engine/commands.py +169 -0
- mini_arcade_core/engine/game.py +369 -0
- mini_arcade_core/engine/render/__init__.py +0 -0
- mini_arcade_core/engine/render/packet.py +56 -0
- mini_arcade_core/engine/render/pipeline.py +63 -0
- mini_arcade_core/engine/render/viewport.py +203 -0
- mini_arcade_core/managers/__init__.py +0 -22
- mini_arcade_core/managers/cheats.py +71 -240
- mini_arcade_core/managers/inputs.py +5 -3
- mini_arcade_core/runtime/__init__.py +0 -0
- mini_arcade_core/runtime/audio/__init__.py +0 -0
- mini_arcade_core/runtime/audio/audio_adapter.py +20 -0
- mini_arcade_core/runtime/audio/audio_port.py +36 -0
- mini_arcade_core/runtime/capture/__init__.py +0 -0
- mini_arcade_core/runtime/capture/capture_adapter.py +143 -0
- mini_arcade_core/runtime/capture/capture_port.py +51 -0
- mini_arcade_core/runtime/context.py +53 -0
- mini_arcade_core/runtime/file/__init__.py +0 -0
- mini_arcade_core/runtime/file/file_adapter.py +20 -0
- mini_arcade_core/runtime/file/file_port.py +31 -0
- mini_arcade_core/runtime/input/__init__.py +0 -0
- mini_arcade_core/runtime/input/input_adapter.py +49 -0
- mini_arcade_core/runtime/input/input_port.py +31 -0
- mini_arcade_core/runtime/input_frame.py +71 -0
- mini_arcade_core/runtime/scene/__init__.py +0 -0
- mini_arcade_core/runtime/scene/scene_adapter.py +97 -0
- mini_arcade_core/runtime/scene/scene_port.py +149 -0
- mini_arcade_core/runtime/services.py +35 -0
- mini_arcade_core/runtime/window/__init__.py +0 -0
- mini_arcade_core/runtime/window/window_adapter.py +90 -0
- mini_arcade_core/runtime/window/window_port.py +109 -0
- mini_arcade_core/scenes/__init__.py +0 -22
- mini_arcade_core/scenes/autoreg.py +1 -1
- mini_arcade_core/scenes/registry.py +21 -19
- mini_arcade_core/scenes/sim_scene.py +41 -0
- mini_arcade_core/scenes/systems/__init__.py +0 -0
- mini_arcade_core/scenes/systems/base_system.py +40 -0
- mini_arcade_core/scenes/systems/system_pipeline.py +57 -0
- mini_arcade_core/sim/__init__.py +0 -0
- mini_arcade_core/sim/protocols.py +41 -0
- mini_arcade_core/sim/runner.py +222 -0
- mini_arcade_core/spaces/__init__.py +0 -12
- mini_arcade_core/spaces/d2/__init__.py +0 -30
- mini_arcade_core/spaces/d2/boundaries2d.py +10 -1
- mini_arcade_core/spaces/d2/collision2d.py +25 -28
- mini_arcade_core/spaces/d2/geometry2d.py +18 -0
- mini_arcade_core/spaces/d2/kinematics2d.py +2 -8
- mini_arcade_core/spaces/d2/physics2d.py +9 -0
- mini_arcade_core/ui/__init__.py +0 -26
- mini_arcade_core/ui/menu.py +271 -85
- mini_arcade_core/utils/__init__.py +10 -0
- mini_arcade_core/utils/deprecated_decorator.py +45 -0
- mini_arcade_core/utils/logging.py +168 -0
- {mini_arcade_core-0.10.0.dist-info → mini_arcade_core-1.0.1.dist-info}/METADATA +1 -1
- mini_arcade_core-1.0.1.dist-info/RECORD +66 -0
- {mini_arcade_core-0.10.0.dist-info → mini_arcade_core-1.0.1.dist-info}/WHEEL +1 -1
- mini_arcade_core/commands.py +0 -84
- mini_arcade_core/entity.py +0 -72
- mini_arcade_core/game.py +0 -287
- mini_arcade_core/keymaps/__init__.py +0 -15
- mini_arcade_core/managers/base.py +0 -132
- mini_arcade_core/managers/entities.py +0 -38
- mini_arcade_core/managers/overlays.py +0 -53
- mini_arcade_core/managers/system.py +0 -26
- mini_arcade_core/scenes/model.py +0 -34
- mini_arcade_core/scenes/runtime.py +0 -29
- mini_arcade_core/scenes/scene.py +0 -109
- mini_arcade_core/scenes/system.py +0 -69
- mini_arcade_core/ui/overlays.py +0 -41
- mini_arcade_core-0.10.0.dist-info/RECORD +0 -40
- /mini_arcade_core/{keymaps → backend}/keys.py +0 -0
- {mini_arcade_core-0.10.0.dist-info → mini_arcade_core-1.0.1.dist-info}/licenses/LICENSE +0 -0
mini_arcade_core/ui/menu.py
CHANGED
|
@@ -7,12 +7,17 @@ from __future__ import annotations
|
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from typing import Callable, Optional, Sequence
|
|
9
9
|
|
|
10
|
-
from mini_arcade_core.backend import Backend
|
|
11
|
-
from mini_arcade_core.
|
|
12
|
-
from mini_arcade_core.
|
|
13
|
-
from mini_arcade_core.
|
|
14
|
-
from mini_arcade_core.
|
|
15
|
-
from mini_arcade_core.
|
|
10
|
+
from mini_arcade_core.backend import Backend
|
|
11
|
+
from mini_arcade_core.backend.events import Event, EventType
|
|
12
|
+
from mini_arcade_core.backend.keys import Key
|
|
13
|
+
from mini_arcade_core.backend.types import Color
|
|
14
|
+
from mini_arcade_core.engine.commands import Command, CommandQueue, QuitCommand
|
|
15
|
+
from mini_arcade_core.engine.render.packet import RenderPacket
|
|
16
|
+
from mini_arcade_core.runtime.context import RuntimeContext
|
|
17
|
+
from mini_arcade_core.runtime.input_frame import InputFrame
|
|
18
|
+
from mini_arcade_core.scenes.systems.system_pipeline import SystemPipeline
|
|
19
|
+
from mini_arcade_core.sim.protocols import SimScene
|
|
20
|
+
from mini_arcade_core.spaces.d2.geometry2d import Size2D
|
|
16
21
|
|
|
17
22
|
|
|
18
23
|
@dataclass(frozen=True)
|
|
@@ -26,20 +31,20 @@ class MenuItem:
|
|
|
26
31
|
|
|
27
32
|
id: str
|
|
28
33
|
label: str
|
|
29
|
-
|
|
30
|
-
label_fn: Optional[Callable[[
|
|
34
|
+
command_factory: Callable[[], Command]
|
|
35
|
+
label_fn: Optional[Callable[[object], str]] = None
|
|
31
36
|
|
|
32
|
-
def resolved_label(self,
|
|
37
|
+
def resolved_label(self, ctx: object) -> str:
|
|
33
38
|
"""
|
|
34
39
|
Get the resolved label for this menu item.
|
|
35
40
|
|
|
36
|
-
:param
|
|
37
|
-
:type
|
|
41
|
+
:param ctx: The current ctx instance.
|
|
42
|
+
:type ctx: object
|
|
38
43
|
|
|
39
44
|
:return: The resolved label string.
|
|
40
45
|
:rtype: str
|
|
41
46
|
"""
|
|
42
|
-
return self.label_fn(
|
|
47
|
+
return self.label_fn(ctx) if self.label_fn else self.label
|
|
43
48
|
|
|
44
49
|
|
|
45
50
|
# Justification: Data container for styling options needs
|
|
@@ -123,6 +128,7 @@ class MenuStyle:
|
|
|
123
128
|
class Menu:
|
|
124
129
|
"""A simple text-based menu system."""
|
|
125
130
|
|
|
131
|
+
# TODO: Solve too-many-arguments warning later
|
|
126
132
|
# Justification: Multiple attributes for menu state
|
|
127
133
|
# pylint: disable=too-many-arguments
|
|
128
134
|
def __init__(
|
|
@@ -146,6 +152,9 @@ class Menu:
|
|
|
146
152
|
|
|
147
153
|
:param style: Optional MenuStyle for customizing appearance.
|
|
148
154
|
:type style: MenuStyle | None
|
|
155
|
+
|
|
156
|
+
:param on_select: Optional callback when an item is selected.
|
|
157
|
+
:type on_select: Optional[Callable[[MenuItem], None]]
|
|
149
158
|
"""
|
|
150
159
|
self.items = list(items)
|
|
151
160
|
self.viewport = viewport
|
|
@@ -159,6 +168,21 @@ class Menu:
|
|
|
159
168
|
|
|
160
169
|
# pylint: enable=too-many-arguments
|
|
161
170
|
|
|
171
|
+
def set_items(self, items: Sequence[MenuItem]):
|
|
172
|
+
"""Set the menu items.
|
|
173
|
+
:param items: Sequence of new MenuItem instances.
|
|
174
|
+
:type items: Sequence[MenuItem]
|
|
175
|
+
"""
|
|
176
|
+
self.items = list(items)
|
|
177
|
+
|
|
178
|
+
def set_selected_index(self, index: int):
|
|
179
|
+
"""Set the selected index of the menu.
|
|
180
|
+
:param index: New selected index.
|
|
181
|
+
:type index: int
|
|
182
|
+
"""
|
|
183
|
+
if 0 <= index < len(self.items):
|
|
184
|
+
self.selected_index = index
|
|
185
|
+
|
|
162
186
|
def set_labels(self, labels: Sequence[str]):
|
|
163
187
|
"""Set the labels of the menu items.
|
|
164
188
|
:param labels: Sequence of new labels for the menu items.
|
|
@@ -170,7 +194,7 @@ class Menu:
|
|
|
170
194
|
self.items[index] = MenuItem(
|
|
171
195
|
id=item.id,
|
|
172
196
|
label=label,
|
|
173
|
-
|
|
197
|
+
command_factory=item.command_factory,
|
|
174
198
|
label_fn=item.label_fn,
|
|
175
199
|
)
|
|
176
200
|
|
|
@@ -230,6 +254,7 @@ class Menu:
|
|
|
230
254
|
|
|
231
255
|
return False
|
|
232
256
|
|
|
257
|
+
# TODO: Delegate drawing to a renderer class later
|
|
233
258
|
def draw(self, surface: Backend):
|
|
234
259
|
"""
|
|
235
260
|
Draw the menu onto the given backend surface.
|
|
@@ -242,7 +267,7 @@ class Menu:
|
|
|
242
267
|
"Menu requires viewport=Size2D for centering/layout"
|
|
243
268
|
)
|
|
244
269
|
|
|
245
|
-
vw, vh = self.viewport
|
|
270
|
+
vw, vh = self.viewport
|
|
246
271
|
|
|
247
272
|
# 0) Solid background (for main menus)
|
|
248
273
|
if self.style.background_color is not None:
|
|
@@ -322,6 +347,7 @@ class Menu:
|
|
|
322
347
|
font_size=self.style.item_font_size,
|
|
323
348
|
)
|
|
324
349
|
|
|
350
|
+
# TODO: Solve too-many-locals warning later
|
|
325
351
|
# Justification: Local variables for layout calculations
|
|
326
352
|
# pylint: disable=too-many-locals
|
|
327
353
|
def _draw_buttons(self, surface: Backend, x_center: int, cursor_y: int):
|
|
@@ -434,13 +460,14 @@ class Menu:
|
|
|
434
460
|
+ self.style.title_margin_bottom
|
|
435
461
|
)
|
|
436
462
|
|
|
437
|
-
#
|
|
463
|
+
# Sticky width (never shrink)
|
|
438
464
|
if self.stable_width:
|
|
439
465
|
self._max_content_w_seen = max(self._max_content_w_seen, max_w)
|
|
440
466
|
max_w = self._max_content_w_seen
|
|
441
467
|
|
|
442
468
|
return max_w, content_h, title_h
|
|
443
469
|
|
|
470
|
+
# TODO: Solve too-many-arguments warning later
|
|
444
471
|
# Justification: Many arguments for text drawing utility
|
|
445
472
|
# pylint: disable=too-many-arguments
|
|
446
473
|
@staticmethod
|
|
@@ -465,130 +492,289 @@ class Menu:
|
|
|
465
492
|
|
|
466
493
|
|
|
467
494
|
@dataclass
|
|
468
|
-
class MenuModel
|
|
469
|
-
"""
|
|
495
|
+
class MenuModel:
|
|
496
|
+
"""
|
|
497
|
+
Data model for menu scenes.
|
|
498
|
+
|
|
499
|
+
:ivar selected (int): Currently selected menu item index.
|
|
500
|
+
:ivar move_cooldown (float): Cooldown time between menu moves.
|
|
501
|
+
:ivar _cooldown_timer (float): Internal timer for move cooldown.
|
|
502
|
+
"""
|
|
470
503
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
504
|
+
selected: int = 0
|
|
505
|
+
move_cooldown: float = 0.12
|
|
506
|
+
_cooldown_timer: float = 0.0
|
|
474
507
|
|
|
508
|
+
def step_timer(self, dt: float):
|
|
509
|
+
"""
|
|
510
|
+
Step the internal cooldown timer.
|
|
511
|
+
|
|
512
|
+
:param dt: Delta time since last update.
|
|
513
|
+
:type dt: float
|
|
514
|
+
"""
|
|
515
|
+
if self._cooldown_timer > 0:
|
|
516
|
+
self._cooldown_timer = max(0.0, self._cooldown_timer - dt)
|
|
517
|
+
|
|
518
|
+
def can_move(self) -> bool:
|
|
519
|
+
"""
|
|
520
|
+
Check if the menu can move selection (cooldown elapsed).
|
|
475
521
|
|
|
476
|
-
|
|
522
|
+
:return: True if movement is allowed, False otherwise.
|
|
523
|
+
:rtype: bool
|
|
524
|
+
"""
|
|
525
|
+
return self._cooldown_timer <= 0.0
|
|
526
|
+
|
|
527
|
+
def consume_move(self):
|
|
528
|
+
"""Consume a move action and reset the cooldown timer."""
|
|
529
|
+
self._cooldown_timer = self.move_cooldown
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
# TODO: Solve too-many-instance-attributes warning later
|
|
533
|
+
# Justification: Context for menu tick needs multiple attributes.
|
|
534
|
+
# pylint: disable=too-many-instance-attributes
|
|
535
|
+
@dataclass
|
|
536
|
+
class MenuTickContext:
|
|
537
|
+
"""
|
|
538
|
+
Context for a single tick of the menu scene.
|
|
539
|
+
|
|
540
|
+
:ivar input_frame (InputFrame): The current input frame.
|
|
541
|
+
:ivar dt (float): Delta time since last tick.
|
|
542
|
+
:ivar menu (Menu): The Menu instance.
|
|
543
|
+
:ivar model (MenuModel): The MenuModel instance.
|
|
544
|
+
:ivar commands (CommandQueue): The command queue for pushing commands.
|
|
545
|
+
:ivar intent (MenuIntent | None): The current menu intent.
|
|
546
|
+
:ivar quit_cmd_factory (callable | None): Factory for quit command.
|
|
547
|
+
:ivar packet (RenderPacket | None): The resulting render packet.
|
|
477
548
|
"""
|
|
478
|
-
Scene system to manage menu interaction and rendering.
|
|
479
549
|
|
|
480
|
-
:
|
|
550
|
+
input_frame: InputFrame
|
|
551
|
+
dt: float
|
|
552
|
+
|
|
553
|
+
menu: "Menu"
|
|
554
|
+
model: "MenuModel"
|
|
555
|
+
commands: CommandQueue
|
|
556
|
+
|
|
557
|
+
# computed data that systems can write into
|
|
558
|
+
intent: "MenuIntent | None" = None
|
|
559
|
+
|
|
560
|
+
# scene hook: a callable to produce quit cmd (per scene override)
|
|
561
|
+
quit_cmd_factory: callable | None = None
|
|
562
|
+
|
|
563
|
+
# final output
|
|
564
|
+
packet: RenderPacket | None = None
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
# pylint: enable=too-many-instance-attributes
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
@dataclass(frozen=True)
|
|
571
|
+
class MenuIntent:
|
|
481
572
|
"""
|
|
573
|
+
Represents the user's intent in the menu for the current tick.
|
|
482
574
|
|
|
483
|
-
|
|
484
|
-
|
|
575
|
+
:ivar move_up (bool): Whether the user intends to move up.
|
|
576
|
+
:ivar move_down (bool): Whether the user intends to move down.
|
|
577
|
+
:ivar select (bool): Whether the user intends to select the current item.
|
|
578
|
+
:ivar quit (bool): Whether the user intends to quit the menu.
|
|
579
|
+
"""
|
|
485
580
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
581
|
+
move_up: bool = False
|
|
582
|
+
move_down: bool = False
|
|
583
|
+
select: bool = False
|
|
584
|
+
quit: bool = False
|
|
489
585
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
586
|
+
|
|
587
|
+
@dataclass
|
|
588
|
+
class MenuInputSystem:
|
|
589
|
+
"""Converts InputFrame -> MenuIntent."""
|
|
590
|
+
|
|
591
|
+
name: str = "menu_input"
|
|
592
|
+
order: int = 10
|
|
593
|
+
|
|
594
|
+
def step(self, ctx: MenuTickContext):
|
|
595
|
+
"""Step the input system to extract menu intent."""
|
|
596
|
+
pressed = ctx.input_frame.keys_pressed
|
|
597
|
+
ctx.intent = MenuIntent(
|
|
598
|
+
move_up=Key.UP in pressed,
|
|
599
|
+
move_down=Key.DOWN in pressed,
|
|
600
|
+
select=(Key.ENTER in pressed) or (Key.SPACE in pressed),
|
|
601
|
+
quit=Key.ESCAPE in pressed,
|
|
497
602
|
)
|
|
498
603
|
|
|
499
|
-
def handle_event(self, event: Event) -> bool:
|
|
500
|
-
if self.menu is None:
|
|
501
|
-
return False
|
|
502
604
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
up_key=self.scene.model.up_key,
|
|
507
|
-
down_key=self.scene.model.down_key,
|
|
508
|
-
select_key=self.scene.model.select_key,
|
|
509
|
-
)
|
|
510
|
-
return selected_action
|
|
605
|
+
@dataclass
|
|
606
|
+
class MenuNavigationSystem:
|
|
607
|
+
"""Menu navigation system."""
|
|
511
608
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
609
|
+
name: str = "menu_nav"
|
|
610
|
+
order: int = 20
|
|
611
|
+
|
|
612
|
+
def step(self, ctx: MenuTickContext):
|
|
613
|
+
"""Update menu selection based on intent."""
|
|
614
|
+
intent = ctx.intent
|
|
615
|
+
if intent is None:
|
|
616
|
+
return
|
|
617
|
+
|
|
618
|
+
ctx.model.step_timer(ctx.dt)
|
|
619
|
+
|
|
620
|
+
if not ctx.model.can_move():
|
|
621
|
+
return
|
|
622
|
+
|
|
623
|
+
if intent.move_up:
|
|
624
|
+
ctx.menu.move_up()
|
|
625
|
+
ctx.model.selected = ctx.menu.selected_index
|
|
626
|
+
ctx.model.consume_move()
|
|
627
|
+
return
|
|
628
|
+
|
|
629
|
+
if intent.move_down:
|
|
630
|
+
ctx.menu.move_down()
|
|
631
|
+
ctx.model.selected = ctx.menu.selected_index
|
|
632
|
+
ctx.model.consume_move()
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
@dataclass
|
|
636
|
+
class MenuActionSystem:
|
|
637
|
+
"""Menu action execution system."""
|
|
638
|
+
|
|
639
|
+
name: str = "menu_actions"
|
|
640
|
+
order: int = 30
|
|
641
|
+
|
|
642
|
+
def step(self, ctx: MenuTickContext):
|
|
643
|
+
"""Execute actions based on menu intent."""
|
|
644
|
+
intent = ctx.intent
|
|
645
|
+
if intent is None:
|
|
646
|
+
return
|
|
647
|
+
|
|
648
|
+
if intent.select and ctx.menu.items:
|
|
649
|
+
item = ctx.menu.items[ctx.menu.selected_index]
|
|
650
|
+
ctx.commands.push(item.command_factory())
|
|
517
651
|
|
|
652
|
+
if intent.quit and ctx.quit_cmd_factory is not None:
|
|
653
|
+
cmd = ctx.quit_cmd_factory()
|
|
654
|
+
if cmd is not None:
|
|
655
|
+
ctx.commands.push(cmd)
|
|
518
656
|
|
|
519
|
-
|
|
657
|
+
|
|
658
|
+
@dataclass
|
|
659
|
+
class MenuRenderSystem:
|
|
660
|
+
"""Menu rendering system."""
|
|
661
|
+
|
|
662
|
+
name: str = "menu_render"
|
|
663
|
+
order: int = 100
|
|
664
|
+
|
|
665
|
+
def step(self, ctx: MenuTickContext):
|
|
666
|
+
"""Set the render packet to draw the menu."""
|
|
667
|
+
ctx.packet = RenderPacket.from_ops([ctx.menu.draw])
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
class BaseMenuScene(SimScene):
|
|
520
671
|
"""
|
|
521
672
|
Base scene class for menu-based scenes.
|
|
522
673
|
|
|
523
674
|
:ivar model (MenuModel): The data model for the menu scene.
|
|
524
675
|
"""
|
|
525
676
|
|
|
526
|
-
|
|
677
|
+
menu: Menu
|
|
678
|
+
systems: SystemPipeline[MenuTickContext]
|
|
527
679
|
|
|
528
|
-
def __init__(self,
|
|
529
|
-
super().__init__(
|
|
680
|
+
def __init__(self, ctx: RuntimeContext):
|
|
681
|
+
super().__init__(ctx)
|
|
530
682
|
self.model = MenuModel()
|
|
531
683
|
|
|
532
|
-
# hooks
|
|
533
684
|
@property
|
|
534
685
|
def menu_title(self) -> str | None:
|
|
535
686
|
"""
|
|
536
|
-
|
|
687
|
+
Get the title of the menu.
|
|
537
688
|
|
|
538
|
-
:return:
|
|
689
|
+
:return: The menu title string, or None for no title.
|
|
539
690
|
:rtype: str | None
|
|
540
691
|
"""
|
|
541
692
|
return None
|
|
542
693
|
|
|
543
694
|
def menu_style(self) -> MenuStyle:
|
|
544
695
|
"""
|
|
545
|
-
|
|
696
|
+
Get the style configuration for the menu.
|
|
546
697
|
|
|
547
|
-
:return: MenuStyle instance.
|
|
698
|
+
:return: The MenuStyle instance for styling the menu.
|
|
548
699
|
:rtype: MenuStyle
|
|
549
700
|
"""
|
|
550
701
|
return MenuStyle()
|
|
551
702
|
|
|
552
703
|
def menu_items(self) -> list[MenuItem]:
|
|
553
704
|
"""
|
|
554
|
-
|
|
705
|
+
Get the list of menu items for the menu.
|
|
555
706
|
|
|
556
|
-
:return: List of MenuItem instances.
|
|
707
|
+
:return: List of MenuItem instances for the menu.
|
|
557
708
|
:rtype: list[MenuItem]
|
|
558
|
-
|
|
559
|
-
:raises NotImplementedError: If not overridden in subclass.
|
|
560
709
|
"""
|
|
561
710
|
raise NotImplementedError
|
|
562
711
|
|
|
563
|
-
def quit_command(self)
|
|
712
|
+
def quit_command(self):
|
|
564
713
|
"""
|
|
565
|
-
|
|
714
|
+
Get the command to execute when quitting the menu.
|
|
566
715
|
|
|
567
|
-
:return:
|
|
568
|
-
:rtype:
|
|
716
|
+
:return: The command to execute on quit.
|
|
717
|
+
:rtype: Command
|
|
569
718
|
"""
|
|
570
|
-
|
|
719
|
+
# default behavior: quit game
|
|
720
|
+
return QuitCommand()
|
|
571
721
|
|
|
572
722
|
def on_enter(self):
|
|
573
|
-
|
|
574
|
-
|
|
723
|
+
self.menu = Menu(
|
|
724
|
+
self._build_display_items(),
|
|
725
|
+
viewport=self.context.services.window.get_virtual_size(),
|
|
726
|
+
title=self.menu_title,
|
|
727
|
+
style=self.menu_style(),
|
|
728
|
+
)
|
|
729
|
+
self.menu.selected_index = self.model.selected
|
|
730
|
+
self.systems = SystemPipeline()
|
|
731
|
+
self.systems.extend(
|
|
732
|
+
[
|
|
733
|
+
MenuInputSystem(),
|
|
734
|
+
MenuNavigationSystem(),
|
|
735
|
+
MenuActionSystem(),
|
|
736
|
+
MenuRenderSystem(),
|
|
737
|
+
]
|
|
738
|
+
)
|
|
575
739
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
if
|
|
579
|
-
|
|
580
|
-
self.services.input.on_quit(cmd, "quit")
|
|
740
|
+
def tick(self, input_frame: InputFrame, dt: float) -> RenderPacket:
|
|
741
|
+
items = self.menu_items()
|
|
742
|
+
if not items:
|
|
743
|
+
return RenderPacket()
|
|
581
744
|
|
|
582
|
-
self.
|
|
745
|
+
self.model.step_timer(dt)
|
|
583
746
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
return
|
|
587
|
-
self.services.input.handle_event(event, self)
|
|
747
|
+
self.menu.set_items(self._build_display_items())
|
|
748
|
+
self.menu.set_selected_index(self.model.selected)
|
|
588
749
|
|
|
589
|
-
|
|
590
|
-
|
|
750
|
+
ctx = MenuTickContext(
|
|
751
|
+
input_frame=input_frame,
|
|
752
|
+
dt=dt,
|
|
753
|
+
menu=self.menu,
|
|
754
|
+
model=self.model,
|
|
755
|
+
commands=self.context.command_queue,
|
|
756
|
+
quit_cmd_factory=self.quit_command,
|
|
757
|
+
)
|
|
758
|
+
|
|
759
|
+
self.systems.step(ctx)
|
|
591
760
|
|
|
592
|
-
|
|
761
|
+
# always return packet from pipeline
|
|
762
|
+
return ctx.packet or RenderPacket()
|
|
593
763
|
|
|
594
|
-
def
|
|
764
|
+
def _build_display_items(self) -> list[MenuItem]:
|
|
765
|
+
"""
|
|
766
|
+
Resolve dynamic labels (label_fn(ctx)) into the label field the Menu draws.
|
|
767
|
+
Keeps command_factory intact.
|
|
768
|
+
"""
|
|
769
|
+
src = self.menu_items()
|
|
770
|
+
out: list[MenuItem] = []
|
|
771
|
+
for it in src:
|
|
772
|
+
out.append(
|
|
773
|
+
MenuItem(
|
|
774
|
+
id=it.id,
|
|
775
|
+
label=it.resolved_label(self.context),
|
|
776
|
+
command_factory=it.command_factory,
|
|
777
|
+
label_fn=it.label_fn,
|
|
778
|
+
)
|
|
779
|
+
)
|
|
780
|
+
return out
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Deprecated utilities for the mini-arcade-core package.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import functools
|
|
8
|
+
|
|
9
|
+
from mini_arcade_core.utils.logging import logger
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def deprecated(
|
|
13
|
+
reason: str | None = None,
|
|
14
|
+
version: str | None = None,
|
|
15
|
+
alternative: str | None = None,
|
|
16
|
+
):
|
|
17
|
+
"""
|
|
18
|
+
Mark a function as deprecated.
|
|
19
|
+
|
|
20
|
+
:param reason: Optional reason for deprecation
|
|
21
|
+
:type reason: str | None
|
|
22
|
+
|
|
23
|
+
:param version: Optional version when it will be removed
|
|
24
|
+
:type version: str | None
|
|
25
|
+
|
|
26
|
+
:param alternative: Optional alternative function to use
|
|
27
|
+
:type alternative: str | None
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def decorator(func):
|
|
31
|
+
@functools.wraps(func)
|
|
32
|
+
def wrapper(*args, **kwargs):
|
|
33
|
+
message = f"The function {func.__name__} is deprecated"
|
|
34
|
+
if version:
|
|
35
|
+
message += f" and will be removed in version {version}"
|
|
36
|
+
if reason:
|
|
37
|
+
message += f". {reason}"
|
|
38
|
+
if alternative:
|
|
39
|
+
message += f" Use {alternative} instead."
|
|
40
|
+
logger.warning(message)
|
|
41
|
+
return func(*args, **kwargs)
|
|
42
|
+
|
|
43
|
+
return wrapper
|
|
44
|
+
|
|
45
|
+
return decorator
|