From fac3aebe6dba02992767a28eaf35b294cdee38bc Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Tue, 2 Jul 2024 03:47:56 -0400 Subject: [PATCH 01/18] Bump to pyglet == 2.1dev3 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 404087a7fa..7956e68381 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", ] dependencies = [ - "pyglet == 2.1dev2", + "pyglet == 2.1dev3", "pillow~=10.2.0", "pymunk~=6.6.0", "pytiled-parser~=2.2.5", From 11f7f4be53b039fedb86d367a65f70b400a0eeef Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Tue, 2 Jul 2024 03:50:04 -0400 Subject: [PATCH 02/18] Remove del trick for getting future vec behavior --- arcade/__init__.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/arcade/__init__.py b/arcade/__init__.py index 3fd5a932d7..2c1709d514 100644 --- a/arcade/__init__.py +++ b/arcade/__init__.py @@ -58,17 +58,6 @@ def configure_logging(level: Optional[int] = None): # noinspection PyPep8 import pyglet -# TODO: Remove ASAP after pyglet >= 2.1dev2 is out -if pyglet.version == "2.1.dev2": - # Temporary monkeypatch via deletion since dev2 still includes - # overly-specific __eq__ behavior. Later pyglet commits restore - # equality with same-valued tuples by deleting the __eq__ methods. - from pyglet import math as _pyglet_math - - del _pyglet_math.Vec2.__eq__ - del _pyglet_math.Vec3.__eq__ - del _pyglet_math.Vec4.__eq__ - # Env variable shortcut for headless mode if os.environ.get("ARCADE_HEADLESS"): pyglet.options["headless"] = True From 22606138e272d558aaf168b35509e22b15165df4 Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Tue, 2 Jul 2024 03:55:31 -0400 Subject: [PATCH 03/18] Update to options object --- arcade/sound.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arcade/sound.py b/arcade/sound.py index 977217bc26..f088bf3996 100644 --- a/arcade/sound.py +++ b/arcade/sound.py @@ -15,11 +15,11 @@ from arcade.resources import resolve if os.environ.get("ARCADE_SOUND_BACKENDS"): - pyglet.options["audio"] = tuple( + pyglet.options.audio = tuple( v.strip() for v in os.environ["ARCADE_SOUND_BACKENDS"].split(",") ) else: - pyglet.options["audio"] = ("openal", "xaudio2", "directsound", "pulse", "silent") + pyglet.options.audio = ("openal", "xaudio2", "directsound", "pulse", "silent") import pyglet.media as media From 1e8e3af881cc4afd96fa8d8e186ccc9de8bc9e77 Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Tue, 2 Jul 2024 05:24:40 -0400 Subject: [PATCH 04/18] Switch arcade/__init__.py to use OOP options object --- arcade/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arcade/__init__.py b/arcade/__init__.py index 2c1709d514..efd1054d2c 100644 --- a/arcade/__init__.py +++ b/arcade/__init__.py @@ -60,13 +60,13 @@ def configure_logging(level: Optional[int] = None): # Env variable shortcut for headless mode if os.environ.get("ARCADE_HEADLESS"): - pyglet.options["headless"] = True + pyglet.options.headless = True from arcade import utils # Disable shadow window on macs and in headless mode. if sys.platform == "darwin" or os.environ.get("ARCADE_HEADLESS") or utils.is_raspberry_pi(): - pyglet.options["shadow_window"] = False + pyglet.options.shadow_window = False # Use the old gdi fonts on windows until directwrite is fast/stable # pyglet.options['win32_gdi_font'] = True @@ -141,7 +141,7 @@ def configure_logging(level: Optional[int] = None): from .screenshot import get_pixel # We don't have joysticks game controllers in headless mode -if not pyglet.options["headless"]: +if not pyglet.options.headless: from .joysticks import get_game_controllers from .joysticks import get_joysticks from .controller import ControllerManager @@ -409,7 +409,7 @@ def configure_logging(level: Optional[int] = None): load_font(":system:fonts/ttf/Kenney_Rocket_Square.ttf") # Load additional game controller mappings to Pyglet - if not pyglet.options["headless"]: + if not pyglet.options.headless: try: import pyglet.input.controller From 93aee70853ccbfe82bc5669c08d7bbaa333c1a22 Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Tue, 2 Jul 2024 05:29:19 -0400 Subject: [PATCH 05/18] Convert applications.py to OOP pyglet options --- arcade/application.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arcade/application.py b/arcade/application.py index 065c730494..5f7c70195a 100644 --- a/arcade/application.py +++ b/arcade/application.py @@ -142,7 +142,7 @@ def __init__( gl_api = "gles" #: Whether this is a headless window - self.headless: bool = pyglet.options.get("headless") is True + self.headless: bool = pyglet.options.headless is True config = None # Attempt to make window with antialiasing @@ -248,7 +248,7 @@ def __init__( if enable_polling: self.keyboard = pyglet.window.key.KeyStateHandler() - if pyglet.options["headless"]: + if pyglet.options.headless: self.push_handlers(self.keyboard) else: From f37793dbbe9221322b461eb557828e79aa8443ab Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Tue, 2 Jul 2024 05:32:45 -0400 Subject: [PATCH 06/18] Convert doc on headless to use options object --- doc/programming_guide/headless.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/programming_guide/headless.rst b/doc/programming_guide/headless.rst index 485d9e6786..4824cb1768 100644 --- a/doc/programming_guide/headless.rst +++ b/doc/programming_guide/headless.rst @@ -34,7 +34,7 @@ This can be done in the following ways: # The above is a shortcut for import pyglet - pyglet.options["headless"] = True + pyglet.options.headless = True This of course also means you can configure headless externally. @@ -182,10 +182,10 @@ to a physical device (graphics card) or a virtual card/device. .. code:: py # Default setting - pyglet.options['headless_device'] = 0 + pyglet.options.headless_device = 0 # Use the second gpu/device - pyglet.options['headless_device'] = 1 + pyglet.options.headless_device = 1 Issues? ------- From 5c06a0658e0203ac801577fc10412649e44c3093 Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Tue, 2 Jul 2024 05:34:08 -0400 Subject: [PATCH 07/18] Fix a comment in arcade/__init__.py --- arcade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arcade/__init__.py b/arcade/__init__.py index efd1054d2c..27c1248389 100644 --- a/arcade/__init__.py +++ b/arcade/__init__.py @@ -69,7 +69,7 @@ def configure_logging(level: Optional[int] = None): pyglet.options.shadow_window = False # Use the old gdi fonts on windows until directwrite is fast/stable -# pyglet.options['win32_gdi_font'] = True +# pyglet.options.win32_gdi_font = True # Imports from modules that don't do anything circular From e3c64db162e9c5f8f413c49379e443f2d460fba4 Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Tue, 2 Jul 2024 05:34:46 -0400 Subject: [PATCH 08/18] Update tests/__init__.py --- tests/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/__init__.py b/tests/__init__.py index ca0a59529b..40bd75a955 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -3,4 +3,4 @@ # Headless mode if os.environ.get("ARCADE_HEADLESS_TEST"): import pyglet - pyglet.options["headless"] = True + pyglet.options.headless = True From d8041f80ca9932f289b32bcc9e6d60c32b4440cf Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Tue, 2 Jul 2024 06:28:23 -0400 Subject: [PATCH 09/18] Fix formatting --- arcade/sound.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/arcade/sound.py b/arcade/sound.py index f088bf3996..f7606d527a 100644 --- a/arcade/sound.py +++ b/arcade/sound.py @@ -15,9 +15,7 @@ from arcade.resources import resolve if os.environ.get("ARCADE_SOUND_BACKENDS"): - pyglet.options.audio = tuple( - v.strip() for v in os.environ["ARCADE_SOUND_BACKENDS"].split(",") - ) + pyglet.options.audio = tuple(v.strip() for v in os.environ["ARCADE_SOUND_BACKENDS"].split(",")) else: pyglet.options.audio = ("openal", "xaudio2", "directsound", "pulse", "silent") From 33c9c874427724d0bb5f6cdd5f4fd27f5322523e Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Tue, 2 Jul 2024 06:36:25 -0400 Subject: [PATCH 10/18] Type compatibility fix in controller db loading --- arcade/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arcade/__init__.py b/arcade/__init__.py index 27c1248389..006f858746 100644 --- a/arcade/__init__.py +++ b/arcade/__init__.py @@ -414,6 +414,7 @@ def configure_logging(level: Optional[int] = None): import pyglet.input.controller mappings_file = resources.resolve(":system:gamecontrollerdb.txt") - pyglet.input.controller.add_mappings_from_file(mappings_file) + # TODO: remove string conversion once fixed upstream + pyglet.input.controller.add_mappings_from_file(str(mappings_file)) except AssertionError: pass From 96827d2839dd581dd0d25c7c0b6115ec5704eeb7 Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Tue, 2 Jul 2024 07:36:38 -0400 Subject: [PATCH 11/18] Use pyglet.media.Source instead of a Union type --- arcade/sound.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arcade/sound.py b/arcade/sound.py index f7606d527a..554badf140 100644 --- a/arcade/sound.py +++ b/arcade/sound.py @@ -11,6 +11,7 @@ from typing import Optional, Union import pyglet +from pyglet.media import Source from arcade.resources import resolve @@ -37,7 +38,7 @@ def __init__(self, file_name: Union[str, Path], streaming: bool = False): raise FileNotFoundError(f"The sound file '{file_name}' is not a file or can't be read.") self.file_name = str(file_name) - self.source: Union[media.StaticSource, media.StreamingSource] = media.load( + self.source: Source = media.load( self.file_name, streaming=streaming ) From 6e9aa4dbad9608d8e6aa325a581607963926e0e3 Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Sun, 7 Jul 2024 10:06:17 -0400 Subject: [PATCH 12/18] Fix color typing in arcade.Text --- arcade/text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arcade/text.py b/arcade/text.py index a90fbbbe22..605b28a5a1 100644 --- a/arcade/text.py +++ b/arcade/text.py @@ -385,7 +385,7 @@ def color(self) -> Color: """ Get or set the text color for the label """ - return self._label.color + return Color.from_iterable(self._label.color) @color.setter def color(self, color: RGBOrA255): From 1faa9feb0f3ec264bf95afc2495bfbaaea886df0 Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Sun, 7 Jul 2024 10:16:29 -0400 Subject: [PATCH 13/18] Ignore over-strict pyright Literal opinion --- arcade/text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arcade/text.py b/arcade/text.py index 605b28a5a1..e44d2292ba 100644 --- a/arcade/text.py +++ b/arcade/text.py @@ -216,7 +216,7 @@ def __init__( anchor_y=anchor_y, # type: ignore color=Color.from_iterable(color), width=width, - align=align, + align=align, # type: ignore bold=bold, italic=italic, multiline=multiline, From db65d560637a7619c30c3445d16f3d984824a1ad Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Sun, 7 Jul 2024 10:32:31 -0400 Subject: [PATCH 14/18] Attempt to patch up the font loader --- arcade/text.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/arcade/text.py b/arcade/text.py index e44d2292ba..9aa345294c 100644 --- a/arcade/text.py +++ b/arcade/text.py @@ -45,9 +45,8 @@ def load_font(path: Union[str, Path]) -> None: FontNameOrNames = Union[str, tuple[str, ...]] -def _attempt_font_name_resolution(font_name: FontNameOrNames) -> FontNameOrNames: - """ - Attempt to resolve a tuple of font names. +def _attempt_font_name_resolution(font_name: FontNameOrNames) -> str: + """Attempt to resolve a font name. Preserves the original logic of this section, even though it doesn't seem to make sense entirely. Comments are an attempt @@ -82,8 +81,12 @@ def _attempt_font_name_resolution(font_name: FontNameOrNames) -> FontNameOrNames except FileNotFoundError: pass - # failed to find it ourselves, hope pyglet can make sense of it - return font_name + # failed to find it ourselves, hope pyglet can make sense of it + # Note this is the best approximation of what I unerstand the old + # behavior to have been. + return pyglet.font.load(font_list).name + + raise ValueError(f"Couldn't find a font for {font_name!r}") def _draw_pyglet_label(label: pyglet.text.Label) -> None: @@ -203,6 +206,7 @@ def __init__( ) adjusted_font = _attempt_font_name_resolution(font_name) + self._label = pyglet.text.Label( text=text, # pyglet is lying about what it takes here and float is entirely valid From 2c7d4ac8de5ed578d84af2e2e5c379eb8a8955e7 Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Sun, 7 Jul 2024 10:43:01 -0400 Subject: [PATCH 15/18] Use new-style | None in application.Window.__init__ --- arcade/application.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arcade/application.py b/arcade/application.py index 5f7c70195a..8fdd513586 100644 --- a/arcade/application.py +++ b/arcade/application.py @@ -112,14 +112,14 @@ def __init__( self, width: int = 1280, height: int = 720, - title: Optional[str] = "Arcade Window", + title: str | None = "Arcade Window", fullscreen: bool = False, resizable: bool = False, update_rate: float = 1 / 60, antialiasing: bool = True, gl_version: tuple[int, int] = (3, 3), - screen: Optional[pyglet.display.Screen] = None, - style: Optional[str] = pyglet.window.Window.WINDOW_STYLE_DEFAULT, + screen: pyglet.display.Screen | None = None, + style: str | None = pyglet.window.Window.WINDOW_STYLE_DEFAULT, visible: bool = True, vsync: bool = False, gc_mode: str = "context_gc", @@ -129,7 +129,7 @@ def __init__( gl_api: str = "gl", draw_rate: float = 1 / 60, fixed_rate: float = 1.0 / 60.0, - fixed_frame_cap: Optional[int] = None, + fixed_frame_cap: int | None = None, ) -> None: # In certain environments we can't have antialiasing/MSAA enabled. # Detect replit environment From 4baacd3e04fb83c57cf5293fb6d3972d1a313baf Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Sun, 7 Jul 2024 10:51:06 -0400 Subject: [PATCH 16/18] Update some typing for set_fullscreen * Use new-style | None * Add casting and TODO on resolving screen coord issues upstream * Mention rounding issue in set_fullscreen --- arcade/application.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/arcade/application.py b/arcade/application.py index 8fdd513586..eeba095c05 100644 --- a/arcade/application.py +++ b/arcade/application.py @@ -365,10 +365,10 @@ def close(self) -> None: def set_fullscreen( self, fullscreen: bool = True, - screen: Optional["Window"] = None, - mode: Optional[ScreenMode] = None, - width: Optional[float] = None, - height: Optional[float] = None, + screen: pyglet.window.Window | None = None, + mode: ScreenMode | None = None, + width: float | None = None, + height: float | None = None, ) -> None: """ Set if we are full screen or not. @@ -380,10 +380,18 @@ def set_fullscreen( have been obtained by enumerating `Screen.get_modes`. If None, an appropriate mode will be selected from the given `width` and `height`. - :param width: - :param height: - """ - super().set_fullscreen(fullscreen, screen, mode, width, height) + :param width: Although marked as py:class:`float`, will be + rounded via :py:class:`int` if not ``None``. + :param height: Although marked as py:class:`float`, will be + rounded via :py:class:`int` if not ``None``. + """ + # fmt: off + super().set_fullscreen( + fullscreen, screen, mode, + # TODO: resolve the upstream int / float screen coord issue + None if width is None else int(width), + None if height is None else int(height)) + # fmt: on def center_window(self) -> None: """ From 41baf5c78df41792cbe5a93a97da5a3a1e1cc4ca Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Sun, 7 Jul 2024 10:55:19 -0400 Subject: [PATCH 17/18] Add temporary type ignore for font_size --- arcade/text.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arcade/text.py b/arcade/text.py index 9aa345294c..3e60415cbd 100644 --- a/arcade/text.py +++ b/arcade/text.py @@ -214,7 +214,8 @@ def __init__( y=y, # type: ignore z=z, # type: ignore font_name=adjusted_font, - font_size=font_size, + # TODO: Fix this upstream (Mac & Linux seem to allow float) + font_size=font_size, # type: ignore # use type: ignore since cast is slow & pyglet used Literal anchor_x=anchor_x, # type: ignore anchor_y=anchor_y, # type: ignore From a815f69796bc1f758d04c4849a7631a8bc41a6d5 Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Sun, 7 Jul 2024 11:03:34 -0400 Subject: [PATCH 18/18] Update type + doc for arcade.text bold support * Update top-level docstring * Update bold setter & getter * Update bold return types --- arcade/text.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/arcade/text.py b/arcade/text.py index 3e60415cbd..b9a436a2f1 100644 --- a/arcade/text.py +++ b/arcade/text.py @@ -134,7 +134,8 @@ class Text: :param width: A width limit in pixels :param align: Horizontal alignment; values other than "left" require width to be set :param Union[str, tuple[str, ...]] font_name: A font name, path to a font file, or list of names - :param bold: Whether to draw the text as bold + :param bold: Whether to draw the text as bold, and if a string, + how bold. See :py:attr:`.bold` to learn more. :param italic: Whether to draw the text as italic :param anchor_x: How to calculate the anchor point's x coordinate. Options: "left", "center", or "right" @@ -183,7 +184,7 @@ def __init__( width: int | None = None, align: str = "left", font_name: FontNameOrNames = ("calibri", "arial"), - bold: bool = False, + bold: bool | str = False, italic: bool = False, anchor_x: str = "left", anchor_y: str = "baseline", @@ -489,14 +490,23 @@ def align(self, align: str): self._label.set_style("align", align) @property - def bold(self) -> bool: + def bold(self) -> bool | str: """ - Get or set bold state of the label + Get or set bold state of the label. + + The supported values include: + + * ``"black"`` + * ``"bold" (same as ``True``) + * ``"semibold"`` + * ``"semilight"`` + * ``"light"`` + """ return self._label.bold @bold.setter - def bold(self, bold: bool): + def bold(self, bold: bool | str): self._label.bold = bold @property @@ -592,7 +602,7 @@ def create_text_sprite( width: int | None = None, align: str = "left", font_name: FontNameOrNames = ("calibri", "arial"), - bold: bool = False, + bold: bool | str = False, italic: bool = False, anchor_x: str = "left", multiline: bool = False, @@ -680,7 +690,7 @@ def draw_text( width: int | None = None, align: str = "left", font_name: FontNameOrNames = ("calibri", "arial"), - bold: bool = False, + bold: bool | str = False, italic: bool = False, anchor_x: str = "left", anchor_y: str = "baseline", @@ -720,7 +730,8 @@ def draw_text( :param width: A width limit in pixels :param align: Horizontal alignment; values other than "left" require width to be set :param Union[str, tuple[str, ...]] font_name: A font name, path to a font file, or list of names - :param bold: Whether to draw the text as bold + :param bold: Whether to draw the text as bold, and if a :py:class:`str`, + how bold to draw it. See :py:attr:`.Text.bold` to learn more. :param italic: Whether to draw the text as italic :param anchor_x: How to calculate the anchor point's x coordinate :param anchor_y: How to calculate the anchor point's y coordinate