Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 62 additions & 12 deletions arcade/gui/widgets/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
Expand All @@ -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,
Expand Down Expand Up @@ -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(),
Expand All @@ -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,
Expand Down
103 changes: 103 additions & 0 deletions tests/unit/gui/test_layout_size_hint_warning.py
Original file line number Diff line number Diff line change
@@ -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)
20 changes: 10 additions & 10 deletions tests/unit/gui/test_layouting_anchorlayout.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
)
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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))
)
Expand All @@ -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()
Expand All @@ -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)))

Expand All @@ -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))
)
Expand Down
Loading