Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
13 changes: 7 additions & 6 deletions arcade/gui/widgets/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ def add(self, child: W, **kwargs) -> W:
bind(child, "size_hint", self._trigger_size_hint_update, weak=True)
bind(child, "size_hint_min", self._trigger_size_hint_update, weak=True)
bind(child, "size_hint_max", self._trigger_size_hint_update, weak=True)
bind(child, "visible", self._trigger_size_hint_update, weak=True)

return super().add(child, **kwargs)

Expand All @@ -308,6 +309,7 @@ def remove(self, child: UIWidget):
unbind(child, "size_hint", self._trigger_size_hint_update)
unbind(child, "size_hint_min", self._trigger_size_hint_update)
unbind(child, "size_hint_max", self._trigger_size_hint_update)
unbind(child, "visible", self._trigger_size_hint_update)

return super().remove(child)

Expand All @@ -317,12 +319,12 @@ def _trigger_size_hint_update(self):
def _update_size_hints(self):
self._size_hint_requires_update = False

required_space_between = max(0, len(self.children) - 1) * self._space_between
min_child_sizes = [
UILayout.min_size_of(child) for child in self.children if child.visible is not None
]
required_space_between = max(0, len(min_child_sizes) - 1) * self._space_between

if len(self.children) == 0:
if not min_child_sizes:
width = 0
height = 0
elif self.vertical:
Expand Down Expand Up @@ -362,12 +364,11 @@ def do_layout(self):
start_y = self.content_rect.top
start_x = self.content_rect.left

if not self.children:
return

children_to_render = [
(child, data) for child, data in self._children if child.visible is not None
]
if not children_to_render:
return

# main axis
constraints = [
Expand All @@ -377,7 +378,7 @@ def do_layout(self):

available_space = (
self.content_height if self.vertical else self.content_width
) - self._space_between * (len(self.children) - 1)
) - self._space_between * (len(children_to_render) - 1)
main_sizes = _box_axis_algorithm(constraints, available_space)

# orthogonal axis
Expand Down
113 changes: 113 additions & 0 deletions tests/unit/gui/test_layouting_boxlayout_collapsed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import pytest

from arcade.gui import UIBoxLayout, UIWidget
from arcade.types import LBWH


@pytest.mark.parametrize("vertical", [False, True])
@pytest.mark.parametrize("count", [0, 1, 3])
def test_all_collapsed_children_preserve_only_padding_and_border(vertical, count):
children = [UIWidget(width=100, height=50) for _ in range(count)]
for child in children:
child.visible = None
original_rects = [child.rect for child in children]
box = UIBoxLayout(vertical=vertical, children=children, space_between=10)
box.with_padding(left=2, right=3, top=5, bottom=7).with_border(width=4)

box.fit_content()
box.do_layout()

assert box.size_hint_min == (13, 20)
assert box.size == (13, 20)
assert [child.rect for child in children] == original_rects


@pytest.mark.parametrize("vertical", [False, True])
@pytest.mark.parametrize("collapsed_index", [0, 1, 2])
def test_collapsed_children_do_not_reserve_gaps(vertical, collapsed_index):
first = UIWidget(size_hint=(1, 1), size_hint_min=(20, 30))
second = UIWidget(size_hint=(1, 1), size_hint_min=(20, 30))
collapsed = UIWidget(width=100, height=100)
collapsed.visible = None
original_rect = collapsed.rect
children = [first, second]
children.insert(collapsed_index, collapsed)
box = UIBoxLayout(vertical=vertical, children=children, space_between=10)

assert box.size_hint_min == ((20, 70) if vertical else (50, 30))
box.rect = LBWH(0, 0, 120, 120)
box.do_layout()

if vertical:
assert first.rect == LBWH(0, 65, 120, 55)
assert second.rect == LBWH(0, 0, 120, 55)
else:
assert first.rect == LBWH(0, 0, 55, 120)
assert second.rect == LBWH(65, 0, 55, 120)
assert collapsed.rect == original_rect


@pytest.mark.parametrize("vertical", [False, True])
@pytest.mark.parametrize("visible", [False, True])
def test_hidden_children_still_participate_in_layout(vertical, visible):
first = UIWidget(width=20, height=30)
first.visible = visible
second = UIWidget(width=40, height=50)
box = UIBoxLayout(vertical=vertical, children=[first, second], space_between=10)

box.fit_content()
box.do_layout()

assert box.size_hint_min == ((40, 90) if vertical else (70, 50))
if vertical:
assert first.top == box.top
assert first.bottom - second.top == 10
assert second.bottom == box.bottom
else:
assert first.left == box.left
assert second.left - first.right == 10
assert second.right == box.right


@pytest.mark.parametrize("vertical", [False, True])
def test_visibility_changes_update_the_minimum_size(vertical):
first = UIWidget(width=20, height=30)
second = UIWidget(width=40, height=50)
box = UIBoxLayout(vertical=vertical, children=[first, second], space_between=10)

first.visible = None
box._prepare_layout()
assert box.size_hint_min == (40, 50)

second.visible = None
box._prepare_layout()
assert box.size_hint_min == (0, 0)

first.visible = False
box._prepare_layout()
assert box.size_hint_min == (20, 30)

second.visible = True
box._prepare_layout()
assert box.size_hint_min == ((40, 90) if vertical else (70, 50))


def test_collapse_and_restore_through_ui_manager(window, ui):
first = UIWidget(width=20, height=30)
second = UIWidget(width=40, height=50)
box = UIBoxLayout(children=[first, second], space_between=10)
ui.add(box)
ui.execute_layout()
assert box.size == (40, 90)

first.visible = None
ui.execute_layout()
assert box.size == (40, 50)

second.visible = None
ui.execute_layout()
assert box.size == (0, 0)

first.visible = False
ui.execute_layout()
assert box.size == (20, 30)
Loading