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
12 changes: 6 additions & 6 deletions .github/workflows/code_quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,23 @@ jobs:
- name: Sync UV project
run: uv sync

# Formatting and linting errors fail the check. The later steps still
# run when an earlier one fails, so every problem is reported at once.
- name: Formatting (Ruff)
if: success()
continue-on-error: true
run: uv run make.py format --check

- name: Linting (Ruff)
if: success()
continue-on-error: true
if: ${{ !cancelled() }}
run: uv run make.py ruff-check

# Type checking doesn't fail the check yet: there are existing errors
- name: Type Checking (MyPy)
if: success()
if: ${{ !cancelled() }}
continue-on-error: true
run: uv run make.py mypy

- name: Type Checking (Pyright)
if: success()
if: ${{ !cancelled() }}
continue-on-error: true
run: uv run make.py pyright

Expand Down
10 changes: 5 additions & 5 deletions arcade/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -579,13 +579,13 @@ def _dispatch_frame(self, delta_time: float) -> None:
The modulus on the accumulated draw time means that when the update rate is greater
than the draw rate no time is lost.

This method is entirely skipped when running in pyodide, this is because the event loop
is driven by requestAnimationFrame in the browser, which adds some unique limitations and
This method is entirely skipped when running in pyodide, this is because the event loop is
driven by requestAnimationFrame in the browser, which adds some unique limitations and
considerations around Arcade's event loop handling. In pyglet, the draw() function of the
window is called directly during the requestAnimationFrame loop, so Arcade handles special
control of the update/draw timing directly in that function. Arcade's version of this function
is never called on desktop, because this function is called instead, and this calls directly
to the superclass's implementation.
control of the update/draw timing directly in that function. Arcade's version of this
function is never called on desktop, because this function is called instead, and this calls
directly to the superclass's implementation.

Args:
delta_time: The amount of time since the last update.
Expand Down
2 changes: 2 additions & 0 deletions arcade/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
from .cli import run_arcade_cli

__all__ = ["run_arcade_cli"]
2 changes: 1 addition & 1 deletion arcade/experimental/input_manager_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from pyglet.input import Controller

import arcade
from arcade.input import ActionState, ControllerSticks, ControllerButtons, InputManager, Keys
from arcade.input import ActionState, ControllerButtons, ControllerSticks, InputManager, Keys

WINDOW_WIDTH = 1280
WINDOW_HEIGHT = 720
Expand Down
2 changes: 1 addition & 1 deletion arcade/gui/widgets/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

import pyglet
from pyglet.event import EVENT_HANDLED, EVENT_UNHANDLED
from pyglet.text.caret import Caret
from pyglet.text import LinearGradient
from pyglet.text.caret import Caret
from pyglet.text.document import AbstractDocument
from typing_extensions import override

Expand Down
84 changes: 52 additions & 32 deletions arcade/input/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,13 @@ class InputDevice(Enum):

class InputManager:
"""
The InputManager is responsible for managing input for a given device, this can be the keyboard/mouse or a controller.
The InputManager is responsible for managing input for a given device, this can be the
keyboard/mouse or a controller.

In general, you can share one InputManager for one controller and the keyboard/mouse, there are even utilities to handle
automatically switching between them as the active device. However if you intend to have multiple controllers connected
to your game, each controller should have it's own InputManager.
In general, you can share one InputManager for one controller and the keyboard/mouse, there are
even utilities to handle automatically switching between them as the active device. However if
you intend to have multiple controllers connected to your game, each controller should have it's
own InputManager.

For runnable examples of how to use this, please see Arcdade's
:ref:`built-in InputManager examples <input_manager_examples>`.
Expand All @@ -79,13 +81,15 @@ class InputManager:
controller:
Either a Pyglet Controller object or None if you only want to use the keyboard/mouse.
allow_keyboard:
Whether to allow keyboard input, defaults to True, can be changed safely after initialization.
Whether to allow keyboard input, defaults to True, can be changed safely after
initialization.
action_handlers:
Either one or a collection of functions that will be called for every action that is triggered.
:py:meth:`InputManager.subscribe_to_action` may be preferred to subscribe to individual actions instead.
Either one or a collection of functions that will be called for every action that is
triggered. :py:meth:`InputManager.subscribe_to_action` may be preferred to subscribe to
individual actions instead.
controller_deadzone:
The deadzone for controller input, defaults to 0.1. If changes to axis values are within this
range from the underlying hardware, they will be ignored.
The deadzone for controller input, defaults to 0.1. If changes to axis values are within
this range from the underlying hardware, they will be ignored.
"""

def __init__(
Expand Down Expand Up @@ -155,15 +159,17 @@ def __init__(

def serialize(self) -> RawInputManager:
"""
Serializes the current state of the InputManager into a RawInputManager dictionary which can easily be saved to JSON.
Serializes the current state of the InputManager into a RawInputManager dictionary which can
easily be saved to JSON.

This does not include current values of inputs, but rather the structure of the InputManager. Including:
This does not include current values of inputs, but rather the structure of the
InputManager. Including:
- Actions: All registered actions
- Axes: All registered axis inputs
- Current Mappings: All current mappings of underlying inputs to actions/axis

The output dictionary of this function can be passed to :meth:`arcade.InputManager.parse` to create a new InputManager
from a serialized one.
The output dictionary of this function can be passed to :meth:`arcade.InputManager.parse` to
create a new InputManager from a serialized one.

Returns:
A RawInputManager dictionary representing the current state of the InputManager.
Expand All @@ -183,8 +189,8 @@ def serialize(self) -> RawInputManager:
@classmethod
def parse(cls, raw: RawInputManager) -> InputManager:
"""
Create a new InputManager from a serialized dictionary. Can be used in combination with the :meth:`arcade.InputManager.serialize` to
save/load input configurations.
Create a new InputManager from a serialized dictionary. Can be used in combination with the
:meth:`arcade.InputManager.serialize` to save/load input configurations.

Returns:
A new InputManager with the state defined in the provided RawInputManager dictionary.
Expand Down Expand Up @@ -218,11 +224,13 @@ def parse(cls, raw: RawInputManager) -> InputManager:

def copy_existing(self, existing: InputManager):
"""
Copies the state of another InputManager into this one. Note that this does not create a new InputManager, but modifies the one on which it is called.
Copies the state of another InputManager into this one. Note that this does not create a new
InputManager, but modifies the one on which it is called.

This does not copy current input values, just the structure/mappings of the InputManager.

If you want to create a new InputManager from an existing one, use :meth:`arcade.InputManager.from_existing`
If you want to create a new InputManager from an existing one, use
:meth:`arcade.InputManager.from_existing`

Args:
existing: The InputManager to copy from.
Expand All @@ -244,13 +252,16 @@ def from_existing(
controller: pyglet.input.Controller | None = None,
) -> InputManager:
"""
Create a new InputManager from an existing one. This does not copy current input values, just the structure/mappings of the InputManager.
Create a new InputManager from an existing one. This does not copy current input values,
just the structure/mappings of the InputManager.

If you want to create a new InputManager from a serialized dictionary, use :meth:`arcade.InputManager.parse`
If you want to create a new InputManager from a serialized dictionary, use
:meth:`arcade.InputManager.parse`

Args:
existing: The InputManager to copy from.
controller: The controller to use for this InputManager. If None, no Controller will be bound.
controller: The controller to use for this InputManager. If None, no Controller will be
bound.

Returns:
A new InputManager with the state defined in the provided existing InputManager.
Expand All @@ -266,7 +277,8 @@ def from_existing(

def bind_controller(self, controller: Controller):
"""
Bind a controller to this InputManager. If a controller is already bound, it will be unbound first.
Bind a controller to this InputManager. If a controller is already bound, it will be unbound
first.

Upon binding a controller it will be set as the active device.

Expand Down Expand Up @@ -318,7 +330,8 @@ def allow_keyboard(self):
"""
Whether the keyboard is allowed for this InputManager. This also effects mouse input.

If this is false then all keyboard and mouse input will be ignored regardless of if there are mappings for them.
If this is false then all keyboard and mouse input will be ignored regardless of if there
are mappings for them.
"""
return self._allow_keyboard

Expand All @@ -343,7 +356,8 @@ def new_action(
name: str,
):
"""
Create a new action with the given name. If an action with the same name already exists, this will do nothing.
Create a new action with the given name. If an action with the same name already exists,
this will do nothing.

Args:
name: The name of the action to create.
Expand All @@ -356,7 +370,8 @@ def new_action(

def remove_action(self, name: str):
"""
Remove the specified action. If the action does not exist, this will do nothing. All registered inputs for the action will be removed.
Remove the specified action. If the action does not exist, this will do nothing. All
registered inputs for the action will be removed.
Args:
name: The name of the action to remove.
"""
Expand Down Expand Up @@ -427,8 +442,9 @@ def register_action_handler(self, handler: OneOrIterableOf[Callable[[str, Action
"""
Register a callback function for all actions from this InputManager.

The callback function should accept a String with the name of the Action, and an ActionState.
This callback will receive all action events, regardless of if :meth:`arcade.InputManager.subscribe_to_action` has been used as well.
The callback function should accept a String with the name of the Action, and an
ActionState. This callback will receive all action events, regardless of if
:meth:`arcade.InputManager.subscribe_to_action` has been used as well.

Args:
handler: The callback function to register.
Expand Down Expand Up @@ -470,7 +486,8 @@ def add_axis_input(self, axis: str, input: InputEnum, scale: float = 1.0):
Args:
axis: The axis to register the input for
input: The input to register
scale: The value to multiply the input by, for non analog inputs the scale value is used literally.
scale: The value to multiply the input by, for non analog inputs the scale value is used
literally.
"""
mapping = AxisMapping(input, scale)
self.axes[axis].add_mapping(mapping)
Expand All @@ -495,8 +512,8 @@ def add_axis_input_combined(
self, axis: str, positive: InputEnum, negative: InputEnum, scale: float = 1.0
):
"""
This is a helper function that wraps :meth:`arcade.InputManager.add_axis_input` to add two inputs
with a positive and negative scale.
This is a helper function that wraps :meth:`arcade.InputManager.add_axis_input` to add two
inputs with a positive and negative scale.

For example, you can do:
add_axis_input_combined("MoveHorizontal", arcade.Keys.RIGHT, arcade.Keys.LEFT, 1.0)
Expand All @@ -508,7 +525,8 @@ def add_axis_input_combined(
axis: The axis name to register the input for
positive: The input that will correspond to the positive side of the axis
negative: The input that will correspond to the negative side of the axis
scale: The value to multiply the input by, for non analog inputs the scale value is used literally.
scale: The value to multiply the input by, for non analog inputs the scale value is used
literally.
"""
self.add_axis_input(axis, positive, scale)
self.add_axis_input(axis, negative, -scale)
Expand All @@ -527,7 +545,8 @@ def clear_axis_input(self, axis: str):

def remove_axis(self, axis: str):
"""
Completely remove an axis from the manager. This will also clear the registered inputs for that axis.
Completely remove an axis from the manager. This will also clear the registered inputs for
that axis.

Args:
axis: The axis to remove
Expand Down Expand Up @@ -691,7 +710,8 @@ def on_righttrigger_motion(self, controller: Controller, value: float):

def update(self):
"""
Updates axis inputs, all axis values will remain unchanged unless this function is called, usually during on_update.
Updates axis inputs, all axis values will remain unchanged unless this function is called,
usually during on_update.
"""
for name in self.axes.keys():
self.axes_state[name] = 0
Expand Down
2 changes: 1 addition & 1 deletion arcade/paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
BasicSprite,
Sprite,
SpriteSequence,
has_collision_with_list,
get_sprites_at_point,
has_collision_with_list,
)
from arcade.math import get_distance, lerp_2d
from arcade.types import Point2
Expand Down
4 changes: 2 additions & 2 deletions arcade/sprite_list/sprite_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
import struct
from abc import abstractmethod
from array import array
from itertools import filterfalse
from math import hypot
from collections import deque
from collections.abc import Callable, Collection, Iterable, Iterator, Sized
from itertools import filterfalse
from math import hypot
from typing import (
TYPE_CHECKING,
Any,
Expand Down
7 changes: 4 additions & 3 deletions make.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ def _resolve(p: PathLike, strict: bool = False) -> Path:
RUFF = "ruff"
RUFFOPTS = ["check"]
RUFFOPTS_ISORT = ["check", "--select", "I"]
RUFFOPTS_PACKAGE = "arcade"
# Code checked by ruff check and import sorting
RUFF_PATHS = ["arcade", "tests"]
MYPY = "mypy"
MYPYOPTS = ["arcade"]
PYRIGHT = "pyright"
Expand Down Expand Up @@ -460,7 +461,7 @@ def lint():
@app.command(rich_help_panel="Code Quality - Advanced")
def ruff_check():
"""Run ruff check for code quality"""
run([RUFF, *RUFFOPTS, RUFFOPTS_PACKAGE])
run([RUFF, *RUFFOPTS, *RUFF_PATHS])


@app.command(rich_help_panel="Code Quality")
Expand All @@ -484,7 +485,7 @@ def ruff_isort(check: bool = False):
"""Sort imports with ruff"""
if not check:
RUFFOPTS_ISORT.append("--fix")
run([RUFF, *RUFFOPTS_ISORT, RUFFOPTS_PACKAGE])
run([RUFF, *RUFFOPTS_ISORT, *RUFF_PATHS])


@app.command(rich_help_panel="Code Quality - Advanced")
Expand Down
6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ output-format = "full"
lint.exclude = [
"venv",
".venv*",
"tests",
"build",
"doc",
"util",
Expand Down Expand Up @@ -123,6 +122,11 @@ lint.select = [
docstring-code-format = false
exclude = ["arcade/examples/*", "benchmarks/*", "doc/*"]

[tool.ruff.lint.isort]
# A pyglet checkout next to arcade would otherwise make ruff treat pyglet as
# first party, and sort its imports differently than in CI
known-third-party = ["pyglet"]

# This ignores __init__.py files and examples for import sorting
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["I"]
Expand Down
5 changes: 2 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@

arcade_accelerate.bootstrap()

import pytest
import PIL.Image
import pytest
from pyglet.math import Mat4

import arcade
from arcade import LBWH, Rect, gl
from arcade.clock import GLOBAL_CLOCK, GLOBAL_FIXED_CLOCK
from arcade import Rect, LBWH
from arcade import gl

# from arcade.texture import default_texture_cache
# NOTE: Load liberation fonts in unit tests
Expand Down
2 changes: 1 addition & 1 deletion tests/doc/check_examples_2.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import re
import os
import re
from pathlib import Path


Expand Down
4 changes: 2 additions & 2 deletions tests/integration/examples/test_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
"""

import contextlib
import io
import inspect
import io
import os
import runpy
from pathlib import Path

import arcade
import pytest

import arcade
import arcade.clock

# File path, module path
Expand Down
Loading
Loading