From 3cfd9b3f32e3c4d1992c00ea4f8c5ae5195b676b Mon Sep 17 00:00:00 2001 From: Paul V Craven Date: Thu, 8 Oct 2026 12:06:42 -0500 Subject: [PATCH] Return get_display_size() in window units with display scaling In stretch mode, pyglet multiplies a window's size by the screen's scale on Windows and Linux, but get_display_size() returned the screen's size in pixels, so a window that size was scaled up twice and didn't fit on the screen. It now divides by the scale there, rounding down. macOS and the web don't scale windows, so they're unchanged (#2791). center_window() needs the size in pixels, to match the framebuffer and set_location, so it reads the window's screen directly. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + arcade/application.py | 7 +-- arcade/window_commands.py | 17 ++++++- tests/unit/window/test_display_size.py | 62 ++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 tests/unit/window/test_display_size.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 49aa1a0905..1b131218ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Arcade [PyPi Release History](https://pypi.org/project/arcade/#history) page. ## Unreleased ### Fixes +- Fixed a window sized with `arcade.get_display_size()` being bigger than the screen with display scaling, such as Windows set to 125% or 150%. Arcade scales windows up by the display scale, but `get_display_size()` returned the screen's size in pixels, so the window was scaled up twice. It now returns the size in window units on Windows and Linux: at 125%, a 1920x1200 screen gives 1536x960, which fills it exactly. On macOS and the web it's unchanged, as they don't scale windows ([#2791](https://github.com/pythonarcade/arcade/issues/2791)). - Fixed `arcade.future.background.ParallaxGroup` raising `ZeroDivisionError` the first time its offset changed after a layer was given a depth of 0. Setting a depth of 0 now raises a `ValueError` right away, which says to use `float("inf")` for a layer that doesn't scroll ([#1551](https://github.com/pythonarcade/arcade/issues/1551)). - GUI: Fixed a gap between `UIFlatButton`'s border and the edge of the button, half the border's width, which also showed on `UIDropdown` ([#2868](https://github.com/pythonarcade/arcade/issues/2868)). - GUI: Passing a single style such as `UIFlatButton.UIStyle()` as a widget's `style` raised a confusing `TypeError` about attribute names. It now raises a `TypeError` saying a dict of styles for each state is needed, and how to start from the widget's `DEFAULT_STYLE` ([#2817](https://github.com/pythonarcade/arcade/issues/2817)). diff --git a/arcade/application.py b/arcade/application.py index 286c0ae268..a5ee05c86b 100644 --- a/arcade/application.py +++ b/arcade/application.py @@ -32,7 +32,7 @@ from arcade.gl.provider import get_arcade_context, set_provider from arcade.types import LBWH, Color, Rect, RGBANormalized, RGBOrA255 from arcade.utils import is_raspberry_pi -from arcade.window_commands import get_display_size, set_window +from arcade.window_commands import set_window if TYPE_CHECKING: from arcade.camera import Projector @@ -534,8 +534,9 @@ def set_fullscreen( def center_window(self) -> None: """Center the window on your desktop.""" - # Get the display screen using pyglet - screen_width, screen_height = get_display_size() + # The screen's size in pixels, like the framebuffer's size and the + # location. get_display_size() is in window units instead. + screen_width, screen_height = self.screen.width, self.screen.height window_width, window_height = self.get_framebuffer_size() # Center the window diff --git a/arcade/window_commands.py b/arcade/window_commands.py index 13dc5c5533..db63a932dc 100644 --- a/arcade/window_commands.py +++ b/arcade/window_commands.py @@ -46,6 +46,11 @@ def get_display_size(screen_id: int = 0) -> tuple[int, int]: The size of the primary monitor is returned by default. + The size is in the same units as a :py:class:`~arcade.Window`'s size, so a + window this size fills the screen. With display scaling, such as Windows set + to 150%, that's smaller than the screen's size in pixels, because Arcade + scales windows up to match. + Args: screen_id: The screen number Returns: @@ -53,7 +58,17 @@ def get_display_size(screen_id: int = 0) -> tuple[int, int]: """ display = pyglet.display.Display() # type: ignore # pending: pyglet fixes import tricks screen = display.get_screens()[screen_id] - return screen.width, screen.height + width, height = screen.width, screen.height + # In stretch mode, pyglet multiplies a window's size by the screen's scale + # on Windows and Linux. Screen sizes on macOS are already in the same units + # as window sizes, and the web doesn't scale windows. + if pyglet.options.dpi_scaling == "stretch" and ( + pyglet.compat_platform == "win32" or pyglet.compat_platform.startswith("linux") + ): + scale = screen.get_scale() + # Round down, so the scaled window isn't bigger than the screen + width, height = int(width / scale), int(height / scale) + return width, height def get_window() -> Window: diff --git a/tests/unit/window/test_display_size.py b/tests/unit/window/test_display_size.py new file mode 100644 index 0000000000..bd7e90add2 --- /dev/null +++ b/tests/unit/window/test_display_size.py @@ -0,0 +1,62 @@ +"""get_display_size() is in window units, so a window that size fills the screen (#2791).""" + +import pyglet +import pytest + +import arcade + + +class FakeScreen: + def __init__(self, width, height, scale): + self.width = width + self.height = height + self._scale = scale + + def get_scale(self): + return self._scale + + +@pytest.fixture +def fake_screen(monkeypatch): + def use(width, height, scale, dpi_scaling, platform): + screen = FakeScreen(width, height, scale) + + class FakeDisplay: + def get_screens(self): + return [screen] + + monkeypatch.setattr(pyglet.display, "Display", FakeDisplay) + monkeypatch.setattr(pyglet.options, "dpi_scaling", dpi_scaling) + monkeypatch.setattr(pyglet, "compat_platform", platform) + + return use + + +@pytest.mark.parametrize("platform", ["win32", "linux", "linux-compat"]) +@pytest.mark.parametrize( + ("width", "height", "scale", "expected"), + [ + (1920, 1080, 1.0, (1920, 1080)), + (2560, 1440, 1.25, (2048, 1152)), + (1920, 1080, 1.5, (1280, 720)), + # Rounded down, so the scaled window isn't bigger than the screen + (1366, 768, 1.75, (780, 438)), + ], +) +def test_stretch_divides_by_scale(fake_screen, platform, width, height, scale, expected): + fake_screen(width, height, scale, "stretch", platform) + size = arcade.get_display_size() + assert size == expected + assert int(size[0] * scale) <= width + assert int(size[1] * scale) <= height + + +@pytest.mark.parametrize("platform", ["darwin", "emscripten"]) +def test_stretch_on_platforms_that_dont_scale_windows(fake_screen, platform): + fake_screen(1440, 900, 2.0, "stretch", platform) + assert arcade.get_display_size() == (1440, 900) + + +def test_platform_scaling_returns_the_screen_size(fake_screen): + fake_screen(2560, 1440, 1.25, "platform", "win32") + assert arcade.get_display_size() == (2560, 1440)