diff --git a/CHANGELOG.md b/CHANGELOG.md index 49aa1a090..1b131218c 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 286c0ae26..a5ee05c86 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 13dc5c553..db63a932d 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 000000000..bd7e90add --- /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)