diff --git a/arcade/application.py b/arcade/application.py index 9c5a16a58..ca7be7197 100644 --- a/arcade/application.py +++ b/arcade/application.py @@ -8,19 +8,20 @@ import logging import os import time -from typing import TYPE_CHECKING +from typing import Sequence, TYPE_CHECKING import pyglet import pyglet.gl as gl import pyglet.window.mouse from pyglet.display.base import Screen, ScreenMode +from pyglet.event import EVENT_HANDLE_STATE, EVENT_UNHANDLED from pyglet.window import MouseCursor import arcade from arcade.clock import GLOBAL_CLOCK, GLOBAL_FIXED_CLOCK, _setup_clock, _setup_fixed_clock from arcade.color import BLACK from arcade.context import ArcadeContext -from arcade.types import LBWH, Color, Rect, RGBANormalized, RGBOrA255 +from arcade.types import Color, LBWH, RGBANormalized, RGBOrA255, Rect from arcade.utils import is_raspberry_pi from arcade.window_commands import get_display_size, set_window @@ -29,7 +30,6 @@ from arcade.camera.default import DefaultProjector from arcade.start_finish_data import StartFinishRenderData - LOG = logging.getLogger(__name__) MOUSE_BUTTON_LEFT = 1 @@ -180,7 +180,7 @@ def __init__( # Attempt to make window with antialiasing if antialiasing: try: - config = pyglet.gl.Config( + config = gl.Config( major_version=gl_version[0], minor_version=gl_version[1], opengl_api=gl_api, # type: ignore # pending: upstream fix @@ -204,7 +204,7 @@ def __init__( antialiasing = False # If we still don't have a config if not config: - config = pyglet.gl.Config( + config = gl.Config( major_version=gl_version[0], minor_version=gl_version[1], opengl_api=gl_api, # type: ignore # pending: upstream fix @@ -239,7 +239,7 @@ def __init__( if antialiasing: try: gl.glEnable(gl.GL_MULTISAMPLE_ARB) - except pyglet.gl.GLException: + except gl.GLException: LOG.warning("Warning: Anti-aliasing not supported on this computer.") _setup_clock() @@ -338,7 +338,7 @@ def ctx(self) -> ArcadeContext: """ return self._ctx - def clear( + def clear( # type: ignore # not sure what to do here, BaseWindow.clear is static self, color: RGBOrA255 | None = None, color_normalized: RGBANormalized | None = None, @@ -554,7 +554,7 @@ def set_draw_rate(self, rate: float) -> None: pyglet.clock.unschedule(pyglet.app.event_loop._redraw_windows) pyglet.clock.schedule_interval(pyglet.app.event_loop._redraw_windows, self._draw_rate) - def on_mouse_motion(self, x: int, y: int, dx: int, dy: int) -> bool | None: + def on_mouse_motion(self, x: int, y: int, dx: int, dy: int) -> EVENT_HANDLE_STATE: """ Called repeatedly while the mouse is moving in the window area. @@ -568,7 +568,7 @@ def on_mouse_motion(self, x: int, y: int, dx: int, dy: int) -> bool | None: """ pass - def on_mouse_press(self, x: int, y: int, button: int, modifiers: int) -> bool | None: + def on_mouse_press(self, x: int, y: int, button: int, modifiers: int) -> EVENT_HANDLE_STATE: """ Called once whenever a mouse button gets pressed down. @@ -596,7 +596,7 @@ def on_mouse_press(self, x: int, y: int, button: int, modifiers: int) -> bool | def on_mouse_drag( self, x: int, y: int, dx: int, dy: int, buttons: int, modifiers: int - ) -> bool | None: + ) -> EVENT_HANDLE_STATE: """ Called repeatedly while the mouse moves with a button down. @@ -619,7 +619,7 @@ def on_mouse_drag( """ return self.on_mouse_motion(x, y, dx, dy) - def on_mouse_release(self, x: int, y: int, button: int, modifiers: int) -> bool | None: + def on_mouse_release(self, x: int, y: int, button: int, modifiers: int) -> EVENT_HANDLE_STATE: """ Called once whenever a mouse button gets released. @@ -642,9 +642,11 @@ def on_mouse_release(self, x: int, y: int, button: int, modifiers: int) -> bool Bitwise 'and' of all modifiers (shift, ctrl, num lock) active during this event. See :ref:`keyboard_modifiers`. """ - return False + return EVENT_UNHANDLED - def on_mouse_scroll(self, x: int, y: int, scroll_x: int, scroll_y: int) -> bool | None: + def on_mouse_scroll( + self, x: int, y: int, scroll_x: float, scroll_y: float + ) -> EVENT_HANDLE_STATE: """ Called repeatedly while a mouse scroll wheel moves. @@ -676,7 +678,7 @@ def on_mouse_scroll(self, x: int, y: int, scroll_x: int, scroll_y: int) -> bool scroll_y: Number of steps scrolled vertically since the last call of this function """ - return False + return EVENT_UNHANDLED def set_mouse_visible(self, visible: bool = True) -> None: """ @@ -724,7 +726,7 @@ def on_action(self, action_name: str, state) -> None: """ pass - def on_key_press(self, symbol: int, modifiers: int) -> bool | None: + def on_key_press(self, symbol: int, modifiers: int) -> EVENT_HANDLE_STATE: """ Called once when a key gets pushed down. @@ -741,9 +743,9 @@ def on_key_press(self, symbol: int, modifiers: int) -> bool | None: Bitwise 'and' of all modifiers (shift, ctrl, num lock) active during this event. See :ref:`keyboard_modifiers`. """ - return False + return EVENT_UNHANDLED - def on_key_release(self, symbol: int, modifiers: int) -> bool | None: + def on_key_release(self, symbol: int, modifiers: int) -> EVENT_HANDLE_STATE: """ Called once when a key gets released. @@ -763,9 +765,9 @@ def on_key_release(self, symbol: int, modifiers: int) -> bool | None: ctrl, num lock) active during this event. See :ref:`keyboard_modifiers`. """ - return False + return EVENT_UNHANDLED - def on_draw(self) -> bool | None: + def on_draw(self) -> EVENT_HANDLE_STATE: """ Override this function to add your custom drawing code. @@ -781,9 +783,9 @@ def on_draw(self) -> bool | None: self._start_finish_render_data.draw() return True - return False + return EVENT_UNHANDLED - def _on_resize(self, width: int, height: int) -> bool | None: + def _on_resize(self, width: int, height: int) -> EVENT_HANDLE_STATE: """ The internal method called when the window is resized. @@ -799,9 +801,9 @@ def _on_resize(self, width: int, height: int) -> bool | None: # Retain viewport self.viewport = (0, 0, width, height) - return False + return EVENT_UNHANDLED - def on_resize(self, width: int, height: int) -> bool | None: + def on_resize(self, width: int, height: int) -> EVENT_HANDLE_STATE: """ Override this method to add custom actions when the window is resized. @@ -855,7 +857,7 @@ def get_size(self) -> tuple[int, int]: def get_location(self) -> tuple[int, int]: """Get the current X/Y coordinates of the window.""" - return super().get_location() + return super().get_location() # type: ignore # Window typed at runtime def set_visible(self, visible: bool = True): """ @@ -1038,34 +1040,34 @@ def flip(self) -> None: num_collected = self.ctx.gc() LOG.debug("Garbage collected %s OpenGL resource(s)", num_collected) - super().flip() + super().flip() # type: ignore # Window typed at runtime def switch_to(self) -> None: """Switch the this window context. This is normally only used in multi-window applications. """ - super().switch_to() + super().switch_to() # type: ignore # Window typed at runtime def set_caption(self, caption) -> None: """Set the caption/title of the window.""" - super().set_caption(caption) + super().set_caption(caption) # type: ignore # Window typed at runtime def set_location(self, x, y) -> None: """Set location of the window.""" - super().set_location(x, y) + super().set_location(x, y) # type: ignore # Window typed at runtime def activate(self) -> None: """Activate this window.""" - super().activate() + super().activate() # type: ignore # Window typed at runtime def minimize(self) -> None: """Minimize the window.""" - super().minimize() + super().minimize() # type: ignore # Window typed at runtime def maximize(self) -> None: """Maximize the window.""" - super().maximize() + super().maximize() # type: ignore # Window typed at runtime def set_vsync(self, vsync: bool) -> None: """Set if we sync our draws to the monitors vertical sync rate.""" @@ -1097,9 +1099,9 @@ def get_system_mouse_cursor(self, name) -> MouseCursor: def dispatch_events(self) -> None: """Dispatch events""" - super().dispatch_events() + super().dispatch_events() # type: ignore # Window typed at runtime - def on_mouse_enter(self, x: int, y: int) -> bool | None: + def on_mouse_enter(self, x: int, y: int) -> EVENT_HANDLE_STATE: """ Called once whenever the mouse enters the window area on screen. @@ -1112,7 +1114,7 @@ def on_mouse_enter(self, x: int, y: int) -> bool | None: """ pass - def on_mouse_leave(self, x: int, y: int) -> bool | None: + def on_mouse_leave(self, x: int, y: int) -> EVENT_HANDLE_STATE: """ Called once whenever the mouse leaves the window area on screen. @@ -1183,6 +1185,15 @@ def fixed_delta_time(self) -> float: """The configured fixed update rate""" return self._fixed_rate + # required because pyglet marks the method as abstract methods, + # but resolves class during runtime + def _create(self) -> None: + """Internal method to create the window.""" + super()._create() # type: ignore + + def _recreate(self, changes: Sequence[str]) -> None: + super()._recreate(changes) # type: ignore + def open_window( width: int, diff --git a/arcade/camera/camera_2d.py b/arcade/camera/camera_2d.py index 5f4c3319f..47b177479 100644 --- a/arcade/camera/camera_2d.py +++ b/arcade/camera/camera_2d.py @@ -2,15 +2,15 @@ from contextlib import contextmanager from math import atan2, cos, degrees, radians, sin -from typing import TYPE_CHECKING, Generator +from typing import Generator, TYPE_CHECKING from pyglet.math import Vec2, Vec3 from typing_extensions import Self from arcade.camera.data_types import ( + CameraData, DEFAULT_FAR, DEFAULT_NEAR_ORTHO, - CameraData, OrthographicProjectionData, ZeroProjectionDimension, ) @@ -20,7 +20,7 @@ project_orthographic, unproject_orthographic, ) -from arcade.types import LBWH, LRBT, XYWH, Point, Rect +from arcade.types import LBWH, LRBT, Point, Rect, XYWH from arcade.types.vector_like import Point2 from arcade.window_commands import get_window @@ -544,10 +544,12 @@ def position(self) -> Vec2: """The 2D world position of the camera along the X and Y axes.""" return Vec2(self._camera_data.position[0], self._camera_data.position[1]) + # Setter with different signature will cause mypy issues + # https://github.com/python/mypy/issues/3004 @position.setter def position(self, _pos: Point) -> None: - x, y, *z = _pos - z = self._camera_data.position[2] if not z else z[0] + x, y, *_z = _pos + z = self._camera_data.position[2] if not _z else _z[0] self._camera_data.position = (x, y, z) @property @@ -900,7 +902,7 @@ def top_left(self, new_corner: Point2): left = self.left x, y = new_corner - self.position = (x - ux * top - rx * left, y - uy * top - ry * left) + self.position = (x - ux * top - rx * left, y - uy * top - ry * left) # type: ignore # top_center @property @@ -918,7 +920,7 @@ def top_center(self, new_top: Point2): top = self.top x, y = new_top - self.position = x - ux * top, y - uy * top + self.position = x - ux * top, y - uy * top # type: ignore # top_right @property @@ -942,7 +944,7 @@ def top_right(self, new_corner: Point2): right = self.right x, y = new_corner - self.position = (x - ux * top - rx * right, y - uy * top - ry * right) + self.position = (x - ux * top - rx * right, y - uy * top - ry * right) # type: ignore # center_right @property @@ -959,7 +961,7 @@ def center_right(self, new_right: Point2): right = self.right x, y = new_right - self.position = x - uy * right, y + ux * right + self.position = x - uy * right, y + ux * right # type: ignore # bottom_right @property @@ -985,7 +987,7 @@ def bottom_right(self, new_corner: Point2): self.position = ( x - ux * bottom - rx * right, y - uy * bottom - ry * right, - ) + ) # type: ignore # bottom_center @property @@ -995,7 +997,7 @@ def bottom_center(self) -> Vec2: ux, uy, *_ = self._camera_data.up bottom = self.bottom - return Vec2(pos.x + ux * bottom, pos.y + uy * bottom) + return pos.x + ux * bottom, pos.y + uy * bottom # type: ignore @bottom_center.setter def bottom_center(self, new_bottom: Point2): @@ -1003,7 +1005,7 @@ def bottom_center(self, new_bottom: Point2): bottom = self.bottom x, y = new_bottom - self.position = x - ux * bottom, y - uy * bottom + self.position = x - ux * bottom, y - uy * bottom # type: ignore # bottom_left @property @@ -1027,7 +1029,7 @@ def bottom_left(self, new_corner: Point2): left = self.left x, y = new_corner - self.position = (x - ux * bottom - rx * left, y - uy * bottom - ry * left) + self.position = x - ux * bottom - rx * left, y - uy * bottom - ry * left # type: ignore # center_left @property @@ -1044,4 +1046,4 @@ def center_left(self, new_left: Point2): left = self.left x, y = new_left - self.position = x - uy * left, y + ux * left + self.position = Vec2(x - uy * left, y + ux * left) diff --git a/arcade/camera/default.py b/arcade/camera/default.py index 2ef4a7744..0ba9044b3 100644 --- a/arcade/camera/default.py +++ b/arcade/camera/default.py @@ -92,8 +92,8 @@ def unproject(self, screen_coordinate: Point) -> Vec3: Due to the nature of viewport projector this does not do anything. """ - x, y, *z = screen_coordinate - z = 0.0 if not z else z[0] + x, y, *_z = screen_coordinate + z = 0.0 if not _z else _z[0] return Vec3(x, y, z) diff --git a/arcade/camera/perspective.py b/arcade/camera/perspective.py index b65b86cbf..1c51dfa05 100644 --- a/arcade/camera/perspective.py +++ b/arcade/camera/perspective.py @@ -182,15 +182,15 @@ def project(self, world_coordinate: Point) -> Vec2: Returns: A 2D screen pixel coordinate. """ - x, y, *z = world_coordinate + x, y, *_z = world_coordinate z = ( ( 0.5 * self.viewport.height / tan(radians(0.5 * self._projection.fov / self._view.zoom)) ) - if not z - else z[0] + if not _z + else _z[0] ) _projection = generate_perspective_matrix(self._projection, self._view.zoom) @@ -214,15 +214,15 @@ def unproject(self, screen_coordinate: Point) -> Vec3: Returns: A 3D vector in world space. """ - x, y, *z = screen_coordinate + x, y, *_z = screen_coordinate z = ( ( 0.5 * self.viewport.height / tan(radians(0.5 * self._projection.fov / self._view.zoom)) ) - if not z - else z[0] + if not _z + else _z[0] ) _projection = generate_perspective_matrix(self._projection, self._view.zoom) diff --git a/arcade/camera/projection_functions.py b/arcade/camera/projection_functions.py index 5a96b9f31..2745fb90f 100644 --- a/arcade/camera/projection_functions.py +++ b/arcade/camera/projection_functions.py @@ -125,8 +125,8 @@ def project_orthographic( view_matrix: Mat4, projection_matrix: Mat4, ) -> Vec2: - x, y, *z = world_coordinate - z = 0.0 if not z else z[0] + x, y, *_z = world_coordinate + z = 0.0 if not _z else _z[0] world_position = Vec4(x, y, z, 1.0) @@ -144,8 +144,8 @@ def unproject_orthographic( view_matrix: Mat4, projection_matrix: Mat4, ) -> Vec3: - x, y, *z = screen_coordinate - z = 0.0 if not z else z[0] + x, y, *_z = screen_coordinate + z = 0.0 if not _z else _z[0] screen_x = 2.0 * (x - viewport[0]) / viewport[2] - 1 screen_y = 2.0 * (y - viewport[1]) / viewport[3] - 1 @@ -165,8 +165,8 @@ def project_perspective( view_matrix: Mat4, projection_matrix: Mat4, ) -> Vec2: - x, y, *z = world_coordinate - z = 1.0 if not z else z[0] + x, y, *_z = world_coordinate + z = 1.0 if not _z else _z[0] world_position = Vec4(x, y, z, 1.0) @@ -188,8 +188,8 @@ def unproject_perspective( view_matrix: Mat4, projection_matrix: Mat4, ) -> Vec3: - x, y, *z = screen_coordinate - z = 1.0 if not z else z[0] + x, y, *_z = screen_coordinate + z = 1.0 if not _z else _z[0] screen_x = 2.0 * (x - viewport[0]) / viewport[2] - 1 screen_y = 2.0 * (y - viewport[1]) / viewport[3] - 1 diff --git a/arcade/examples/gl/3d_sphere.py b/arcade/examples/gl/3d_sphere.py index 1b155f758..249bdcf39 100644 --- a/arcade/examples/gl/3d_sphere.py +++ b/arcade/examples/gl/3d_sphere.py @@ -16,7 +16,6 @@ class Sphere3D(arcade.Window): - def __init__(self, width, height, title): super().__init__(width, height, title, resizable=True) self.sphere = geometry.sphere(1.0, 32, 32, uvs=False) @@ -69,20 +68,36 @@ def __init__(self, width, height, title): self.text_batch = Batch() self.text_cull = arcade.Text( - "F2: Toggle cull face (True)", x=10, y=10, font_size=15, color=arcade.color.WHITE, - batch=self.text_batch + "F2: Toggle cull face (True)", + x=10, + y=10, + font_size=15, + color=arcade.color.WHITE, + batch=self.text_batch, ) self.text_depth = arcade.Text( - "F1: Toggle depth test (True)", x=10, y=30, font_size=15, color=arcade.color.WHITE, - batch=self.text_batch + "F1: Toggle depth test (True)", + x=10, + y=30, + font_size=15, + color=arcade.color.WHITE, + batch=self.text_batch, ) self.text_wireframe = arcade.Text( - "SPACE: Toggle wireframe (False)", x=10, y=50, font_size=15, color=arcade.color.WHITE, - batch=self.text_batch + "SPACE: Toggle wireframe (False)", + x=10, + y=50, + font_size=15, + color=arcade.color.WHITE, + batch=self.text_batch, ) self.text_fs = arcade.Text( - "F: Toggle fullscreen (False)", x=10, y=70, font_size=15, color=arcade.color.WHITE, - batch=self.text_batch + "F: Toggle fullscreen (False)", + x=10, + y=70, + font_size=15, + color=arcade.color.WHITE, + batch=self.text_batch, ) self.text_vert_count = arcade.Text( "Use mouse wheel to add/remove vertices", @@ -169,7 +184,7 @@ def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers): def on_mouse_release(self, x, y, button, modifiers): self.drag_time = None - def on_mouse_scroll(self, x: int, y: int, scroll_x: int, scroll_y: int): + def on_mouse_scroll(self, x: int, y: int, scroll_x: float, scroll_y: float): self.vert_count = clamp(self.vert_count + scroll_y / 500, 0.0, 1.0) diff --git a/arcade/examples/gl/normal_mapping_simple.py b/arcade/examples/gl/normal_mapping_simple.py index 93f6d678e..3503cd69d 100644 --- a/arcade/examples/gl/normal_mapping_simple.py +++ b/arcade/examples/gl/normal_mapping_simple.py @@ -21,7 +21,6 @@ class NormalMapping(arcade.Window): - def __init__(self): super().__init__(512, 512, "Normal Mapping") @@ -130,7 +129,7 @@ def on_mouse_motion(self, x: int, y: int, dx: int, dy: int): # (0.0, 0.0) is bottom left, (1.0, 1.0) is top right self.mouse_x, self.mouse_y = x / self.width, y / self.height - def on_mouse_scroll(self, x: int, y: int, scroll_x: int, scroll_y: int): + def on_mouse_scroll(self, x: int, y: int, scroll_x: float, scroll_y: float): """Zoom in/out with the mouse wheel.""" self.mouse_z += scroll_y * 0.05 diff --git a/arcade/examples/gui/own_layout.py b/arcade/examples/gui/own_layout.py index ee3797498..8550e4389 100644 --- a/arcade/examples/gui/own_layout.py +++ b/arcade/examples/gui/own_layout.py @@ -11,10 +11,13 @@ from __future__ import annotations from math import cos, sin +from typing import TypeVar import arcade from arcade.gui import UIAnchorLayout, UIFlatButton, UILayout, UIView, UIWidget +W = TypeVar("W", bound="UIWidget") + class CircleLayout(UILayout): """A custom progress bar widget. @@ -27,7 +30,7 @@ def __init__(self, size_hint=(1, 1), **kwargs): super().__init__(size_hint=size_hint, **kwargs) self._time = 0 # used for rotation - def add(self, child: UIWidget, **kwargs) -> UIWidget: + def add(self, child: W, **kwargs) -> W: """Add a widget to the layout. The widget is placed in a circle around the center of the screen. diff --git a/arcade/experimental/crt_filter.py b/arcade/experimental/crt_filter.py index a26443453..10667a414 100644 --- a/arcade/experimental/crt_filter.py +++ b/arcade/experimental/crt_filter.py @@ -25,7 +25,7 @@ def __init__( resolution_down_scale: float = 6.0, hard_scan: float = -8.0, hard_pix: float = -3.0, - display_warp: Vec2 = (1.0 / 32.0, 1.0 / 24.0), + display_warp: Vec2 = Vec2(1.0 / 32.0, 1.0 / 24.0), mask_dark: float = 0.5, mask_light: float = 1.5, ): diff --git a/arcade/gl/framebuffer.py b/arcade/gl/framebuffer.py index 982ba667a..b62027d9f 100644 --- a/arcade/gl/framebuffer.py +++ b/arcade/gl/framebuffer.py @@ -2,13 +2,12 @@ import weakref from contextlib import contextmanager -from ctypes import c_int, string_at -from typing import TYPE_CHECKING, Generator +from ctypes import Array, c_int, c_uint, string_at +from typing import Generator, TYPE_CHECKING from pyglet import gl from arcade.types import RGBOrA255, RGBOrANormalized - from .texture import Texture2D from .types import pixel_formats @@ -124,7 +123,7 @@ def __init__( # we use in the use() method to activate the different color attachment layers layers = [gl.GL_COLOR_ATTACHMENT0 + i for i, _ in enumerate(self._color_attachments)] # pyglet wants this as a ctypes thingy, so let's prepare it - self._draw_buffers = (gl.GLuint * len(layers))(*layers) + self._draw_buffers: Array[c_uint] | None = (gl.GLuint * len(layers))(*layers) # Restore the original bound framebuffer to avoid confusion self.ctx.active_framebuffer.use(force=True) diff --git a/arcade/gui/ui_manager.py b/arcade/gui/ui_manager.py index 4b047fb68..4f36c6cce 100644 --- a/arcade/gui/ui_manager.py +++ b/arcade/gui/ui_manager.py @@ -169,7 +169,7 @@ def clear(self): self.remove(widget) def get_widgets_at( - self, pos: Point2, cls: type[W] = UIWidget, layer=DEFAULT_LAYER + self, pos: Point2, cls: type[W] | type[UIWidget] = UIWidget, layer=DEFAULT_LAYER ) -> Iterable[W]: """Yields all widgets containing a position, returns first top laying widgets which is instance of cls. diff --git a/arcade/perf_info.py b/arcade/perf_info.py index c25451f1b..efb1b67e3 100644 --- a/arcade/perf_info.py +++ b/arcade/perf_info.py @@ -151,7 +151,7 @@ def enable_timings(max_history: int = 100) -> None: _pyglets_dispatch_event = pyglet.window.BaseWindow.dispatch_event # Override the pyglet dispatch event function - pyglet.window.BaseWindow.dispatch_event = _dispatch_event + pyglet.window.BaseWindow.dispatch_event = _dispatch_event # type: ignore _max_history = max_history diff --git a/arcade/sound.py b/arcade/sound.py index 3cf687e81..8b60d45c3 100644 --- a/arcade/sound.py +++ b/arcade/sound.py @@ -143,7 +143,7 @@ def _on_player_eos(): # we need to delete this function. player.on_player_eos = None # type: ignore # pending https://github.com/pyglet/pyglet/issues/845 - player.on_player_eos = _on_player_eos + player.on_player_eos = _on_player_eos # type: ignore return player def stop(self, player: media.Player) -> None: diff --git a/arcade/sprite/base.py b/arcade/sprite/base.py index 614d0ae1d..46dfcca15 100644 --- a/arcade/sprite/base.py +++ b/arcade/sprite/base.py @@ -7,7 +7,7 @@ from arcade.exceptions import ReplacementWarning, warning from arcade.hitbox import HitBox from arcade.texture import Texture -from arcade.types import LRBT, RGBA255, AsFloat, Color, Point, Point2, PointList, Rect, RGBOrA255 +from arcade.types import LRBT, Point2List, RGBA255, AsFloat, Color, Point, Point2, Rect, RGBOrA255 from arcade.utils import copy_dunders_unimplemented if TYPE_CHECKING: @@ -437,7 +437,6 @@ def rgb(self) -> tuple[int, int, int]: @rgb.setter def rgb(self, color: RGBOrA255): - # Fast validation of size by unpacking channel values try: r, g, b, *_a = color @@ -775,7 +774,7 @@ def draw_hit_box(self, color: RGBA255 = BLACK, line_thickness: float = 2.0) -> N line_thickness: How thick the box should be """ - points: PointList = self.hit_box.get_adjusted_points() + points: Point2List = self.hit_box.get_adjusted_points() # NOTE: This is a COPY operation. We don't want to modify the points. points = tuple(points) + tuple(points[:-1]) arcade.draw_line_strip(points, color=color, line_width=line_thickness) diff --git a/arcade/sprite/sprite.py b/arcade/sprite/sprite.py index 59b9e6b1f..3e6b120ff 100644 --- a/arcade/sprite/sprite.py +++ b/arcade/sprite/sprite.py @@ -150,8 +150,8 @@ def __init__( self._hit_box: RotatableHitBox = self._hit_box.create_rotatable(angle=self._angle) - self._width = self._texture.width * scale - self._height = self._texture.height * scale + self._width = self._texture.width * self.scale[0] + self._height = self._texture.height * self.scale[1] # --- Properties --- diff --git a/arcade/types/rect.py b/arcade/types/rect.py index 8ee9fe08c..787c4bc82 100644 --- a/arcade/types/rect.py +++ b/arcade/types/rect.py @@ -3,12 +3,13 @@ from __future__ import annotations import math -from typing import NamedTuple, TypedDict +from typing import Any, NamedTuple, TypedDict from pyglet.math import Vec2 from arcade.types.numbers import AsFloat from arcade.types.vector_like import AnchorPoint, Point2 +from arcade.utils import is_iterable RectParams = tuple[AsFloat, AsFloat, AsFloat, AsFloat] ViewportParams = tuple[int, int, int, int] @@ -462,12 +463,15 @@ def point_in_bounds(self, point: Point2) -> bool: px, py = point return (self.left < px < self.right) and (self.bottom < py < self.top) - def __contains__(self, point: Point2) -> bool: + def __contains__(self, point: Point2 | Any) -> bool: """Shorthand for :py:meth:`rect.point_in_rect(point) <point_in_rect>`. Args: point: A tuple of :py:class:`int` or :py:class:`float` values. """ + if not is_iterable(point): + return False + return self.point_in_rect(point) def distance_from_bounds(self, point: Point2) -> float: @@ -487,6 +491,7 @@ def point_on_bounds(self, point: Point2, tolerance: float) -> bool: Args: point: The point to check. + tolerance: The maximum distance the point can be from the bounds. """ return abs(self.distance_from_bounds(point)) < tolerance diff --git a/arcade/types/vector_like.py b/arcade/types/vector_like.py index fe31da458..e09439b37 100644 --- a/arcade/types/vector_like.py +++ b/arcade/types/vector_like.py @@ -9,14 +9,14 @@ from __future__ import annotations -from typing import Sequence, Union +from typing import Sequence, Tuple, Union from pyglet.math import Vec2, Vec3 from arcade.types.numbers import AsFloat #: Matches both :py:class:`~pyglet.math.Vec2` and tuples of two numbers. -Point2 = Union[tuple[AsFloat, AsFloat], Vec2] +Point2 = Union[Tuple[AsFloat, AsFloat], Vec2] #: Matches both :py:class:`~pyglet.math.Vec3` and tuples of three numbers. Point3 = Union[tuple[AsFloat, AsFloat, AsFloat], Vec3]