Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ Arcade [PyPi Release History](https://pypi.org/project/arcade/#history) page.
- Type checking with mypy and pyright now fails the CI check, like formatting and linting. Their existing errors are fixed.
- Fixed code excerpts in the docs that highlighted or showed the wrong lines, because the example code had changed since their line numbers were set. This covers 62 excerpts: 48 in the platformer, pymunk platformer, menu, card game, compute shader, raycasting and shader toy glow tutorials, and 14 on example pages. Most now highlight the same code they were written for. Where the code was rewritten, they highlight the code that does the same job now.
- Removed `arcade/experimental/perspective_parallax.py`, an unreferenced demo of `PerspectiveProjector` that didn't show a parallax effect. The `background_parallax` example shows parallax scrolling.
- Sped up `import arcade` from about 2.7 to 0.9 seconds on Windows ([#1159](https://github.com/pythonarcade/arcade/issues/1159)). Most of the time went to pyglet looking for game controllers when imported. Arcade now imports the controller code the first time `get_controllers()`, `ControllerManager`, `get_joysticks()` or `get_game_controllers()` is used, so the wait happens then instead. Sound still loads at import, so playing the first sound doesn't pause the game.
- Arcade's extra controller mappings now load at the same time. Code that calls `pyglet.input.get_controllers()` directly without using Arcade's controller functions first only gets pyglet's own mappings.

## 4.0.0.dev8

Expand Down
40 changes: 25 additions & 15 deletions arcade/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# Error out if we import Arcade with an incompatible version of Python.
import sys
import os
from typing import Final
from typing import TYPE_CHECKING, Final

from pathlib import Path

Expand Down Expand Up @@ -138,8 +138,9 @@ def configure_logging(level: int | None = None):
from .screenshot import get_image
from .screenshot import get_pixel

# We don't have joysticks game controllers in headless mode
if not headless:
# Game controllers are imported on first use, because pyglet lists the
# input devices when imported. See __getattr__ at the bottom of this file.
if TYPE_CHECKING:
from .joysticks import get_game_controllers
from .joysticks import get_joysticks
from .controller import ControllerManager
Expand Down Expand Up @@ -444,15 +445,24 @@ def configure_logging(level: int | None = None):

__version__ = VERSION

# Piggyback on pyglet's doc run detection
if not getattr(sys, "is_pyglet_doc_run", False):
# Load additional game controller mappings to Pyglet
if not headless:
try:
import pyglet.input.controller

mappings_file = resources.resolve(":system:gamecontrollerdb.txt")
# TODO: remove string conversion once fixed upstream
pyglet.input.controller.add_mappings_from_file(str(mappings_file))
except AssertionError:
pass
_LAZY_IMPORTS: dict[str, str] = {}
# We don't have joysticks game controllers in headless mode
if not headless:
_LAZY_IMPORTS = {
"get_joysticks": "joysticks",
"get_game_controllers": "joysticks",
"joysticks": "joysticks",
"ControllerManager": "controller",
"get_controllers": "controller",
"controller": "controller",
}


def __getattr__(name: str):
module_name = _LAZY_IMPORTS.get(name)
if module_name is None:
raise AttributeError(f"module 'arcade' has no attribute {name!r}")
import importlib

module = importlib.import_module(f".{module_name}", __name__)
return module if name == module_name else getattr(module, name)
15 changes: 15 additions & 0 deletions arcade/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,25 @@
https://pyglet.readthedocs.io/en/latest/programming_guide/input.html#using-controllers
"""

import sys

import pyglet.input
import pyglet.input.controller

from arcade import resources

__all__ = ["get_controllers", "ControllerManager"]

# Load additional game controller mappings to pyglet. Piggyback on
# pyglet's doc run detection.
if not getattr(sys, "is_pyglet_doc_run", False):
try:
mappings_file = resources.resolve(":system:gamecontrollerdb.txt")
# TODO: remove string conversion once fixed upstream
pyglet.input.controller.add_mappings_from_file(str(mappings_file))
except AssertionError:
pass


def get_controllers():
"""
Expand Down
12 changes: 9 additions & 3 deletions arcade/experimental/controller_window.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import warnings
from __future__ import annotations

from pyglet.input import Controller
import warnings
from typing import TYPE_CHECKING

import arcade
from arcade import ControllerManager

if TYPE_CHECKING:
# Importing pyglet.input is slow, so only type checkers do it here
from pyglet.input import Controller


class _WindowControllerBridge:
Expand All @@ -21,6 +25,8 @@ class _WindowControllerBridge:
def __init__(self, window: arcade.Window):
self.window = window

from arcade.controller import ControllerManager

self.cm = ControllerManager()
self.cm.push_handlers(self)

Expand Down
2 changes: 1 addition & 1 deletion arcade/experimental/input_manager_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def __init__(
# with open("out.json", "w") as f:
# json.dump(serialized, f)

self.controller_manager = pyglet.input.ControllerManager()
self.controller_manager = arcade.ControllerManager()
self.controller_manager.set_handlers(self.on_connect, self.on_disconnect)

for index, controller in enumerate(self.controller_manager.get_controllers()):
Expand Down
21 changes: 12 additions & 9 deletions arcade/gui/ui_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@

from collections import defaultdict
from collections.abc import Iterable
from typing import TypeGuard, TypeVar
from typing import TYPE_CHECKING, TypeGuard, TypeVar

from pyglet.event import EVENT_HANDLED, EVENT_UNHANDLED, EventDispatcher
from pyglet.input import Controller
from pyglet.math import Vec2

import arcade
Expand Down Expand Up @@ -43,6 +42,10 @@
from arcade.gui.widgets import UIWidget
from arcade.types import LBWH, AnchorPoint, Point2, Rect

if TYPE_CHECKING:
# Importing pyglet.input is slow, so only type checkers do it here
from pyglet.input import Controller

W = TypeVar("W", bound=UIWidget)


Expand Down Expand Up @@ -496,27 +499,27 @@ def on_resize(self, width, height):

self.trigger_render()

def on_connect(self, controller: Controller):
def on_connect(self, controller: "Controller"):
"""Called when a controller is connected."""
self.dispatch_ui_event(UIControllerConnectEvent(controller))

def on_disconnect(self, controller: Controller):
def on_disconnect(self, controller: "Controller"):
"""Called when a controller is disconnected."""
self.dispatch_ui_event(UIControllerDisconnectEvent(controller))

def on_stick_motion(self, controller: Controller, name: str, value: Vec2):
def on_stick_motion(self, controller: "Controller", name: str, value: Vec2):
return self.dispatch_ui_event(UIControllerStickEvent(controller, name, value))

def on_trigger_motion(self, controller: Controller, name: str, value: float):
def on_trigger_motion(self, controller: "Controller", name: str, value: float):
return self.dispatch_ui_event(UIControllerTriggerEvent(controller, name, value))

def on_button_press(self, controller: Controller, button: str):
def on_button_press(self, controller: "Controller", button: str):
return self.dispatch_ui_event(UIControllerButtonPressEvent(controller, button))

def on_button_release(self, controller: Controller, button: str):
def on_button_release(self, controller: "Controller", button: str):
return self.dispatch_ui_event(UIControllerButtonReleaseEvent(controller, button))

def on_dpad_motion(self, controller: Controller, value: Vec2):
def on_dpad_motion(self, controller: "Controller", value: Vec2):
return self.dispatch_ui_event(UIControllerDpadEvent(controller, value))

@property
Expand Down
9 changes: 6 additions & 3 deletions arcade/input/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

from collections.abc import Callable
from enum import Enum
from typing import Any, TypeVar
from typing import TYPE_CHECKING, Any, TypeVar

import pyglet
from pyglet.input.base import Controller
from typing_extensions import TypedDict

import arcade
Expand All @@ -18,6 +17,10 @@
serialize_action,
serialize_axis,
)

if TYPE_CHECKING:
# Importing pyglet.input is slow, so only type checkers do it here
from pyglet.input import Controller
from arcade.input.inputs import InputEnum, InputType
from arcade.input.raw_dicts import RawAction, RawAxis
from arcade.types import OneOrIterableOf
Expand Down Expand Up @@ -247,7 +250,7 @@ def copy_existing(self, existing: InputManager):
def from_existing(
cls,
existing: InputManager,
controller: pyglet.input.Controller | None = None,
controller: Controller | None = None,
) -> InputManager:
"""
Create a new InputManager from an existing one. This does not copy current input values,
Expand Down
Loading