From 63be4b4754e8f5d8f13dbfde1c2ea76ecc83a1a6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 21:15:02 +0000 Subject: [PATCH 1/4] Initial plan From 570df7a8559fbe1f4f4ddb76739f46be0bd85cc7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 21:26:43 +0000 Subject: [PATCH 2/4] Add size_hint warnings for GUI layouts when fixed width/height conflicts with active size_hint Co-authored-by: eruvanos <9437863+eruvanos@users.noreply.github.com> --- arcade/gui/widgets/layout.py | 74 +++++++++++-- .../unit/gui/test_layout_size_hint_warning.py | 103 ++++++++++++++++++ tests/unit/gui/test_layouting_anchorlayout.py | 20 ++-- tests/unit/gui/test_layouting_boxlayout.py | 20 ++-- 4 files changed, 185 insertions(+), 32 deletions(-) create mode 100644 tests/unit/gui/test_layout_size_hint_warning.py diff --git a/arcade/gui/widgets/layout.py b/arcade/gui/widgets/layout.py index bdf2512178..ab5611c3cc 100644 --- a/arcade/gui/widgets/layout.py +++ b/arcade/gui/widgets/layout.py @@ -14,6 +14,47 @@ W = TypeVar("W", bound="UIWidget") +_NO_EXPLICIT_SIZE = object() +"""Sentinel value to detect when width/height was not explicitly provided by the user.""" + + +def _warn_if_size_hint_overrides_fixed_size( + class_name: str, + width, + height, + size_hint, +) -> None: + """Warn when a fixed width/height is given but the size_hint will override it. + + Layouts have non-None size_hint by default, which causes the parent layout to + resize them, overriding any fixed width/height given by the developer. + + Args: + class_name: Name of the layout class, used in the warning message. + width: The width argument passed to __init__, or ``_NO_EXPLICIT_SIZE`` if + width was not explicitly provided. + height: The height argument passed to __init__, or ``_NO_EXPLICIT_SIZE`` if + height was not explicitly provided. + size_hint: The size_hint argument passed to __init__. + """ + sh_w = size_hint[0] if size_hint is not None else None + sh_h = size_hint[1] if size_hint is not None else None + + if width is not _NO_EXPLICIT_SIZE and sh_w is not None: + warnings.warn( + f"{class_name} was given a fixed width, but size_hint_x is {sh_w!r}. " + f"The size_hint will override the fixed width. " + f"Set size_hint=(None, ...) to use a fixed width.", + stacklevel=3, + ) + if height is not _NO_EXPLICIT_SIZE and sh_h is not None: + warnings.warn( + f"{class_name} was given a fixed height, but size_hint_y is {sh_h!r}. " + f"The size_hint will override the fixed height. " + f"Set size_hint=(..., None) to use a fixed height.", + stacklevel=3, + ) + class UIAnchorLayout(UILayout): """Places children based on anchor values. @@ -73,19 +114,22 @@ def __init__( *, x: float = 0, y: float = 0, - width: float = 1, - height: float = 1, + width: float = _NO_EXPLICIT_SIZE, + height: float = _NO_EXPLICIT_SIZE, children: Iterable[UIWidget] = tuple(), size_hint=(1, 1), size_hint_min=None, size_hint_max=None, **kwargs, ): + _warn_if_size_hint_overrides_fixed_size( + type(self).__name__, width, height, size_hint + ) super().__init__( x=x, y=y, - width=width, - height=height, + width=1 if width is _NO_EXPLICIT_SIZE else width, + height=1 if height is _NO_EXPLICIT_SIZE else height, children=children, size_hint=size_hint, size_hint_min=size_hint_min, @@ -241,8 +285,8 @@ def __init__( *, x=0, y=0, - width=1, - height=1, + width=_NO_EXPLICIT_SIZE, + height=_NO_EXPLICIT_SIZE, vertical=True, align="center", children: Iterable[UIWidget] = tuple(), @@ -252,11 +296,14 @@ def __init__( style=None, **kwargs, ): + _warn_if_size_hint_overrides_fixed_size( + type(self).__name__, width, height, size_hint + ) super().__init__( x=x, y=y, - width=width, - height=height, + width=1 if width is _NO_EXPLICIT_SIZE else width, + height=1 if height is _NO_EXPLICIT_SIZE else height, children=children, size_hint=size_hint, size_hint_max=size_hint_max, @@ -487,8 +534,8 @@ def __init__( *, x=0, y=0, - width=1, - height=1, + width=_NO_EXPLICIT_SIZE, + height=_NO_EXPLICIT_SIZE, align_horizontal="center", align_vertical="center", children: Iterable[UIWidget] = tuple(), @@ -500,11 +547,14 @@ def __init__( row_count: int = 1, **kwargs, ): + _warn_if_size_hint_overrides_fixed_size( + type(self).__name__, width, height, size_hint + ) super().__init__( x=x, y=y, - width=width, - height=height, + width=1 if width is _NO_EXPLICIT_SIZE else width, + height=1 if height is _NO_EXPLICIT_SIZE else height, children=children, size_hint=size_hint, size_hint_max=size_hint_max, diff --git a/tests/unit/gui/test_layout_size_hint_warning.py b/tests/unit/gui/test_layout_size_hint_warning.py new file mode 100644 index 0000000000..b50b8a65bc --- /dev/null +++ b/tests/unit/gui/test_layout_size_hint_warning.py @@ -0,0 +1,103 @@ +"""Tests that layouts warn when explicit width/height conflicts with active size_hint.""" +import pytest + +from arcade.gui import UIBoxLayout +from arcade.gui.widgets.layout import UIAnchorLayout, UIGridLayout + + +def test_anchor_layout_warns_when_width_given_with_default_size_hint(window): + """UIAnchorLayout should warn when width is given but size_hint_x is active.""" + with pytest.warns(UserWarning, match="size_hint_x"): + UIAnchorLayout(width=500) + + +def test_anchor_layout_warns_when_height_given_with_default_size_hint(window): + """UIAnchorLayout should warn when height is given but size_hint_y is active.""" + with pytest.warns(UserWarning, match="size_hint_y"): + UIAnchorLayout(height=500) + + +def test_anchor_layout_no_warning_when_size_hint_none(window): + """UIAnchorLayout should not warn when size_hint=None is explicitly set.""" + # No warning expected + UIAnchorLayout(width=500, height=500, size_hint=None) + + +def test_anchor_layout_no_warning_when_no_explicit_size(window): + """UIAnchorLayout should not warn when width/height are not explicitly given.""" + # No warning expected + UIAnchorLayout(size_hint=(1, 1)) + + +def test_anchor_layout_no_warning_when_size_hint_x_none(window): + """UIAnchorLayout should not warn for width when size_hint_x is None.""" + # No width warning expected (only height warning) + with pytest.warns(UserWarning, match="size_hint_y"): + UIAnchorLayout(width=500, height=500, size_hint=(None, 1)) + + +def test_anchor_layout_no_warning_when_size_hint_y_none(window): + """UIAnchorLayout should not warn for height when size_hint_y is None.""" + # No height warning expected (only width warning) + with pytest.warns(UserWarning, match="size_hint_x"): + UIAnchorLayout(width=500, height=500, size_hint=(1, None)) + + +def test_box_layout_warns_when_width_given_with_default_size_hint(window): + """UIBoxLayout should warn when width is given but size_hint_x is active.""" + with pytest.warns(UserWarning, match="size_hint_x"): + UIBoxLayout(width=200) + + +def test_box_layout_warns_when_height_given_with_default_size_hint(window): + """UIBoxLayout should warn when height is given but size_hint_y is active.""" + with pytest.warns(UserWarning, match="size_hint_y"): + UIBoxLayout(height=200) + + +def test_box_layout_no_warning_when_size_hint_none(window): + """UIBoxLayout should not warn when size_hint=None is explicitly set.""" + # No warning expected + UIBoxLayout(width=200, height=200, size_hint=None) + + +def test_box_layout_no_warning_when_no_explicit_size(window): + """UIBoxLayout should not warn when width/height are not explicitly given.""" + # No warning expected + UIBoxLayout(size_hint=(0, 0)) + + +def test_grid_layout_warns_when_width_given_with_default_size_hint(window): + """UIGridLayout should warn when width is given but size_hint_x is active.""" + with pytest.warns(UserWarning, match="size_hint_x"): + UIGridLayout(width=200) + + +def test_grid_layout_warns_when_height_given_with_default_size_hint(window): + """UIGridLayout should warn when height is given but size_hint_y is active.""" + with pytest.warns(UserWarning, match="size_hint_y"): + UIGridLayout(height=200) + + +def test_grid_layout_no_warning_when_size_hint_none(window): + """UIGridLayout should not warn when size_hint=None is explicitly set.""" + # No warning expected + UIGridLayout(width=200, height=200, size_hint=None) + + +def test_grid_layout_no_warning_when_no_explicit_size(window): + """UIGridLayout should not warn when width/height are not explicitly given.""" + # No warning expected + UIGridLayout(size_hint=(0, 0)) + + +def test_warning_message_includes_class_name(window): + """Warning should include the layout class name for clear identification.""" + with pytest.warns(UserWarning, match="UIBoxLayout"): + UIBoxLayout(width=200) + + with pytest.warns(UserWarning, match="UIAnchorLayout"): + UIAnchorLayout(width=200) + + with pytest.warns(UserWarning, match="UIGridLayout"): + UIGridLayout(width=200) diff --git a/tests/unit/gui/test_layouting_anchorlayout.py b/tests/unit/gui/test_layouting_anchorlayout.py index 0948dab833..b6fbc5b8d3 100644 --- a/tests/unit/gui/test_layouting_anchorlayout.py +++ b/tests/unit/gui/test_layouting_anchorlayout.py @@ -8,7 +8,7 @@ def test_place_widget(window): dummy = UIDummy(width=100, height=200) - subject = UIAnchorLayout(x=0, y=0, width=500, height=500) + subject = UIAnchorLayout(x=0, y=0, width=500, height=500, size_hint=None) subject.add( dummy, @@ -30,7 +30,7 @@ def test_place_widget_relative_to_own_content_rect(window): dummy = UIDummy(width=100, height=200) subject = ( - UIAnchorLayout(x=0, y=0, width=500, height=500) + UIAnchorLayout(x=0, y=0, width=500, height=500, size_hint=None) .with_border(width=2) .with_padding(left=50, top=100) ) @@ -68,7 +68,7 @@ def test_place_box_layout(window, ui): def test_grow_child_half(window): - subject = UIAnchorLayout(width=400, height=400) + subject = UIAnchorLayout(width=400, height=400, size_hint=None) dummy = subject.add(UIDummy(width=100, height=100, size_hint=(0.5, 0.5))) subject._do_layout() @@ -78,7 +78,7 @@ def test_grow_child_half(window): def test_grow_child_full_width(window): - subject = UIAnchorLayout(width=400, height=400) + subject = UIAnchorLayout(width=400, height=400, size_hint=None) dummy = subject.add(UIDummy(width=100, height=100, size_hint=(1, 0.5))) subject._do_layout() @@ -88,7 +88,7 @@ def test_grow_child_full_width(window): def test_grow_child_full_height(window): - subject = UIAnchorLayout(width=400, height=400) + subject = UIAnchorLayout(width=400, height=400, size_hint=None) dummy = subject.add(UIDummy(width=100, height=100, size_hint=(0.5, 1))) subject._do_layout() @@ -98,7 +98,7 @@ def test_grow_child_full_height(window): def test_grow_child_to_max_size(window): - subject = UIAnchorLayout(width=400, height=400) + subject = UIAnchorLayout(width=400, height=400, size_hint=None) dummy = subject.add(UIDummy(width=100, height=100, size_hint=(1, 1), size_hint_max=(200, 150))) subject._do_layout() @@ -108,7 +108,7 @@ def test_grow_child_to_max_size(window): def test_shrink_child_to_min_size(window): - subject = UIAnchorLayout(width=400, height=400) + subject = UIAnchorLayout(width=400, height=400, size_hint=None) dummy = subject.add( UIDummy(width=100, height=100, size_hint=(0.1, 0.1), size_hint_min=(200, 150)) ) @@ -121,7 +121,7 @@ def test_shrink_child_to_min_size(window): def test_children_can_grow_out_of_bounce(window): """This tests behavior, which is used for scrolling.""" - subject = UIAnchorLayout(width=400, height=400) + subject = UIAnchorLayout(width=400, height=400, size_hint=None) dummy = subject.add(UIDummy(width=100, height=100, size_hint=(2, 2))) subject._do_layout() @@ -132,7 +132,7 @@ def test_children_can_grow_out_of_bounce(window): def test_children_limited_to_layout_size_when_enforced(window): """This tests behavior, which is used for scrolling.""" - subject = UIAnchorLayout(width=400, height=400) + subject = UIAnchorLayout(width=400, height=400, size_hint=None) subject._restrict_child_size = True dummy = subject.add(UIDummy(width=100, height=100, size_hint=(2, 2))) @@ -143,7 +143,7 @@ def test_children_limited_to_layout_size_when_enforced(window): def test_only_adjust_size_if_size_hint_is_given_for_dimension(window): - subject = UIAnchorLayout(width=400, height=400) + subject = UIAnchorLayout(width=400, height=400, size_hint=None) dummy = subject.add( UIDummy(width=100, height=100, size_hint=(2, None), size_hint_min=(None, 200)) ) diff --git a/tests/unit/gui/test_layouting_boxlayout.py b/tests/unit/gui/test_layouting_boxlayout.py index 894d24155e..7a366bf5c5 100644 --- a/tests/unit/gui/test_layouting_boxlayout.py +++ b/tests/unit/gui/test_layouting_boxlayout.py @@ -324,7 +324,7 @@ def test_size_hint_contains_border_and_padding(window, ui): def test_vertical_resize_child_according_size_hint_full(window): - box = UIBoxLayout(width=200, height=200, vertical=True) + box = UIBoxLayout(width=200, height=200, vertical=True, size_hint=None) dummy_1 = box.add(UIDummy(width=100, height=100, size_hint=(1, 1))) box._do_layout() @@ -334,7 +334,7 @@ def test_vertical_resize_child_according_size_hint_full(window): def test_vertical_resize_child_according_size_hint_half(window): - box = UIBoxLayout(width=200, height=200, vertical=True) + box = UIBoxLayout(width=200, height=200, vertical=True, size_hint=None) dummy_1 = box.add(UIDummy(width=100, height=100, size_hint=(0.5, 0.5))) box._do_layout() @@ -344,7 +344,7 @@ def test_vertical_resize_child_according_size_hint_half(window): def test_vertical_resize_children_according_size_hint(window): - box = UIBoxLayout(width=300, height=400, vertical=True) + box = UIBoxLayout(width=300, height=400, vertical=True, size_hint=None) dummy_1 = box.add(UIDummy(size_hint_min=(100, 100), size_hint=(1, 1))) dummy_2 = box.add(UIDummy(size_hint_min=(100, 100), size_hint=(0.5, 0.5))) @@ -356,7 +356,7 @@ def test_vertical_resize_children_according_size_hint(window): def test_vertical_ignores_size_hint_none(window): - box = UIBoxLayout(width=300, height=400, vertical=True) + box = UIBoxLayout(width=300, height=400, vertical=True, size_hint=None) dummy_1 = box.add(UIDummy(width=100, height=100, size_hint=(1, None))) dummy_2 = box.add(UIDummy(width=100, height=100, size_hint=(None, 1))) @@ -368,7 +368,7 @@ def test_vertical_ignores_size_hint_none(window): def test_vertical_fit_content(window): - box = UIBoxLayout(width=100, height=100, vertical=True) + box = UIBoxLayout(width=100, height=100, vertical=True, size_hint=None) _ = box.add(UIDummy(width=100, height=50)) _ = box.add(UIDummy(width=20, height=100)) @@ -378,7 +378,7 @@ def test_vertical_fit_content(window): def test_horizontal_resize_child_according_size_hint_full(window): - box = UIBoxLayout(width=200, height=200, vertical=False) + box = UIBoxLayout(width=200, height=200, vertical=False, size_hint=None) dummy_1 = box.add(UIDummy(width=100, height=100, size_hint=(1, 1))) box._do_layout() @@ -388,7 +388,7 @@ def test_horizontal_resize_child_according_size_hint_full(window): def test_horizontal_resize_child_according_size_hint_half(window): - box = UIBoxLayout(width=200, height=200, vertical=False) + box = UIBoxLayout(width=200, height=200, vertical=False, size_hint=None) dummy_1 = box.add(UIDummy(width=100, height=100, size_hint=(0.5, 0.5))) box._do_layout() @@ -398,7 +398,7 @@ def test_horizontal_resize_child_according_size_hint_half(window): def test_horizontal_resize_children_according_size_hint(window): - box = UIBoxLayout(width=300, height=400, vertical=False) + box = UIBoxLayout(width=300, height=400, vertical=False, size_hint=None) dummy_1 = box.add(UIDummy(size_hint_min=(100, 100), size_hint=(1, 1))) dummy_2 = box.add(UIDummy(size_hint_min=(100, 100), size_hint=(0.5, 0.5))) @@ -410,7 +410,7 @@ def test_horizontal_resize_children_according_size_hint(window): def test_horizontal_ignores_size_hint_none(window): - box = UIBoxLayout(width=300, height=400, vertical=False) + box = UIBoxLayout(width=300, height=400, vertical=False, size_hint=None) dummy_1 = box.add(UIDummy(width=100, height=100, size_hint=(1, None))) dummy_2 = box.add(UIDummy(width=100, height=100, size_hint=(None, 1))) @@ -422,7 +422,7 @@ def test_horizontal_ignores_size_hint_none(window): def test_horizontal_fit_content(window): - box = UIBoxLayout(width=100, height=100, vertical=False) + box = UIBoxLayout(width=100, height=100, vertical=False, size_hint=None) _ = box.add(UIDummy(width=100, height=50)) _ = box.add(UIDummy(width=20, height=100)) From 8d0674926947118f0477a9d0fe677f69875d9a53 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 21:40:13 +0000 Subject: [PATCH 3/4] Address review feedback: use Ellipsis sentinel, add type hints, use catch_warnings in tests Co-authored-by: eruvanos <9437863+eruvanos@users.noreply.github.com> --- arcade/gui/widgets/layout.py | 10 +++--- .../unit/gui/test_layout_size_hint_warning.py | 32 ++++++++++++------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/arcade/gui/widgets/layout.py b/arcade/gui/widgets/layout.py index ab5611c3cc..c5798a48a4 100644 --- a/arcade/gui/widgets/layout.py +++ b/arcade/gui/widgets/layout.py @@ -14,7 +14,7 @@ W = TypeVar("W", bound="UIWidget") -_NO_EXPLICIT_SIZE = object() +_NO_EXPLICIT_SIZE = ... """Sentinel value to detect when width/height was not explicitly provided by the user.""" @@ -31,9 +31,9 @@ def _warn_if_size_hint_overrides_fixed_size( Args: class_name: Name of the layout class, used in the warning message. - width: The width argument passed to __init__, or ``_NO_EXPLICIT_SIZE`` if + width: The width argument passed to __init__, or ``...`` if width was not explicitly provided. - height: The height argument passed to __init__, or ``_NO_EXPLICIT_SIZE`` if + height: The height argument passed to __init__, or ``...`` if height was not explicitly provided. size_hint: The size_hint argument passed to __init__. """ @@ -114,8 +114,8 @@ def __init__( *, x: float = 0, y: float = 0, - width: float = _NO_EXPLICIT_SIZE, - height: float = _NO_EXPLICIT_SIZE, + width: float | type(...) = _NO_EXPLICIT_SIZE, + height: float | type(...) = _NO_EXPLICIT_SIZE, children: Iterable[UIWidget] = tuple(), size_hint=(1, 1), size_hint_min=None, diff --git a/tests/unit/gui/test_layout_size_hint_warning.py b/tests/unit/gui/test_layout_size_hint_warning.py index b50b8a65bc..a0e5c29103 100644 --- a/tests/unit/gui/test_layout_size_hint_warning.py +++ b/tests/unit/gui/test_layout_size_hint_warning.py @@ -1,4 +1,6 @@ """Tests that layouts warn when explicit width/height conflicts with active size_hint.""" +import warnings + import pytest from arcade.gui import UIBoxLayout @@ -19,14 +21,16 @@ def test_anchor_layout_warns_when_height_given_with_default_size_hint(window): def test_anchor_layout_no_warning_when_size_hint_none(window): """UIAnchorLayout should not warn when size_hint=None is explicitly set.""" - # No warning expected - UIAnchorLayout(width=500, height=500, size_hint=None) + with warnings.catch_warnings(): + warnings.simplefilter("error") + UIAnchorLayout(width=500, height=500, size_hint=None) def test_anchor_layout_no_warning_when_no_explicit_size(window): """UIAnchorLayout should not warn when width/height are not explicitly given.""" - # No warning expected - UIAnchorLayout(size_hint=(1, 1)) + with warnings.catch_warnings(): + warnings.simplefilter("error") + UIAnchorLayout(size_hint=(1, 1)) def test_anchor_layout_no_warning_when_size_hint_x_none(window): @@ -57,14 +61,16 @@ def test_box_layout_warns_when_height_given_with_default_size_hint(window): def test_box_layout_no_warning_when_size_hint_none(window): """UIBoxLayout should not warn when size_hint=None is explicitly set.""" - # No warning expected - UIBoxLayout(width=200, height=200, size_hint=None) + with warnings.catch_warnings(): + warnings.simplefilter("error") + UIBoxLayout(width=200, height=200, size_hint=None) def test_box_layout_no_warning_when_no_explicit_size(window): """UIBoxLayout should not warn when width/height are not explicitly given.""" - # No warning expected - UIBoxLayout(size_hint=(0, 0)) + with warnings.catch_warnings(): + warnings.simplefilter("error") + UIBoxLayout(size_hint=(0, 0)) def test_grid_layout_warns_when_width_given_with_default_size_hint(window): @@ -81,14 +87,16 @@ def test_grid_layout_warns_when_height_given_with_default_size_hint(window): def test_grid_layout_no_warning_when_size_hint_none(window): """UIGridLayout should not warn when size_hint=None is explicitly set.""" - # No warning expected - UIGridLayout(width=200, height=200, size_hint=None) + with warnings.catch_warnings(): + warnings.simplefilter("error") + UIGridLayout(width=200, height=200, size_hint=None) def test_grid_layout_no_warning_when_no_explicit_size(window): """UIGridLayout should not warn when width/height are not explicitly given.""" - # No warning expected - UIGridLayout(size_hint=(0, 0)) + with warnings.catch_warnings(): + warnings.simplefilter("error") + UIGridLayout(size_hint=(0, 0)) def test_warning_message_includes_class_name(window): From 306c9b314c41fb16846cc1e6ed5b64bb2a53e10d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 21:57:18 +0000 Subject: [PATCH 4/4] Inline Ellipsis sentinel and move warning to UILayout method Co-authored-by: eruvanos <9437863+eruvanos@users.noreply.github.com> --- arcade/gui/widgets/__init__.py | 33 +++++++++++++ arcade/gui/widgets/layout.py | 86 ++++++++-------------------------- 2 files changed, 53 insertions(+), 66 deletions(-) diff --git a/arcade/gui/widgets/__init__.py b/arcade/gui/widgets/__init__.py index f275509e22..7f1c9a8034 100644 --- a/arcade/gui/widgets/__init__.py +++ b/arcade/gui/widgets/__init__.py @@ -1,5 +1,6 @@ from __future__ import annotations +import warnings import weakref from abc import ABC from collections.abc import Iterable @@ -1026,6 +1027,38 @@ def do_layout(self): frame, this will happen automatically if the position or size of this widget changed. """ + def _warn_if_size_hint_overrides_fixed_size(self, width, height, size_hint) -> None: + """Warn when a fixed width/height is given but the size_hint will override it. + + Layouts have non-None size_hint by default, which causes the parent layout to + resize them, overriding any fixed width/height given by the developer. + + Args: + width: The width argument passed to __init__, or ``...`` if + width was not explicitly provided. + height: The height argument passed to __init__, or ``...`` if + height was not explicitly provided. + size_hint: The size_hint argument passed to __init__. + """ + class_name = type(self).__name__ + sh_w = size_hint[0] if size_hint is not None else None + sh_h = size_hint[1] if size_hint is not None else None + + if width is not ... and sh_w is not None: + warnings.warn( + f"{class_name} was given a fixed width, but size_hint_x is {sh_w!r}. " + f"The size_hint will override the fixed width. " + f"Set size_hint=(None, ...) to use a fixed width.", + stacklevel=3, + ) + if height is not ... and sh_h is not None: + warnings.warn( + f"{class_name} was given a fixed height, but size_hint_y is {sh_h!r}. " + f"The size_hint will override the fixed height. " + f"Set size_hint=(..., None) to use a fixed height.", + stacklevel=3, + ) + class UISpace(UIWidget): """Widget reserving space, can also have a background color. diff --git a/arcade/gui/widgets/layout.py b/arcade/gui/widgets/layout.py index c5798a48a4..ffd010689d 100644 --- a/arcade/gui/widgets/layout.py +++ b/arcade/gui/widgets/layout.py @@ -4,6 +4,7 @@ from collections.abc import Iterable from dataclasses import dataclass from typing import Literal, TypeVar +from types import EllipsisType from typing_extensions import override @@ -14,47 +15,6 @@ W = TypeVar("W", bound="UIWidget") -_NO_EXPLICIT_SIZE = ... -"""Sentinel value to detect when width/height was not explicitly provided by the user.""" - - -def _warn_if_size_hint_overrides_fixed_size( - class_name: str, - width, - height, - size_hint, -) -> None: - """Warn when a fixed width/height is given but the size_hint will override it. - - Layouts have non-None size_hint by default, which causes the parent layout to - resize them, overriding any fixed width/height given by the developer. - - Args: - class_name: Name of the layout class, used in the warning message. - width: The width argument passed to __init__, or ``...`` if - width was not explicitly provided. - height: The height argument passed to __init__, or ``...`` if - height was not explicitly provided. - size_hint: The size_hint argument passed to __init__. - """ - sh_w = size_hint[0] if size_hint is not None else None - sh_h = size_hint[1] if size_hint is not None else None - - if width is not _NO_EXPLICIT_SIZE and sh_w is not None: - warnings.warn( - f"{class_name} was given a fixed width, but size_hint_x is {sh_w!r}. " - f"The size_hint will override the fixed width. " - f"Set size_hint=(None, ...) to use a fixed width.", - stacklevel=3, - ) - if height is not _NO_EXPLICIT_SIZE and sh_h is not None: - warnings.warn( - f"{class_name} was given a fixed height, but size_hint_y is {sh_h!r}. " - f"The size_hint will override the fixed height. " - f"Set size_hint=(..., None) to use a fixed height.", - stacklevel=3, - ) - class UIAnchorLayout(UILayout): """Places children based on anchor values. @@ -114,22 +74,20 @@ def __init__( *, x: float = 0, y: float = 0, - width: float | type(...) = _NO_EXPLICIT_SIZE, - height: float | type(...) = _NO_EXPLICIT_SIZE, + width: float | EllipsisType = ..., + height: float | EllipsisType = ..., children: Iterable[UIWidget] = tuple(), size_hint=(1, 1), size_hint_min=None, size_hint_max=None, **kwargs, ): - _warn_if_size_hint_overrides_fixed_size( - type(self).__name__, width, height, size_hint - ) + self._warn_if_size_hint_overrides_fixed_size(width, height, size_hint) super().__init__( x=x, y=y, - width=1 if width is _NO_EXPLICIT_SIZE else width, - height=1 if height is _NO_EXPLICIT_SIZE else height, + width=1 if width is ... else width, + height=1 if height is ... else height, children=children, size_hint=size_hint, size_hint_min=size_hint_min, @@ -283,10 +241,10 @@ class UIBoxLayout(UILayout): def __init__( self, *, - x=0, - y=0, - width=_NO_EXPLICIT_SIZE, - height=_NO_EXPLICIT_SIZE, + x: float = 0, + y: float = 0, + width: float | EllipsisType = ..., + height: float | EllipsisType = ..., vertical=True, align="center", children: Iterable[UIWidget] = tuple(), @@ -296,14 +254,12 @@ def __init__( style=None, **kwargs, ): - _warn_if_size_hint_overrides_fixed_size( - type(self).__name__, width, height, size_hint - ) + self._warn_if_size_hint_overrides_fixed_size(width, height, size_hint) super().__init__( x=x, y=y, - width=1 if width is _NO_EXPLICIT_SIZE else width, - height=1 if height is _NO_EXPLICIT_SIZE else height, + width=1 if width is ... else width, + height=1 if height is ... else height, children=children, size_hint=size_hint, size_hint_max=size_hint_max, @@ -532,10 +488,10 @@ class UIGridLayout(UILayout): def __init__( self, *, - x=0, - y=0, - width=_NO_EXPLICIT_SIZE, - height=_NO_EXPLICIT_SIZE, + x: float = 0, + y: float = 0, + width: float | EllipsisType = ..., + height: float | EllipsisType = ..., align_horizontal="center", align_vertical="center", children: Iterable[UIWidget] = tuple(), @@ -547,14 +503,12 @@ def __init__( row_count: int = 1, **kwargs, ): - _warn_if_size_hint_overrides_fixed_size( - type(self).__name__, width, height, size_hint - ) + self._warn_if_size_hint_overrides_fixed_size(width, height, size_hint) super().__init__( x=x, y=y, - width=1 if width is _NO_EXPLICIT_SIZE else width, - height=1 if height is _NO_EXPLICIT_SIZE else height, + width=1 if width is ... else width, + height=1 if height is ... else height, children=children, size_hint=size_hint, size_hint_max=size_hint_max,