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
67 changes: 55 additions & 12 deletions arcade/gui/widgets/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ def __init__(
align_horizontal="center",
align_vertical="center",
children: Iterable[UIWidget] = tuple(),
size_hint=None,
size_hint=(0, 0),
size_hint_min=None,
size_hint_max=None,
horizontal_spacing: int = 0,
Expand Down Expand Up @@ -458,6 +458,16 @@ def __init__(
# initially update size hints
self._update_size_hints()

@staticmethod
def _layouting_allowed(child: UIWidget) -> Tuple[bool, bool]:
"""
Checks if size_hint is given for the dimension, which would allow the layout to resize this widget

:return: horizontal, vertical
"""
sh_w, sh_h = child.size_hint or (None, None)
return sh_w is not None, sh_h is not None

def _update_size_hints(self):

child_sorted_row_wise = [
Expand All @@ -471,21 +481,35 @@ def _update_size_hints(self):
[(0, 1) for _ in range(self.column_count)] for _ in range(self.row_count)
]

def min_size(child: UIWidget) -> Tuple[float, float]:
"""
Determine min size of a child widget
This can be the size_hint_min. If no size_hints are provided the child size has to stay the same and
the minimal size is the current size.
"""
h_allowed, v_allowed = UIGridLayout._layouting_allowed(child)
shmn_w, shmn_h = child.size_hint_min or (None, None)
shmn_w = shmn_w or 0 if h_allowed else child.width
shmn_h = shmn_h or 0 if v_allowed else child.height
return shmn_w, shmn_h

for child, data in self._children:
col_num = data["col_num"]
row_num = data["row_num"]
col_span = data["col_span"]
row_span = data["row_span"]

shmn_w, shmn_h = min_size(child)

for i in range(col_num, col_span + col_num):
max_width_per_column[i][row_num] = (0, 0)

max_width_per_column[col_num][row_num] = (child.width, col_span)
max_width_per_column[col_num][row_num] = (shmn_w, col_span)

for i in range(row_num, row_span + row_num):
max_height_per_row[i][col_num] = (0, 0)

max_height_per_row[row_num][col_num] = (child.height, row_span)
max_height_per_row[row_num][col_num] = (shmn_h, row_span)

for row in child_sorted_row_wise[
row_num : row_num + row_span # noqa: E203
Expand Down Expand Up @@ -603,36 +627,51 @@ def do_layout(self):
start_x = initial_left_x

for col_num, child in enumerate(row):
new_rect = child.rect

max_height = (
max_height_per_row[row_num][col_num][0] + self._vertical_spacing
)
max_width = (
max_width_per_column[col_num][row_num][0] + self._horizontal_spacing
)

# re-assigning max_width and max_height to remove empty rows and columns as spacing is added to all cells.
if max_width == self._horizontal_spacing:
max_width = 0
if max_height == self._vertical_spacing:
max_height = 0

col_span = max_width_per_column[col_num][row_num][1] or 1
# col_span = max_width_per_column[col_num][row_num][1] or 1
row_span = max_height_per_row[row_num][col_num][1] or 1

center_y = start_y - (max_height / 2)
center_x = start_x + (max_width / 2)

start_x += max_width

if max_height / row_span > max_height_row:
max_height_row = max_height / row_span

if child is not None and max_width != 0 and max_height != 0:

sh_w, sh_h = 0, 0
if child.size_hint:
sh_w, sh_h = (child.size_hint[0] or 0), (child.size_hint[1] or 0)
shmn_w, shmn_h = child.size_hint_min or (None, None)
shmx_w, shmx_h = child.size_hint_max or (None, None)

new_width = max(shmn_w or 0, sh_w*max_width or child.width)
if shmx_w:
new_width = min(shmx_w, new_width)
new_height = max(shmn_h or 0, sh_h*max_height or child.height)
if shmx_h:
new_height = min(shmx_h, new_height)
new_rect = new_rect.resize(width=new_width, height=new_height)

if self.align_vertical == "top":
new_rect = child.rect.align_top(start_y)
new_rect = new_rect.align_top(start_y)
elif self.align_vertical == "bottom":
new_rect = child.rect.align_bottom(start_y - max_height)
new_rect = new_rect.align_bottom(start_y - max_height)
else:
new_rect = child.rect.align_center_y(center_y)
new_rect = new_rect.align_center_y(center_y)

if self.align_horizontal == "left":
new_rect = new_rect.align_left(start_x - max_width)
Expand All @@ -641,7 +680,11 @@ def do_layout(self):
else:
new_rect = new_rect.align_center_x(center_x)

if new_rect != child.rect:
child.rect = new_rect
child.rect = new_rect

# this is required due to row-wise rendering as start_y doesn't resets like start_x
actual_row_height = max_height / row_span
if actual_row_height > max_height_row:
max_height_row = actual_row_height

start_y -= max_height_row
55 changes: 17 additions & 38 deletions tests/test_gui/test_layouting_gridlayout.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,50 +115,29 @@ def test_fit_content_by_default(window):

assert subject.size_hint == (0, 0)


def test_growth_child(window):
dummy1 = UIDummy(width=100, height=100, size_hint=(1, 1))

subject = UIGridLayout(
column_count=1,
row_count=1,
)

subject.add(dummy1, 0, 0)

subject.resize(width=200, height=300)
subject.do_layout()

assert dummy1.size == (200, 300)


def test_shrink_child(window):
dummy1 = UIDummy(width=100, height=100, size_hint=(1, 1))
def test_adjust_children_size_relative(window):
dummy1 = UIDummy(width=100, height=100)
dummy2 = UIDummy(width=50, height=50, size_hint=(.75, .75))
dummy3 = UIDummy(width=100, height=100, size_hint=(.5, .5), size_hint_min=(60, 60))
dummy4 = UIDummy(width=100, height=100)

subject = UIGridLayout(
column_count=1,
row_count=1,
column_count=2,
row_count=2,
)

subject.add(dummy1, 0, 0)
subject.add(dummy2, 0, 1)
subject.add(dummy3, 1, 0)
subject.add(dummy4, 1, 1)

subject.resize(width=50, height=60)
subject.rect = Rect(0, 0, *subject.size_hint_min)
subject.do_layout()

assert dummy1.size == (50, 60)


def test_adjust_child_size_relative(window):
dummy1 = UIDummy(width=100, height=100, size_hint=(0.5, 0.5))

subject = UIGridLayout(
column_count=1,
row_count=1,
)

subject.add(dummy1, 0, 0)

subject.resize(width=100, height=200)
subject.do_layout()
# check that do_layout doesn't manipulate the rect
assert subject.rect == (0, 0, 200, 200)

assert dummy1.size == (50, 100)
assert dummy1.size == (100, 100)
assert dummy2.size == (75, 75)
assert dummy3.size == (60, 60)
assert dummy4.size == (100, 100)