diff --git a/arcade/gui/widgets/layout.py b/arcade/gui/widgets/layout.py index a809fdf76b..3bf18722be 100644 --- a/arcade/gui/widgets/layout.py +++ b/arcade/gui/widgets/layout.py @@ -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, @@ -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 = [ @@ -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 @@ -603,6 +627,8 @@ 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 ) @@ -610,12 +636,13 @@ def do_layout(self): 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) @@ -623,16 +650,28 @@ def do_layout(self): 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) @@ -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 diff --git a/tests/test_gui/test_layouting_gridlayout.py b/tests/test_gui/test_layouting_gridlayout.py index 8f64fb9eee..907eea9649 100644 --- a/tests/test_gui/test_layouting_gridlayout.py +++ b/tests/test_gui/test_layouting_gridlayout.py @@ -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) \ No newline at end of file