mini-arcade-core 1.1.1__py3-none-any.whl → 1.2.0__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.
Files changed (57) hide show
  1. mini_arcade_core/__init__.py +14 -42
  2. mini_arcade_core/backend/__init__.py +1 -2
  3. mini_arcade_core/backend/backend.py +182 -184
  4. mini_arcade_core/backend/types.py +5 -1
  5. mini_arcade_core/engine/commands.py +8 -8
  6. mini_arcade_core/engine/game.py +54 -354
  7. mini_arcade_core/engine/game_config.py +40 -0
  8. mini_arcade_core/engine/gameplay_settings.py +24 -0
  9. mini_arcade_core/engine/loop/config.py +20 -0
  10. mini_arcade_core/engine/loop/hooks.py +77 -0
  11. mini_arcade_core/engine/loop/runner.py +272 -0
  12. mini_arcade_core/engine/loop/state.py +32 -0
  13. mini_arcade_core/engine/managers.py +24 -0
  14. mini_arcade_core/engine/render/context.py +0 -2
  15. mini_arcade_core/engine/render/effects/base.py +2 -2
  16. mini_arcade_core/engine/render/effects/crt.py +4 -4
  17. mini_arcade_core/engine/render/effects/registry.py +1 -1
  18. mini_arcade_core/engine/render/effects/vignette.py +8 -8
  19. mini_arcade_core/engine/render/passes/begin_frame.py +1 -1
  20. mini_arcade_core/engine/render/passes/end_frame.py +1 -1
  21. mini_arcade_core/engine/render/passes/postfx.py +1 -1
  22. mini_arcade_core/engine/render/passes/ui.py +1 -1
  23. mini_arcade_core/engine/render/passes/world.py +6 -6
  24. mini_arcade_core/engine/render/pipeline.py +7 -6
  25. mini_arcade_core/engine/render/viewport.py +10 -4
  26. mini_arcade_core/engine/scenes/models.py +54 -0
  27. mini_arcade_core/engine/scenes/scene_manager.py +213 -0
  28. mini_arcade_core/runtime/audio/audio_adapter.py +4 -3
  29. mini_arcade_core/runtime/audio/audio_port.py +0 -4
  30. mini_arcade_core/runtime/capture/capture_adapter.py +13 -6
  31. mini_arcade_core/runtime/capture/capture_port.py +0 -4
  32. mini_arcade_core/runtime/context.py +8 -6
  33. mini_arcade_core/runtime/scene/scene_query_adapter.py +31 -0
  34. mini_arcade_core/runtime/scene/scene_query_port.py +38 -0
  35. mini_arcade_core/runtime/services.py +3 -2
  36. mini_arcade_core/runtime/window/window_adapter.py +43 -41
  37. mini_arcade_core/runtime/window/window_port.py +3 -17
  38. mini_arcade_core/scenes/debug_overlay.py +5 -4
  39. mini_arcade_core/scenes/registry.py +11 -1
  40. mini_arcade_core/scenes/sim_scene.py +14 -14
  41. mini_arcade_core/ui/menu.py +54 -16
  42. mini_arcade_core/utils/__init__.py +2 -1
  43. mini_arcade_core/utils/logging.py +47 -18
  44. mini_arcade_core/utils/profiler.py +283 -0
  45. {mini_arcade_core-1.1.1.dist-info → mini_arcade_core-1.2.0.dist-info}/METADATA +1 -1
  46. mini_arcade_core-1.2.0.dist-info/RECORD +92 -0
  47. {mini_arcade_core-1.1.1.dist-info → mini_arcade_core-1.2.0.dist-info}/WHEEL +1 -1
  48. mini_arcade_core/managers/inputs.py +0 -284
  49. mini_arcade_core/runtime/scene/scene_adapter.py +0 -125
  50. mini_arcade_core/runtime/scene/scene_port.py +0 -170
  51. mini_arcade_core/sim/protocols.py +0 -41
  52. mini_arcade_core/sim/runner.py +0 -222
  53. mini_arcade_core-1.1.1.dist-info/RECORD +0 -85
  54. /mini_arcade_core/{managers → engine}/cheats.py +0 -0
  55. /mini_arcade_core/{managers → engine/loop}/__init__.py +0 -0
  56. /mini_arcade_core/{sim → engine/scenes}/__init__.py +0 -0
  57. {mini_arcade_core-1.1.1.dist-info → mini_arcade_core-1.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -9,7 +9,8 @@ import traceback
9
9
  from importlib.metadata import PackageNotFoundError, version
10
10
  from typing import Callable, Type, Union
11
11
 
12
- from mini_arcade_core.engine.game import Game, GameConfig, WindowConfig
12
+ from mini_arcade_core.engine.game import Game
13
+ from mini_arcade_core.engine.game_config import GameConfig
13
14
  from mini_arcade_core.scenes.registry import SceneRegistry
14
15
  from mini_arcade_core.scenes.sim_scene import SimScene
15
16
  from mini_arcade_core.utils import logger
@@ -17,59 +18,31 @@ from mini_arcade_core.utils import logger
17
18
  SceneFactoryLike = Union[Type[SimScene], Callable[[Game], SimScene]]
18
19
 
19
20
 
20
- # TODO: Improve exception handling and logging in run_game
21
- # TODO: Consider reducing parameters by using a single config object
22
- # TODO: Delegate responsibilities to Game class where appropriate
21
+ # TODO: Improve exception handliers by usingng and logging in run_game
23
22
  def run_game(
24
- scene: SceneFactoryLike | None = None,
25
- config: GameConfig | None = None,
26
- registry: SceneRegistry | None = None,
27
- initial_scene: str = "main",
23
+ game_config: GameConfig | None = None,
24
+ scene_registry: SceneRegistry | None = None,
28
25
  ):
29
26
  """
30
27
  Convenience helper to bootstrap and run a game with a single scene.
31
28
 
32
- Supports both:
33
- - run_game(SceneClass, cfg) # legacy
34
- - run_game(config=cfg, initial_scene="main", registry=...) # registry-based
35
- - run_game(cfg) # config-only
29
+ :param game_config: Optional GameConfig to customize game settings.
30
+ :type game_config: GameConfig | None
36
31
 
37
- :param scene: Optional SimScene factory/class to register
38
- :type scene: SceneFactoryLike | None
32
+ :param scene_registry: Optional SceneRegistry for scene management.
33
+ :type scene_registry: SceneRegistry | None
39
34
 
40
- :param initial_scene: The SimScene ID to start the game with.
41
- :type initial_scene: str
42
-
43
- :param config: Optional GameConfig to customize game settings.
44
- :type config: GameConfig | None
45
-
46
- :param registry: Optional SceneRegistry for scene management.
47
- :type registry: SceneRegistry | None
48
-
49
- :raises ValueError: If the provided config does not have a valid Backend.
35
+ :raises ValueError: If the provided game_config does not have a valid Backend.
50
36
  """
51
37
  try:
52
- # Handle run_game(cfg) where the first arg is actually a GameConfig
53
- if isinstance(scene, GameConfig) and config is None:
54
- config = scene
55
- scene = None
56
-
57
- cfg = config or GameConfig()
38
+ cfg = game_config or GameConfig()
58
39
  if cfg.backend is None:
59
40
  raise ValueError(
60
41
  "GameConfig.backend must be set to a Backend instance"
61
42
  )
62
43
 
63
- # If user provided a SimScene factory/class, ensure it's registered
64
- if scene is not None:
65
- if registry is None:
66
- registry = SceneRegistry(_factories={})
67
- registry.register(
68
- initial_scene, scene
69
- ) # SimScene class is callable(game) -> SimScene
70
-
71
- game = Game(cfg, registry=registry)
72
- game.run(initial_scene)
44
+ game = Game(cfg, scene_registry=scene_registry)
45
+ game.run()
73
46
  # Justification: We need to catch all exceptions while we improve error handling.
74
47
  # pylint: disable=broad-exception-caught
75
48
  except Exception as e:
@@ -78,7 +51,7 @@ def run_game(
78
51
  # pylint: enable=broad-exception-caught
79
52
 
80
53
 
81
- PACKAGE_NAME = "mini-arcade-core" # or whatever is in your pyproject.toml
54
+ PACKAGE_NAME = "mini-arcade-core"
82
55
 
83
56
 
84
57
  def get_version() -> str:
@@ -107,7 +80,6 @@ def get_version() -> str:
107
80
  __all__ = [
108
81
  "Game",
109
82
  "GameConfig",
110
- "WindowConfig",
111
83
  "run_game",
112
84
  ]
113
85
 
@@ -6,9 +6,8 @@ This is the only part of the code that talks to SDL/pygame directly.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- from .backend import Backend, WindowSettings
9
+ from .backend import Backend
10
10
 
11
11
  __all__ = [
12
12
  "Backend",
13
- "WindowSettings",
14
13
  ]
@@ -5,320 +5,318 @@ This is the only part of the code that talks to SDL/pygame directly.
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
- from dataclasses import dataclass
9
8
  from typing import Iterable, Protocol
10
9
 
11
10
  from .events import Event
12
- from .types import Color
13
11
 
12
+ # Justification: Many positional and keyword arguments needed for some backend methods.
13
+ # Might be refactored later.
14
+ # pylint: disable=too-many-positional-arguments,too-many-arguments
14
15
 
15
- @dataclass
16
- class WindowSettings:
17
- """
18
- Settings for the backend window.
19
16
 
20
- :ivar width (int): Width of the window in pixels.
21
- :ivar height (int): Height of the window in pixels.
17
+ class WindowProtocol(Protocol):
18
+ """
19
+ Represents a game window.
22
20
  """
23
21
 
24
22
  width: int
25
23
  height: int
26
24
 
25
+ def set_title(self, title: str):
26
+ """
27
+ Set the window title.
27
28
 
28
- # TODO: Refactor backend interface into smaller protocols?
29
- # Justification: Many public methods needed for backend interface
30
- # pylint: disable=too-many-public-methods
31
- class Backend(Protocol):
32
- """
33
- Interface that any rendering/input backend must implement.
29
+ :param title: New window title.
30
+ :type title: str
31
+ """
34
32
 
35
- mini-arcade-core only talks to this protocol, never to SDL/pygame directly.
36
- """
33
+ def resize(self, width: int, height: int):
34
+ """
35
+ Resize the window.
37
36
 
38
- def init(self, window_settings: WindowSettings):
37
+ :param width: New width in pixels.
38
+ :type width: int
39
+ :param height: New height in pixels.
40
+ :type height: int
39
41
  """
40
- Initialize the backend and open a window.
41
- Should be called once before the main loop.
42
42
 
43
- :param window_settings: Settings for the backend window.
44
- :type window_settings: WindowSettings
43
+ def size(self) -> tuple[int, int]:
45
44
  """
45
+ Get the window size.
46
46
 
47
- def set_window_title(self, title: str):
47
+ :return: Tuple of (width, height) in pixels.
48
+ :rtype: tuple[int, int]
48
49
  """
49
- Set the window title.
50
50
 
51
- :param title: The new title for the window.
52
- :type title: str
51
+ def drawable_size(self) -> tuple[int, int]:
53
52
  """
54
- raise NotImplementedError
53
+ Get the drawable size of the window.
55
54
 
56
- def poll_events(self) -> Iterable[Event]:
55
+ :return: Tuple of (width, height) in pixels.
56
+ :rtype: tuple[int, int]
57
57
  """
58
- Return all pending events since last call.
59
- Concrete backends will translate their native events into core Event objects.
60
58
 
61
- :return: An iterable of Event objects.
59
+
60
+ class InputProtocol(Protocol):
61
+ """
62
+ Interface for input operations.
63
+ """
64
+
65
+ def poll(self) -> Iterable[Event]:
66
+ """
67
+ Get the list of input events since the last call.
68
+
69
+ :return: Iterable of Event instances.
62
70
  :rtype: Iterable[Event]
63
71
  """
64
72
 
73
+
74
+ class RenderProtocol(Protocol):
75
+ """
76
+ Interface for rendering operations.
77
+ """
78
+
65
79
  def set_clear_color(self, r: int, g: int, b: int):
66
80
  """
67
- Set the background/clear color used by begin_frame.
81
+ Set the clear color for the renderer.
68
82
 
69
83
  :param r: Red component (0-255).
70
84
  :type r: int
71
-
72
85
  :param g: Green component (0-255).
73
86
  :type g: int
74
-
75
87
  :param b: Blue component (0-255).
76
88
  :type b: int
77
89
  """
78
90
 
79
91
  def begin_frame(self):
80
- """
81
- Prepare for drawing a new frame (e.g. clear screen).
82
- """
92
+ """Begin a new rendering frame."""
83
93
 
84
94
  def end_frame(self):
85
- """
86
- Present the frame to the user (swap buffers).
87
- """
95
+ """End the current rendering frame."""
88
96
 
89
- # Justification: Simple drawing API for now
90
- # pylint: disable=too-many-arguments,too-many-positional-arguments
91
- def draw_rect(
92
- self,
93
- x: int,
94
- y: int,
95
- w: int,
96
- h: int,
97
- color: Color = (255, 255, 255),
98
- ):
97
+ def draw_rect(self, x: int, y: int, w: int, h: int, color=(255, 255, 255)):
99
98
  """
100
- Draw a filled rectangle in some default color.
101
- We'll keep this minimal for now; later we can extend with colors/sprites.
99
+ Draw a filled rectangle.
102
100
 
103
- :param x: X position of the rectangle's top-left corner.
101
+ :param x: The x-coordinate of the rectangle.
104
102
  :type x: int
105
-
106
- :param y: Y position of the rectangle's top-left corner.
103
+ :param y: The y-coordinate of the rectangle.
107
104
  :type y: int
108
-
109
- :param w: Width of the rectangle.
105
+ :param w: The width of the rectangle.
110
106
  :type w: int
111
-
112
- :param h: Height of the rectangle.
107
+ :param h: The height of the rectangle.
113
108
  :type h: int
114
-
115
- :param color: RGB color tuple.
116
- :type color: Color
109
+ :param color: The color of the rectangle as an (R, G, B) or (R, G, B, A) tuple.
110
+ :type color: tuple[int, int, int] | tuple[int, int, int, int]
117
111
  """
118
112
 
119
- def draw_text(
120
- self,
121
- x: int,
122
- y: int,
123
- text: str,
124
- color: Color = (255, 255, 255),
125
- font_size: int | None = None,
113
+ def draw_line(
114
+ self, x1: int, y1: int, x2: int, y2: int, color=(255, 255, 255)
126
115
  ):
127
116
  """
128
- Draw text at the given position in a default font and color.
117
+ Draw a line between two points.
129
118
 
130
- Backends may ignore advanced styling for now; this is just to render
131
- simple labels like menu items, scores, etc.
119
+ :param x1: The x-coordinate of the start point.
120
+ :type x1: int
121
+ :param y1: The y-coordinate of the start point.
122
+ :type y1: int
123
+ :param x2: The x-coordinate of the end point.
124
+ :type x2: int
125
+ :param y2: The y-coordinate of the end point.
126
+ :type y2: int
127
+ :param color: The color of the line as an (R, G, B) or (R, G, B, A) tuple.
128
+ :type color: tuple[int, int, int] | tuple[int, int, int, int]
129
+ """
132
130
 
133
- :param x: X position of the text's top-left corner.
134
- :type x: int
131
+ def set_clip_rect(self, x: int, y: int, w: int, h: int):
132
+ """
133
+ Set the clipping rectangle.
135
134
 
136
- :param y: Y position of the text's top-left corner.
135
+ :param x: The x-coordinate of the clipping rectangle.
136
+ :type x: int
137
+ :param y: The y-coordinate of the clipping rectangle.
137
138
  :type y: int
139
+ :param w: The width of the clipping rectangle.
140
+ :type w: int
141
+ :param h: The height of the clipping rectangle.
142
+ :type h: int
143
+ """
138
144
 
139
- :param text: The text string to draw.
140
- :type text: str
145
+ def clear_clip_rect(self):
146
+ """Clear the clipping rectangle."""
141
147
 
142
- :param color: RGB color tuple.
143
- :type color: Color
144
- """
145
148
 
146
- # pylint: enable=too-many-arguments,too-many-positional-arguments
149
+ class TextProtocol(Protocol):
150
+ """
151
+ Interface for text rendering operations.
152
+ """
147
153
 
148
- def measure_text(self, text: str) -> tuple[int, int]:
154
+ def measure(
155
+ self, text: str, font_size: int | None = None
156
+ ) -> tuple[int, int]:
149
157
  """
150
- Measure the width and height of the given text string in pixels.
158
+ Measure the width and height of the given text.
151
159
 
152
- :param text: The text string to measure.
160
+ :param text: The text to measure.
153
161
  :type text: str
154
-
155
- :return: A tuple (width, height) in pixels.
162
+ :param font_size: The font size to use for measurement.
163
+ :type font_size: int | None
164
+ :return: A tuple containing the width and height of the text.
156
165
  :rtype: tuple[int, int]
157
166
  """
158
- raise NotImplementedError
159
167
 
160
- def capture_frame(self, path: str | None = None) -> bytes | None:
168
+ def draw(
169
+ self,
170
+ x: int,
171
+ y: int,
172
+ text: str,
173
+ color=(255, 255, 255),
174
+ font_size: int | None = None,
175
+ ):
176
+ """
177
+ Draw the given text at the specified position.
178
+
179
+ :param x: The x-coordinate to draw the text.
180
+ :type x: int
181
+ :param y: The y-coordinate to draw the text.
182
+ :type y: int
183
+ :param text: The text to draw.
184
+ :type text: str
185
+ :param color: The color of the text as an (R, G, B) or (R, G, B, A) tuple.
186
+ :type color: tuple[int, int, int] | tuple[int, int, int, int]
187
+ :param font_size: The font size to use for drawing.
188
+ :type font_size: int | None
161
189
  """
162
- Capture the current frame.
163
- If `path` is provided, save to that file (e.g. PNG).
164
- Returns raw bytes (PNG) or None if unsupported.
165
190
 
166
- :param path: Optional file path to save the screenshot.
167
- :type path: str | None
168
191
 
169
- :return: Raw image bytes if no path given, else None.
170
- :rtype: bytes | None
171
- """
172
- raise NotImplementedError
192
+ class AudioProtocol(Protocol):
193
+ """
194
+ Interface for audio operations.
195
+ """
173
196
 
174
- def init_audio(
197
+ def init(
175
198
  self, frequency: int = 44100, channels: int = 2, chunk_size: int = 2048
176
199
  ):
177
200
  """
178
- Initialize SDL_mixer audio.
201
+ Initialize audio subsystem.
179
202
 
180
203
  :param frequency: Audio frequency in Hz.
181
204
  :type frequency: int
182
-
183
205
  :param channels: Number of audio channels (1=mono, 2=stereo).
184
206
  :type channels: int
185
-
186
207
  :param chunk_size: Size of audio chunks.
187
208
  :type chunk_size: int
188
209
  """
189
210
 
190
- def shutdown_audio(self):
191
- """Shutdown SDL_mixer audio and free loaded sounds."""
211
+ def shutdown(self):
212
+ """
213
+ Shutdown the audio subsystem.
214
+ """
192
215
 
193
216
  def load_sound(self, sound_id: str, path: str):
194
217
  """
195
- Load a WAV sound and store it by ID.
196
- Example: backend.load_sound("hit", "assets/sfx/hit.wav")
218
+ Load a sound file.
197
219
 
198
220
  :param sound_id: Unique identifier for the sound.
199
221
  :type sound_id: str
200
-
201
- :param path: File path to the WAV sound.
222
+ :param path: File path to the sound.
202
223
  :type path: str
203
224
  """
204
225
 
205
226
  def play_sound(self, sound_id: str, loops: int = 0):
206
227
  """
207
228
  Play a loaded sound.
208
- loops=0 => play once
209
- loops=-1 => infinite loop
210
- loops=1 => play twice (SDL convention)
211
229
 
212
230
  :param sound_id: Unique identifier for the sound.
213
231
  :type sound_id: str
214
-
215
232
  :param loops: Number of times to loop the sound.
216
233
  :type loops: int
217
234
  """
218
235
 
219
236
  def set_master_volume(self, volume: int):
220
237
  """
221
- Master volume: 0..128
238
+ Set the master volume.
239
+
240
+ :param volume: Volume level (0-128).
241
+ :type volume: int
222
242
  """
223
243
 
224
244
  def set_sound_volume(self, sound_id: str, volume: int):
225
245
  """
226
- Per-sound volume: 0..128
246
+ Set volume for a specific sound.
227
247
 
228
248
  :param sound_id: Unique identifier for the sound.
229
249
  :type sound_id: str
230
-
231
250
  :param volume: Volume level (0-128).
232
251
  :type volume: int
233
252
  """
234
253
 
235
- def stop_all_sounds(self):
236
- """Stop all channels."""
237
-
238
- def set_viewport_transform(
239
- self, offset_x: int, offset_y: int, scale: float
240
- ):
254
+ def stop_all(self):
241
255
  """
242
- Apply a transform so draw_* receives VIRTUAL coords and backend maps to screen.
243
-
244
- :param offset_x: X offset in pixels.
245
- :type offset_x: int
246
-
247
- :param offset_y: Y offset in pixels.
248
- :type offset_y: int
249
-
250
- :param scale: Scale factor.
251
- :type scale: float
256
+ Stop all currently playing sounds.
252
257
  """
253
- raise NotImplementedError
254
258
 
255
- def clear_viewport_transform(self):
256
- """Reset any viewport transform back to identity."""
257
- raise NotImplementedError
258
-
259
- def resize_window(self, width: int, height: int):
260
- """
261
- Resize the actual OS window (SDL_SetWindowSize in native backend).
262
259
 
263
- :param width: New width in pixels.
264
- :type width: int
260
+ class CaptureProtocol(Protocol):
261
+ """
262
+ Interface for frame capture operations.
263
+ """
265
264
 
266
- :param height: New height in pixels.
267
- :type height: int
265
+ def bmp(self, path: str | None = None) -> bool:
268
266
  """
269
- raise NotImplementedError
267
+ Capture the current frame as a BMP file.
270
268
 
271
- def set_clip_rect(self, x: int, y: int, w: int, h: int):
269
+ :param path: Optional file path to save the BMP. If None, returns bytes.
270
+ :type path: str | None
271
+ :return: Whether the capture was successful.
272
+ :rtype: bool
272
273
  """
273
- Set a clipping rectangle for rendering.
274
274
 
275
- :param x: X position of the rectangle's top-left corner.
276
- :type x: int
277
275
 
278
- :param y: Y position of the rectangle's top-left corner.
279
- :type y: int
276
+ # TODO: Refactor backend interface into smaller protocols?
277
+ # Justification: Many public methods needed for backend interface
278
+ # pylint: disable=too-many-public-methods
279
+ class Backend(Protocol):
280
+ """
281
+ Interface that any rendering/input backend must implement.
282
+ mini-arcade-core only talks to this protocol, never to SDL/pygame directly.
280
283
 
281
- :param w: Width of the rectangle.
282
- :type w: int
284
+ :ivar window (WindowProtocol): Window management interface.
285
+ :ivar audio (AudioProtocol): Audio management interface.
286
+ :ivar input (InputProtocol): Input management interface.
287
+ :ivar render (RenderProtocol): Rendering interface.
288
+ :ivar text (TextProtocol): Text rendering interface.
289
+ :ivar capture (CaptureProtocol): Frame capture interface.
290
+ """
283
291
 
284
- :param h: Height of the rectangle.
285
- :type h: int
286
- """
287
- raise NotImplementedError
292
+ window: WindowProtocol
293
+ audio: AudioProtocol
294
+ input: InputProtocol
295
+ render: RenderProtocol
296
+ text: TextProtocol
297
+ capture: CaptureProtocol
288
298
 
289
- def clear_clip_rect(self):
290
- """Clear any clipping rectangle."""
291
- raise NotImplementedError
299
+ def init(self):
300
+ """
301
+ Initialize the backend and open a window.
302
+ Should be called once before the main loop.
303
+ """
292
304
 
293
- # Justification: Simple drawing API for now
294
- # pylint: disable=too-many-arguments,too-many-positional-arguments
295
- def draw_line(
296
- self,
297
- x1: int,
298
- y1: int,
299
- x2: int,
300
- y2: int,
301
- color: tuple[int, ...] = (255, 255, 255),
305
+ def set_viewport_transform(
306
+ self, offset_x: int, offset_y: int, scale: float
302
307
  ):
303
308
  """
304
- Draw a line between two points in some default color.
305
-
306
- :param x1: X position of the start point.
307
- :type x1: int
308
-
309
- :param y1: Y position of the start point.
310
- :type y1: int
311
-
312
- :param x2: X position of the end point.
313
- :type x2: int
309
+ Set the viewport transformation.
314
310
 
315
- :param y2: Y position of the end point.
316
- :type y2: int
317
-
318
- :param color: RGB color tuple.
319
- :type color: tuple[int, ...]
311
+ :param offset_x: Horizontal offset.
312
+ :type offset_x: int
313
+ :param offset_y: Vertical offset.
314
+ :type offset_y: int
315
+ :param scale: Scaling factor.
316
+ :type scale: float
320
317
  """
321
- raise NotImplementedError
322
318
 
323
-
324
- # pylint: enable=too-many-arguments,too-many-positional-arguments
319
+ def clear_viewport_transform(self):
320
+ """
321
+ Clear the viewport transformation (reset to defaults).
322
+ """
@@ -6,4 +6,8 @@ from __future__ import annotations
6
6
 
7
7
  from typing import Tuple, Union
8
8
 
9
- Color = Union[Tuple[int, int, int], Tuple[int, int, int, int]]
9
+ ColorRGB = Tuple[int, int, int]
10
+ ColorRGBA = Tuple[int, int, int, int]
11
+
12
+ Color = Union[ColorRGB, ColorRGBA]
13
+ Alpha = Union[float, int]