From 0168882e2b8484b444621184a354cedabea5dc28 Mon Sep 17 00:00:00 2001 From: Charles Whittington Date: Sat, 28 Dec 2024 00:37:37 -0500 Subject: [PATCH 01/16] Combined row and column layout methods --- core/src/toga/style/pack.py | 785 ++++++++++++++---------------------- 1 file changed, 308 insertions(+), 477 deletions(-) diff --git a/core/src/toga/style/pack.py b/core/src/toga/style/pack.py index 0932fc21f6..11189c3598 100644 --- a/core/src/toga/style/pack.py +++ b/core/src/toga/style/pack.py @@ -92,10 +92,15 @@ class Pack(BaseStyle): class Box(BaseBox): - pass + def content(self, name): + return getattr(self, f"content_{name}") + + def min_content(self, name): + return getattr(self, f"min_content_{name}") class IntrinsicSize(BaseIntrinsicSize): - pass + def dim(self, name): + return getattr(self, name) _depth = -1 @@ -387,20 +392,22 @@ def _layout_node( if node.children: if self.direction == COLUMN: - min_width, width, min_height, height = self._layout_column_children( + min_height, height, min_width, width = self._layout_children( node, - available_width=available_width, - available_height=available_height, - use_all_height=use_all_height, - use_all_width=use_all_width, + direction=COLUMN, + available_main=available_height, + available_cross=available_width, + use_all_main=use_all_height, + use_all_cross=use_all_width, ) else: - min_width, width, min_height, height = self._layout_row_children( + min_width, width, min_height, height = self._layout_children( node, - available_width=available_width, - available_height=available_height, - use_all_height=use_all_height, - use_all_width=use_all_width, + direction=ROW, + available_main=available_width, + available_cross=available_height, + use_all_main=use_all_width, + use_all_cross=use_all_height, ) # self._debug(f"HAS CHILDREN {min_width=} {width=} {min_height=} {height=}") else: @@ -427,575 +434,399 @@ def _layout_node( # self._debug("END LAYOUT", node, node.layout) self.__class__._depth -= 1 - def _layout_row_children( + def _layout_node_in_direction( self, node: Node, - available_width: int, - available_height: int, - use_all_width: bool, - use_all_height: bool, - ) -> tuple[int, int, int, int]: - # self._debug(f"LAYOUT ROW CHILDREN {available_width=} {available_height=}") - # Pass 1: Lay out all children with a hard-specified width, or an - # intrinsic non-flexible width. While iterating, collect the flex - # total of remaining elements. - flex_total = 0 - min_flex = 0 - width = 0 - min_width = 0 - remaining_width = available_width - - for i, child in enumerate(node.children): - # self._debug(f"PASS 1 {child}") - if child.style.width != NONE: - # self._debug(f"- fixed width {child.style.width}") - child.style._layout_node( - child, - alloc_width=remaining_width, - alloc_height=available_height, - use_all_width=False, - use_all_height=child.style.direction == ROW, - ) - child_content_width = child.layout.content_width - # It doesn't matter how small the children can be laid out; - # we have an intrinsic size; so don't use min_content_width - min_child_content_width = child.layout.content_width - elif child.intrinsic.width is not None: - if hasattr(child.intrinsic.width, "value"): - if child.style.flex: - # self._debug(f"- intrinsic flex width {child.intrinsic.width}") - flex_total += child.style.flex - # Final child content size will be computed in pass 2, after the - # amount of flexible space is known. For now, set an initial - # content width based on the intrinsic size, which will be the - # minimum possible allocation. - child_content_width = child.intrinsic.width.value - min_child_content_width = child.intrinsic.width.value - min_flex += ( - child.style.margin_left - + child.intrinsic.width.value - + child.style.margin_right - ) - else: - # self._debug(f"- intrinsic non-flex {child.intrinsic.width=}") - child.style._layout_node( - child, - alloc_width=0, - alloc_height=available_height, - use_all_width=False, - use_all_height=child.style.direction == ROW, - ) - child_content_width = child.layout.content_width - # It doesn't matter how small the children can be laid out; - # we have an intrinsic size; so don't use min_content_width - min_child_content_width = child.layout.content_width - else: - # self._debug(f"- intrinsic {child.intrinsic.width=}") - child.style._layout_node( - child, - alloc_width=remaining_width, - alloc_height=available_height, - use_all_width=False, - use_all_height=child.style.direction == ROW, - ) - child_content_width = child.layout.content_width - # It doesn't matter how small the children can be laid out; - # we have an intrinsic size; so don't use min_content_width - min_child_content_width = child.layout.content_width - else: - if child.style.flex: - # self._debug("- unspecified flex width") - flex_total += child.style.flex - # Final child content size will be computed in pass 2, after the - # amount of flexible space is known. For now, use 0 as the minimum, - # as that's the best hint the widget style can give. - child_content_width = 0 - min_child_content_width = 0 - else: - # self._debug("- unspecified non-flex width") - child.style._layout_node( - child, - alloc_width=remaining_width, - alloc_height=available_height, - use_all_width=False, - use_all_height=child.style.direction == ROW, - ) - child_content_width = child.layout.content_width - min_child_content_width = child.layout.min_content_width - - gap = 0 if i == 0 else self.gap - child_width = ( - child.style.margin_left + child_content_width + child.style.margin_right - ) - width += gap + child_width - remaining_width -= gap + child_width - - min_child_width = ( - child.style.margin_left - + min_child_content_width - + child.style.margin_right + direction: str, # ROW | COLUMN + alloc_main: int, + alloc_cross: int, + use_all_main: bool, + use_all_cross: bool, + ) -> None: + if direction == COLUMN: + node.style._layout_node( + node, + alloc_height=alloc_main, + alloc_width=alloc_cross, + use_all_height=use_all_main, + use_all_width=use_all_cross, ) - min_width += gap + min_child_width - - # self._debug(f" {min_child_width=} {min_width=} {min_flex=}") - # self._debug(f" {child_width=} {width=} {remaining_width=}") - - if flex_total > 0: - quantum = (remaining_width + min_flex) / flex_total - # In an ideal flex layout, all flex children will have a width proportional - # to their flex value. However, if a flex child has a flexible minimum width - # constraint that is greater than the ideal width for a balanced flex - # layout, they need to be removed from the flex calculation. - # self._debug(f"PASS 1a; {quantum=}") - for child in node.children: - if child.style.flex and child.intrinsic.width is not None: - try: - ideal_width = quantum * child.style.flex - if child.intrinsic.width.value > ideal_width: - # self._debug(f"- {child} overflows ideal width") - flex_total -= child.style.flex - min_flex -= ( - child.style.margin_left - + child.intrinsic.width.value - + child.style.margin_right - ) - except AttributeError: - # Intrinsic width isn't flexible - pass - - if flex_total > 0: - quantum = (remaining_width + min_flex) / flex_total - else: - quantum = 0 else: - quantum = 0 - # self._debug(f"END PASS 1; {min_width=} {width=} {min_flex=} {quantum=}") - - # Pass 2: Lay out children with an intrinsic flexible width, - # or no width specification at all. - for child in node.children: - # self._debug(f"PASS 2 {child}") - if child.style.width != NONE: - # self._debug("- already laid out (explicit width)") - pass - elif child.style.flex: - if child.intrinsic.width is not None: - try: - child_alloc_width = ( - child.style.margin_left - + child.intrinsic.width.value - + child.style.margin_right - ) - ideal_width = quantum * child.style.flex - # self._debug(f"- flexible intrinsic {child_alloc_width=}") - if ideal_width > child_alloc_width: - # self._debug(f" {ideal_width=}") - child_alloc_width = ideal_width - - child.style._layout_node( - child, - alloc_width=child_alloc_width, - alloc_height=available_height, - use_all_width=True, - use_all_height=child.style.direction == ROW, - ) - # Our width calculation already takes into account the intrinsic - # width; that has now expanded as a result of layout, so adjust - # to use the new layout size. Min width may also change, by the - # same scheme, because the flex child can itself have children, - # and those grandchildren have now been laid out. - # self._debug(f" sub {child.intrinsic.width.value=}") - # self._debug(f" add {child.layout.content_width=}") - # self._debug(f" add min {child.layout.min_content_width=}") - width = ( - width - - child.intrinsic.width.value - + child.layout.content_width - ) - min_width = ( - min_width - - child.intrinsic.width.value - + child.layout.min_content_width - ) - except AttributeError: - # self._debug("- already laid out (fixed intrinsic width)") - pass - else: - if quantum: - # self._debug(f"- unspecified flex width with {quantum=}") - child_alloc_width = quantum * child.style.flex - else: - # self._debug("- unspecified flex width") - child_alloc_width = ( - child.style.margin_left + child.style.margin_right - ) - - child.style._layout_node( - child, - alloc_width=child_alloc_width, - alloc_height=available_height, - use_all_width=True, - use_all_height=child.style.direction == ROW, - ) - # We now know the final min_width/width that accounts for flexible - # sizing; add that to the overall. - # self._debug(f" add {child.layout.min_content_width=}") - # self._debug(f" add {child.layout.content_width=}") - width += child.layout.content_width - min_width += child.layout.min_content_width - else: - # self._debug("- already laid out (intrinsic non-flex width)") - pass - - # self._debug(f" {min_width=} {width=}") - - # self._debug(f"PASS 2 COMPLETE; USED {width=}") - if use_all_width: - width = max(width, available_width) - # self._debug(f"COMPUTED {min_width=} {width=}") - - # Pass 3: Set the horizontal position of each child, and establish row height - offset = 0 - height = 0 - min_height = 0 - for child in node.children: - # self._debug(f"PASS 3: {child} AT HORIZONTAL {offset=}") - if node.style.text_direction == RTL: - # self._debug("- RTL") - offset += child.layout.content_width + child.style.margin_right - child.layout.content_left = width - offset - offset += child.style.margin_left - else: - # self._debug("- LTR") - offset += child.style.margin_left - child.layout.content_left = offset - offset += child.layout.content_width + child.style.margin_right - - offset += self.gap - - child_height = ( - child.style.margin_top - + child.layout.content_height - + child.style.margin_bottom - ) - height = max(height, child_height) - - min_child_height = ( - child.style.margin_top - + child.layout.min_content_height - + child.style.margin_bottom + node.style._layout_node( + node, + alloc_width=alloc_main, + alloc_height=alloc_cross, + use_all_width=use_all_main, + use_all_height=use_all_cross, ) - min_height = max(min_height, min_child_height) - - # self._debug(f"ROW {min_height=} {height=}") - if use_all_height: - height = max(height, available_height) - # self._debug(f"FINAL ROW {min_height=} {height=}") - - # Pass 4: set vertical position of each child. - for child in node.children: - # self._debug(f"PASS 4: {child}") - extra = height - ( - child.layout.content_height - + child.style.margin_top - + child.style.margin_bottom - ) - # self._debug(f"- row extra width {extra}") - if self.align_items == END: - child.layout.content_top = extra + child.style.margin_top - # self._debug(f" align {child} to bottom {child.layout.content_top=}") - elif self.align_items == CENTER: - child.layout.content_top = int(extra / 2) + child.style.margin_top - # self._debug(f" align {child} to center {child.layout.content_top=}") - else: - child.layout.content_top = child.style.margin_top - # self._debug(f" align {child} to top {child.layout.content_top=}") - return min_width, width, min_height, height - - def _layout_column_children( + def _layout_children( self, node: Node, - available_width: int, - available_height: int, - use_all_width: bool, - use_all_height: bool, - ) -> tuple[int, int, int, int]: - # self._debug(f"LAYOUT COLUMN CHILDREN {available_width=} {available_height=}") - # Pass 1: Lay out all children with a hard-specified height, or an - # intrinsic non-flexible height. While iterating, collect the flex + direction: str, # ROW | COLUMN + available_main: int, + available_cross: int, + use_all_main: bool, + use_all_cross: bool, + ) -> tuple[int, int, int, int]: # min_main, main, min_cross, cross + # Pass 1: Lay out all children with a hard-specified main-axis dimension, or an + # intrinsic non-flexible dimension. While iterating, collect the flex # total of remaining elements. flex_total = 0 min_flex = 0 - height = 0 - min_height = 0 - remaining_height = available_height + main = 0 + min_main = 0 + remaining_main = available_main + + horizontal = ( + (LEFT, RIGHT) if node.style.text_direction == LTR else (RIGHT, LEFT) + ) + if direction == COLUMN: + main_name, cross_name = "height", "width" + main_start, main_end = TOP, BOTTOM + cross_start, cross_end = horizontal + else: + main_name, cross_name = "width", "height" + main_start, main_end = horizontal + cross_start, cross_end = TOP, BOTTOM + + # self._debug( + # f"LAYOUT {direction.upper()} CHILDREN " + # f"{main_name=} {available_main=} {available_cross=}" + # ) for i, child in enumerate(node.children): # self._debug(f"PASS 1 {child}") - if child.style.height != NONE: - # self._debug(f"- fixed height {child.style.height}") - child.style._layout_node( + if child.style[main_name] != NONE: + # self._debug(f"- fixed {main_name} {child.style[main_name]}") + child.style._layout_node_in_direction( child, - alloc_width=available_width, - alloc_height=remaining_height, - use_all_width=child.style.direction == COLUMN, - use_all_height=False, + direction=direction, + alloc_main=remaining_main, + alloc_cross=available_cross, + use_all_main=False, + use_all_cross=child.style.direction == direction, ) - child_content_height = child.layout.content_height - # It doesn't matter how small the children can be laid out; - # we have an intrinsic size; so don't use min_content_height - min_child_content_height = child.layout.content_height - elif child.intrinsic.height is not None: - if hasattr(child.intrinsic.height, "value"): + child_content_main = child.layout.content(main_name) + + # It doesn't matter how small the children can be laid out; we have an + # intrinsic size; so don't use min_content.(main_name) + min_child_content_main = child.layout.content(main_name) + + elif child.intrinsic.dim(main_name) is not None: + if hasattr(child.intrinsic.dim(main_name), "value"): if child.style.flex: # self._debug( - # f"- intrinsic flex height {child.intrinsic.height}" + # f"- intrinsic flex {main_name} " + # f"{child.intrinsic.dim(main_name)=}" # ) flex_total += child.style.flex # Final child content size will be computed in pass 2, after the # amount of flexible space is known. For now, set an initial - # content height based on the intrinsic size, which will be the - # minimum possible allocation. - child_content_height = child.intrinsic.height.value - min_child_content_height = child.intrinsic.height.value + # content main-axis size based on the intrinsic size, which + # will be the minimum possible allocation. + child_content_main = child.intrinsic.dim(main_name).value + min_child_content_main = child.intrinsic.dim(main_name).value min_flex += ( - child.style.margin_top - + child_content_height - + child.style.margin_bottom + child.style[f"margin_{main_start}"] + + child_content_main + + child.style[f"margin_{main_end}"] ) else: - # self._debug(f"- intrinsic non-flex {child.intrinsic.height=}") - child.style._layout_node( + # self._debug( + # f"- intrinsic non-flex {main_name} " + # f"{child.intrinsic.dim(main_name)=}" + # ) + child.style._layout_node_in_direction( child, - alloc_width=available_width, - alloc_height=0, - use_all_width=child.style.direction == COLUMN, - use_all_height=False, + direction=direction, + alloc_main=0, + alloc_cross=available_cross, + use_all_main=False, + use_all_cross=child.style.direction == direction, ) - child_content_height = child.layout.content_height - # It doesn't matter how small the children can be laid out; - # we have an intrinsic size; so don't use min_content_height - min_child_content_height = child.layout.content_height + + child_content_main = child.layout.content(main_name) + + # It doesn't matter how small the children can be laid out; we + # have an intrinsic size; so don't use layout.min_content(main) + min_child_content_main = child.layout.content(main_name) else: - # self._debug(f"- intrinsic {child.intrinsic.height=}") - child.style._layout_node( + # self._debug( + # f"- intrinsic {main_name} {child.intrinsic.dim(main_name)=}" + # ) + child.style._layout_node_in_direction( child, - alloc_width=available_width, - alloc_height=remaining_height, - use_all_width=child.style.direction == COLUMN, - use_all_height=False, + direction=direction, + alloc_main=remaining_main, + alloc_cross=available_cross, + use_all_main=False, + use_all_cross=child.style.direction == direction, ) - child_content_height = child.layout.content_height - # It doesn't matter how small the children can be laid out; - # we have an intrinsic size; so don't use min_content_height - min_child_content_height = child.layout.content_height + + child_content_main = child.layout.content(main_name) + + # It doesn't matter how small the children can be laid out; we have + # an intrinsic size; so don't use layout.min_content(main) + min_child_content_main = child.layout.content(main_name) else: if child.style.flex: - # self._debug("- unspecified flex height") + # self._debug(f"- unspecified flex {main_name}") flex_total += child.style.flex # Final child content size will be computed in pass 2, after the # amount of flexible space is known. For now, use 0 as the minimum, # as that's the best hint the widget style can give. - child_content_height = 0 - min_child_content_height = 0 + child_content_main = 0 + min_child_content_main = 0 else: - # self._debug("- unspecified non-flex height") - child.style._layout_node( + # self._debug(f"- unspecified non-flex {main_name}") + child.style._layout_node_in_direction( child, - alloc_width=available_width, - alloc_height=remaining_height, - use_all_width=child.style.direction == COLUMN, - use_all_height=False, + direction=direction, + alloc_main=remaining_main, + alloc_cross=available_cross, + use_all_main=False, + use_all_cross=child.style.direction == direction, ) - child_content_height = child.layout.content_height - min_child_content_height = child.layout.min_content_height + child_content_main = child.layout.content(main_name) + min_child_content_main = child.layout.min_content(main_name) gap = 0 if i == 0 else self.gap - child_height = ( - child.style.margin_top - + child_content_height - + child.style.margin_bottom + child_main = ( + child.style[f"margin_{main_start}"] + + child_content_main + + child.style[f"margin_{main_end}"] ) - height += gap + child_height - remaining_height -= gap + child_height + main += gap + child_main + remaining_main -= gap + child_main - min_child_height = ( - child.style.margin_top - + min_child_content_height - + child.style.margin_bottom + min_child_main = ( + child.style[f"margin_{main_start}"] + + min_child_content_main + + child.style[f"margin_{main_end}"] ) - min_height += gap + min_child_height + min_main += gap + min_child_main - # self._debug(f" {min_child_height=} {min_height=} {min_flex=}") - # self._debug(f" {child_height=} {height=} {remaining_height=}") + # self._debug(f" {min_child_main=} {min_main=} {min_flex=}") + # self._debug(f" {child_main=} {main=} {remaining_main=}") if flex_total > 0: - quantum = (remaining_height + min_flex) / flex_total - # In an ideal flex layout, all flex children will have a height proportional - # to their flex value. However, if a flex child has a flexible minimum - # height constraint that is greater than the ideal height for a balanced - # flex layout, they need to be removed from the flex calculation. + quantum = (remaining_main + min_flex) / flex_total + # In an ideal flex layout, all flex children will have a main-axis size + # proportional to their flex value. However, if a flex child has a flexible + # minimum main-axis size constraint that is greater than the ideal + # main-axis size for a balanced flex layout, they need to be removed from + # the flex calculation. + # self._debug(f"PASS 1a; {quantum=}") for child in node.children: - if child.style.flex and child.intrinsic.height is not None: + child_intrinsic_main = child.intrinsic.dim(main_name) + if child.style.flex and child_intrinsic_main is not None: try: - ideal_height = quantum * child.style.flex - if child.intrinsic.height.value > ideal_height: - # self._debug(f"- {child} overflows ideal height") + ideal_main = quantum * child.style.flex + if child_intrinsic_main.value > ideal_main: + # self._debug(f"- {child} overflows ideal main dimension") flex_total -= child.style.flex min_flex -= ( - child.style.margin_top - + child.intrinsic.height.value - + child.style.margin_bottom + child.style[f"margin_{main_start}"] + + child_intrinsic_main.value + + child.style[f"margin_{main_end}"] ) except AttributeError: - # Intrinsic height isn't flexible + # Intrinsic main-axis size isn't flexible pass if flex_total > 0: - quantum = (min_flex + remaining_height) / flex_total + quantum = (min_flex + remaining_main) / flex_total else: quantum = 0 else: quantum = 0 - # self._debug(f"END PASS 1; {min_height=} {height=} {min_flex=} {quantum=}") + # self._debug(f"END PASS 1; {min_main=} {main=} {min_flex=} {quantum=}") - # Pass 2: Lay out children with an intrinsic flexible height, - # or no height specification at all. + # Pass 2: Lay out children with an intrinsic flexible main-axis size, or no + # main-axis size specification at all. for child in node.children: # self._debug(f"PASS 2 {child}") - if child.style.height != NONE: - # self._debug("- already laid out (explicit height)") + if child.style[main_name] != NONE: + # self._debug(f"- already laid out (explicit {main_name})") pass elif child.style.flex: - if child.intrinsic.height is not None: + if child.intrinsic.dim(main_name) is not None: try: - child_alloc_height = ( - child.style.margin_top - + child.intrinsic.height.value - + child.style.margin_bottom + child_alloc_main = ( + child.style[f"margin_{main_start}"] + + child.intrinsic.dim(main_name).value + + child.style[f"margin_{main_end}"] ) - ideal_height = quantum * child.style.flex - # self._debug(f"- flexible intrinsic {child_alloc_height=}") - if ideal_height > child_alloc_height: - # self._debug(f" {ideal_height=}") - child_alloc_height = ideal_height + ideal_main = quantum * child.style.flex + # self._debug( + # f"- flexible intrinsic {main_name} {child_alloc_main=}" + # ) + if ideal_main > child_alloc_main: + # self._debug(f" {ideal_main=}") + child_alloc_main = ideal_main - child.style._layout_node( + child.style._layout_node_in_direction( child, - alloc_width=available_width, - alloc_height=child_alloc_height, - use_all_width=child.style.direction == COLUMN, - use_all_height=True, + direction=direction, + alloc_main=child_alloc_main, + alloc_cross=available_cross, + use_all_main=True, + use_all_cross=child.style.direction == direction, ) - # Our height calculation already takes into account the - # intrinsic height; that has now expanded as a result of layout, - # so adjust to use the new layout size. Min height may also - # change, by the same scheme, because the flex child can itself - # have children, and those grandchildren have now been laid out. - # self._debug(f" sub {child.intrinsic.height.value=}") - # self._debug(f" add {child.layout.content_height}") - # self._debug(f" add min {child.layout.min_content_height}") - height = ( - height - - child.intrinsic.height.value - + child.layout.content_height + # Our main-axis dimension calculation already takes into account + # the intrinsic size; that has now expanded as a result of + # layout, so adjust to use the new layout size. Min size may + # also change, by the same scheme, because the flex child can + # itself have children, and those grandchildren have now been + # laid out. + + # self._debug( + # f" sub {child.intrinsic.dim(main_name).value=}" + # ) + # self._debug( + # f" add {child.layout.content(main_name)=}" + # ) + # self._debug( + # f" add min {child.layout.min_content(main_name)=}" + # ) + main = ( + main + - child.intrinsic.dim(main_name).value + + child.layout.content(main_name) ) - min_height = ( - min_height - - child.intrinsic.height.value - + child.layout.min_content_height + min_main = ( + min_main + - child.intrinsic.dim(main_name).value + + child.layout.min_content(main_name) ) except AttributeError: - # self._debug("- already laid out (fixed intrinsic height)") + # self._debug( + # "- already laid out (fixed intrinsic main-axis dimension)" + # ) pass else: if quantum: - # self._debug(f"- unspecified flex height with {quantum=}") - child_alloc_height = quantum * child.style.flex + # self._debug( + # f"- unspecified flex {main_name} with {quantum=}" + # ) + child_alloc_main = quantum * child.style.flex else: - # self._debug("- unspecified flex height") - child_alloc_height = ( - child.style.margin_top + child.style.margin_bottom + # self._debug(f"- unspecified flex {main_name}") + child_alloc_main = ( + child.style[f"margin_{main_start}"] + + child.style[f"margin_{main_end}"] ) - child.style._layout_node( + child.style._layout_node_in_direction( child, - alloc_width=available_width, - alloc_height=child_alloc_height, - use_all_width=child.style.direction == COLUMN, - use_all_height=True, + direction=direction, + alloc_main=child_alloc_main, + alloc_cross=available_cross, + use_all_main=True, + use_all_cross=child.style.direction == direction, ) - # We now know the final min_height/height that accounts for flexible + # We now know the final min_main/main that accounts for flexible # sizing; add that to the overall. - # self._debug(f" add {child.layout.min_content_height=}") - # self._debug(f" add {child.layout.content_height=}") - height += child.layout.content_height - min_height += child.layout.min_content_height + + # self._debug(f" add {child.layout.min_content(main_name)=}") + # self._debug(f" add {child.layout.content(main_name)=}") + main += child.layout.content(main_name) + min_main += child.layout.min_content(main_name) else: - # self._debug("- already laid out (intrinsic non-flex height)") + # self._debug(f"- already laid out (intrinsic non-flex {main_name})") pass - # self._debug(f" {min_height=} {height=}") + # self._debug(f"{main_name} {min_main=} {main=}") - # self._debug(f"PASS 2 COMPLETE; USED {height=}") - if use_all_height: - height = max(height, available_height) - # self._debug(f"COMPUTED {min_height=} {height=}") + # self._debug(f"PASS 2 COMPLETE; USED {main=} {main_name}") + if use_all_main: + main = max(main, available_main) + # self._debug(f"COMPUTED {main_name} {min_main=} {main=}") - # Pass 3: Set the vertical position of each element, and establish column width + # Pass 3: Set the main-axis position of each element, and establish box's + # cross-axis dimension offset = 0 - width = 0 - min_width = 0 + cross = 0 + min_cross = 0 for child in node.children: - # self._debug(f"PASS 3: {child} AT VERTICAL OFFSET {offset}") - offset += child.style.margin_top - child.layout.content_top = offset - offset += child.layout.content_height + child.style.margin_bottom + # self._debug(f"PASS 3: {child} AT MAIN-AXIS OFFSET {offset}") + if main_start == RIGHT: + # Needs special casing, since it's still ultimately content_left that + # needs to be set. + offset += child.layout.content_width + child.style.margin_right + child.layout.content_left = main - offset + offset += child.style.margin_left + else: + offset += child.style[f"margin_{main_start}"] + setattr(child.layout, f"content_{main_start}", offset) + offset += child.layout.content(main_name) + offset += child.style[f"margin_{main_end}"] + offset += self.gap - child_width = ( - child.layout.content_width - + child.style.margin_left - + child.style.margin_right + child_cross = ( + child.layout.content(cross_name) + + child.style[f"margin_{cross_start}"] + + child.style[f"margin_{cross_end}"] ) - width = max(width, child_width) + cross = max(cross, child_cross) - min_child_width = ( - child.style.margin_left - + child.layout.min_content_width - + child.style.margin_right + min_child_cross = ( + child.style[f"margin_{cross_start}"] + + child.layout.min_content(cross_name) + + child.style[f"margin_{cross_end}"] ) - min_width = max(min_width, min_child_width) + min_cross = max(min_cross, min_child_cross) - # self._debug(f"ROW {min_width=} {width=}") - if use_all_width: - width = max(width, available_width) - # self._debug(f"FINAL ROW {min_width=} {width=}") + # self._debug(f"{direction.upper()} {min_cross=} {cross=}") + if use_all_cross: + cross = max(cross, available_cross) + # self._debug(f"FINAL {direction.upper()} {min_width=} {width=}") + + # Pass 4: Set cross-axis position of each child. + + # Translate RTL into left-origin, which effectively flips start/end item + # alignment. + align_items = self.align_items + if cross_start == RIGHT: + cross_start = LEFT + + if align_items == START: + align_items = END + elif align_items == END: + align_items = START - # Pass 4: set horizontal position of each child. for child in node.children: # self._debug(f"PASS 4: {child}") - extra = width - ( - child.layout.content_width - + child.style.margin_left - + child.style.margin_right + extra = cross - ( + child.layout.content(cross_name) + + child.style[f"margin_{cross_start}"] + + child.style[f"margin_{cross_end}"] ) - # self._debug(f"- row extra width {extra}") - if (self.text_direction, self.align_items) in [(LTR, END), (RTL, START)]: - child.layout.content_left = extra + child.style.margin_left - # self._debug(f" align {child} to right {child.layout.content_left=}") - elif self.align_items == CENTER: - child.layout.content_left = int(extra / 2) + child.style.margin_left - # self._debug(f" align {child} to center {child.layout.content_left=}") + # self._debug(f"- {direction} extra {cross_name} {extra}") + + if align_items == END: + cross_start_value = extra + child.style[f"margin_{cross_start}"] + # self._debug(f" align {child} to {cross_end}") + + elif align_items == CENTER: + cross_start_value = ( + int(extra / 2) + child.style[f"margin_{cross_start}"] + ) + # self._debug(f" align {child} to center") + else: - child.layout.content_left = child.style.margin_left - # self._debug(f" align {child} to left {child.layout.content_left=}") + cross_start_value = child.style[f"margin_{cross_start}"] + # self._debug(f" align {child} to {cross_start} ") + + setattr(child.layout, f"content_{cross_start}", cross_start_value) + # self._debug(f" {child.layout.content(cross_start)=}") - return min_width, width, min_height, height + return min_main, main, min_cross, cross def __css__(self) -> str: css = [] From 1113d1e499d2824ac4f9d9f8896a8f4f4cb32217 Mon Sep 17 00:00:00 2001 From: Charles Whittington Date: Sat, 28 Dec 2024 00:38:53 -0500 Subject: [PATCH 02/16] Added changenote --- changes/3061.misc.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/3061.misc.rst diff --git a/changes/3061.misc.rst b/changes/3061.misc.rst new file mode 100644 index 0000000000..f0e43b09ed --- /dev/null +++ b/changes/3061.misc.rst @@ -0,0 +1 @@ +Pack's layout logic for rows and columns has been combined into one direction-agnostic method. From a3f3c00941439dcee1f9f197c94b1533ef67502a Mon Sep 17 00:00:00 2001 From: Charles Whittington Date: Sat, 28 Dec 2024 01:05:14 -0500 Subject: [PATCH 03/16] Fixed variable name in comments --- core/src/toga/style/pack.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/toga/style/pack.py b/core/src/toga/style/pack.py index 11189c3598..431dd2a140 100644 --- a/core/src/toga/style/pack.py +++ b/core/src/toga/style/pack.py @@ -549,7 +549,8 @@ def _layout_children( child_content_main = child.layout.content(main_name) # It doesn't matter how small the children can be laid out; we - # have an intrinsic size; so don't use layout.min_content(main) + # have an intrinsic size; so don't use + # layout.min_content(main_name) min_child_content_main = child.layout.content(main_name) else: # self._debug( @@ -567,7 +568,7 @@ def _layout_children( child_content_main = child.layout.content(main_name) # It doesn't matter how small the children can be laid out; we have - # an intrinsic size; so don't use layout.min_content(main) + # an intrinsic size; so don't use layout.min_content(main_name) min_child_content_main = child.layout.content(main_name) else: if child.style.flex: From 535f01936bcbfe9e7597ce89c4249cf0e15d2ec1 Mon Sep 17 00:00:00 2001 From: Charles Whittington Date: Sat, 28 Dec 2024 01:54:16 -0500 Subject: [PATCH 04/16] Changed self to node --- core/src/toga/style/pack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/toga/style/pack.py b/core/src/toga/style/pack.py index 431dd2a140..f7e4f4b140 100644 --- a/core/src/toga/style/pack.py +++ b/core/src/toga/style/pack.py @@ -792,7 +792,7 @@ def _layout_children( # Translate RTL into left-origin, which effectively flips start/end item # alignment. - align_items = self.align_items + align_items = node.align_items if cross_start == RIGHT: cross_start = LEFT From fc2eb9a76c09d44f11d1d0c6af63739faad01043 Mon Sep 17 00:00:00 2001 From: Charles Whittington Date: Sat, 28 Dec 2024 02:09:21 -0500 Subject: [PATCH 05/16] Fixed to node.style --- core/src/toga/style/pack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/toga/style/pack.py b/core/src/toga/style/pack.py index f7e4f4b140..f371dff74d 100644 --- a/core/src/toga/style/pack.py +++ b/core/src/toga/style/pack.py @@ -792,7 +792,7 @@ def _layout_children( # Translate RTL into left-origin, which effectively flips start/end item # alignment. - align_items = node.align_items + align_items = node.style.align_items if cross_start == RIGHT: cross_start = LEFT From 0f92e77005e93ec3399b9e0866a47e857fb41007 Mon Sep 17 00:00:00 2001 From: Charles Whittington Date: Sat, 28 Dec 2024 02:47:07 -0500 Subject: [PATCH 06/16] changed to class methods --- core/src/toga/style/pack.py | 173 +++++++++++++++++++----------------- 1 file changed, 89 insertions(+), 84 deletions(-) diff --git a/core/src/toga/style/pack.py b/core/src/toga/style/pack.py index f371dff74d..8d314b82b9 100644 --- a/core/src/toga/style/pack.py +++ b/core/src/toga/style/pack.py @@ -104,8 +104,9 @@ def dim(self, name): _depth = -1 - def _debug(self, *args: str) -> None: # pragma: no cover - print(" " * self.__class__._depth, *args) + @classmethod + def _debug(cls, *args: str) -> None: # pragma: no cover + print(" " * cls._depth, *args) @property def _hidden(self) -> bool: @@ -300,14 +301,15 @@ def apply(self, prop: str, value: object) -> None: # so perform a refresh. self._applicator.refresh() - def layout(self, node: Node, viewport: Any) -> None: - # self._debug("=" * 80) - # self._debug( + @classmethod + def layout(cls, node: Node, viewport: Any) -> None: + # cls._debug("=" * 80) + # cls._debug( # f"Layout root {node}, available {viewport.width}x{viewport.height}" # ) - self.__class__._depth = -1 + cls._depth = -1 - self._layout_node( + cls._layout_node( node, alloc_width=viewport.width, alloc_height=viewport.height, @@ -320,16 +322,17 @@ def layout(self, node: Node, viewport: Any) -> None: node.layout.content_left = node.style.margin_left node.layout.content_right = node.style.margin_right + @classmethod def _layout_node( - self, + cls, node: Node, alloc_width: int, alloc_height: int, use_all_width: bool, use_all_height: bool, ) -> None: - self.__class__._depth += 1 - # self._debug( + cls._depth += 1 + # cls._debug( # f"COMPUTE LAYOUT for {node} available " # f"{alloc_width}{'+' if use_all_width else ''}" # " x " @@ -337,21 +340,21 @@ def _layout_node( # ) # Establish available width - if self.width != NONE: + if node.style.width != NONE: # If width is specified, use it - available_width = self.width - min_width = self.width - # self._debug(f"SPECIFIED WIDTH {self.width}") + available_width = node.style.width + min_width = node.style.width + # cls._debug(f"SPECIFIED WIDTH {node.style.width}") else: # If no width is specified, assume we're going to use all # the available width. If there is an intrinsic width, # use it to make sure the width is at least the amount specified. available_width = max( - 0, (alloc_width - self.margin_left - self.margin_right) + 0, (alloc_width - node.style.margin_left - node.style.margin_right) ) - # self._debug(f"INITIAL {available_width=}") + # cls._debug(f"INITIAL {available_width=}") if node.intrinsic.width is not None: - # self._debug(f"INTRINSIC WIDTH {node.intrinsic.width}") + # cls._debug(f"INTRINSIC WIDTH {node.intrinsic.width}") try: min_width = node.intrinsic.width.value available_width = max(available_width, min_width) @@ -359,25 +362,25 @@ def _layout_node( available_width = node.intrinsic.width min_width = node.intrinsic.width - # self._debug(f"ADJUSTED {available_width=}") + # cls._debug(f"ADJUSTED {available_width=}") else: - # self._debug(f"AUTO {available_width=}") + # cls._debug(f"AUTO {available_width=}") min_width = 0 # Establish available height - if self.height != NONE: + if node.style.height != NONE: # If height is specified, use it. - available_height = self.height - min_height = self.height - # self._debug(f"SPECIFIED HEIGHT {self.height}") + available_height = node.style.height + min_height = node.style.height + # cls._debug(f"SPECIFIED HEIGHT {node.style.height}") else: available_height = max( 0, - alloc_height - self.margin_top - self.margin_bottom, + alloc_height - node.style.margin_top - node.style.margin_bottom, ) - # self._debug(f"INITIAL {available_height=}") + # cls._debug(f"INITIAL {available_height=}") if node.intrinsic.height is not None: - # self._debug(f"INTRINSIC HEIGHT {node.intrinsic.height}") + # cls._debug(f"INTRINSIC HEIGHT {node.intrinsic.height}") try: min_height = node.intrinsic.height.value available_height = max(available_height, min_height) @@ -385,14 +388,14 @@ def _layout_node( available_height = node.intrinsic.height min_height = node.intrinsic.height - # self._debug(f"ADJUSTED {available_height=}") + # cls._debug(f"ADJUSTED {available_height=}") else: - # self._debug(f"AUTO {available_height=}") + # cls._debug(f"AUTO {available_height=}") min_height = 0 if node.children: - if self.direction == COLUMN: - min_height, height, min_width, width = self._layout_children( + if node.style.direction == COLUMN: + min_height, height, min_width, width = cls._layout_children( node, direction=COLUMN, available_main=available_height, @@ -401,7 +404,7 @@ def _layout_node( use_all_cross=use_all_width, ) else: - min_width, width, min_height, height = self._layout_children( + min_width, width, min_height, height = cls._layout_children( node, direction=ROW, available_main=available_width, @@ -409,33 +412,34 @@ def _layout_node( use_all_main=use_all_width, use_all_cross=use_all_height, ) - # self._debug(f"HAS CHILDREN {min_width=} {width=} {min_height=} {height=}") + # cls._debug(f"HAS CHILDREN {min_width=} {width=} {min_height=} {height=}") else: width = available_width height = available_height - # self._debug(f"NO CHILDREN {min_width=} {width=} {min_height=} {height=}") + # cls._debug(f"NO CHILDREN {min_width=} {width=} {min_height=} {height=}") # If an explicit width/height was given, that specification # overrides the width/height evaluated by the layout of children - if self.width != NONE: - width = self.width + if node.style.width != NONE: + width = node.style.width min_width = width - if self.height != NONE: - height = self.height + if node.style.height != NONE: + height = node.style.height min_height = height - # self._debug(f"FINAL SIZE {min_width}x{min_height} {width}x{height}") + # cls._debug(f"FINAL SIZE {min_width}x{min_height} {width}x{height}") node.layout.content_width = int(width) node.layout.content_height = int(height) node.layout.min_content_width = int(min_width) node.layout.min_content_height = int(min_height) - # self._debug("END LAYOUT", node, node.layout) - self.__class__._depth -= 1 + # cls._debug("END LAYOUT", node, node.layout) + cls._depth -= 1 + @classmethod def _layout_node_in_direction( - self, + cls, node: Node, direction: str, # ROW | COLUMN alloc_main: int, @@ -444,7 +448,7 @@ def _layout_node_in_direction( use_all_cross: bool, ) -> None: if direction == COLUMN: - node.style._layout_node( + cls._layout_node( node, alloc_height=alloc_main, alloc_width=alloc_cross, @@ -452,7 +456,7 @@ def _layout_node_in_direction( use_all_width=use_all_cross, ) else: - node.style._layout_node( + cls._layout_node( node, alloc_width=alloc_main, alloc_height=alloc_cross, @@ -460,8 +464,9 @@ def _layout_node_in_direction( use_all_height=use_all_cross, ) + @classmethod def _layout_children( - self, + cls, node: Node, direction: str, # ROW | COLUMN available_main: int, @@ -490,15 +495,15 @@ def _layout_children( main_start, main_end = horizontal cross_start, cross_end = TOP, BOTTOM - # self._debug( + # cls._debug( # f"LAYOUT {direction.upper()} CHILDREN " # f"{main_name=} {available_main=} {available_cross=}" # ) for i, child in enumerate(node.children): - # self._debug(f"PASS 1 {child}") + # cls._debug(f"PASS 1 {child}") if child.style[main_name] != NONE: - # self._debug(f"- fixed {main_name} {child.style[main_name]}") + # cls._debug(f"- fixed {main_name} {child.style[main_name]}") child.style._layout_node_in_direction( child, direction=direction, @@ -516,7 +521,7 @@ def _layout_children( elif child.intrinsic.dim(main_name) is not None: if hasattr(child.intrinsic.dim(main_name), "value"): if child.style.flex: - # self._debug( + # cls._debug( # f"- intrinsic flex {main_name} " # f"{child.intrinsic.dim(main_name)=}" # ) @@ -533,7 +538,7 @@ def _layout_children( + child.style[f"margin_{main_end}"] ) else: - # self._debug( + # cls._debug( # f"- intrinsic non-flex {main_name} " # f"{child.intrinsic.dim(main_name)=}" # ) @@ -553,7 +558,7 @@ def _layout_children( # layout.min_content(main_name) min_child_content_main = child.layout.content(main_name) else: - # self._debug( + # cls._debug( # f"- intrinsic {main_name} {child.intrinsic.dim(main_name)=}" # ) child.style._layout_node_in_direction( @@ -572,7 +577,7 @@ def _layout_children( min_child_content_main = child.layout.content(main_name) else: if child.style.flex: - # self._debug(f"- unspecified flex {main_name}") + # cls._debug(f"- unspecified flex {main_name}") flex_total += child.style.flex # Final child content size will be computed in pass 2, after the # amount of flexible space is known. For now, use 0 as the minimum, @@ -580,7 +585,7 @@ def _layout_children( child_content_main = 0 min_child_content_main = 0 else: - # self._debug(f"- unspecified non-flex {main_name}") + # cls._debug(f"- unspecified non-flex {main_name}") child.style._layout_node_in_direction( child, direction=direction, @@ -592,7 +597,7 @@ def _layout_children( child_content_main = child.layout.content(main_name) min_child_content_main = child.layout.min_content(main_name) - gap = 0 if i == 0 else self.gap + gap = 0 if i == 0 else node.style.gap child_main = ( child.style[f"margin_{main_start}"] + child_content_main @@ -608,8 +613,8 @@ def _layout_children( ) min_main += gap + min_child_main - # self._debug(f" {min_child_main=} {min_main=} {min_flex=}") - # self._debug(f" {child_main=} {main=} {remaining_main=}") + # cls._debug(f" {min_child_main=} {min_main=} {min_flex=}") + # cls._debug(f" {child_main=} {main=} {remaining_main=}") if flex_total > 0: quantum = (remaining_main + min_flex) / flex_total @@ -619,14 +624,14 @@ def _layout_children( # main-axis size for a balanced flex layout, they need to be removed from # the flex calculation. - # self._debug(f"PASS 1a; {quantum=}") + # cls._debug(f"PASS 1a; {quantum=}") for child in node.children: child_intrinsic_main = child.intrinsic.dim(main_name) if child.style.flex and child_intrinsic_main is not None: try: ideal_main = quantum * child.style.flex if child_intrinsic_main.value > ideal_main: - # self._debug(f"- {child} overflows ideal main dimension") + # cls._debug(f"- {child} overflows ideal main dimension") flex_total -= child.style.flex min_flex -= ( child.style[f"margin_{main_start}"] @@ -644,14 +649,14 @@ def _layout_children( else: quantum = 0 - # self._debug(f"END PASS 1; {min_main=} {main=} {min_flex=} {quantum=}") + # cls._debug(f"END PASS 1; {min_main=} {main=} {min_flex=} {quantum=}") # Pass 2: Lay out children with an intrinsic flexible main-axis size, or no # main-axis size specification at all. for child in node.children: - # self._debug(f"PASS 2 {child}") + # cls._debug(f"PASS 2 {child}") if child.style[main_name] != NONE: - # self._debug(f"- already laid out (explicit {main_name})") + # cls._debug(f"- already laid out (explicit {main_name})") pass elif child.style.flex: if child.intrinsic.dim(main_name) is not None: @@ -662,11 +667,11 @@ def _layout_children( + child.style[f"margin_{main_end}"] ) ideal_main = quantum * child.style.flex - # self._debug( + # cls._debug( # f"- flexible intrinsic {main_name} {child_alloc_main=}" # ) if ideal_main > child_alloc_main: - # self._debug(f" {ideal_main=}") + # cls._debug(f" {ideal_main=}") child_alloc_main = ideal_main child.style._layout_node_in_direction( @@ -684,13 +689,13 @@ def _layout_children( # itself have children, and those grandchildren have now been # laid out. - # self._debug( + # cls._debug( # f" sub {child.intrinsic.dim(main_name).value=}" # ) - # self._debug( + # cls._debug( # f" add {child.layout.content(main_name)=}" # ) - # self._debug( + # cls._debug( # f" add min {child.layout.min_content(main_name)=}" # ) main = ( @@ -704,18 +709,18 @@ def _layout_children( + child.layout.min_content(main_name) ) except AttributeError: - # self._debug( + # cls._debug( # "- already laid out (fixed intrinsic main-axis dimension)" # ) pass else: if quantum: - # self._debug( + # cls._debug( # f"- unspecified flex {main_name} with {quantum=}" # ) child_alloc_main = quantum * child.style.flex else: - # self._debug(f"- unspecified flex {main_name}") + # cls._debug(f"- unspecified flex {main_name}") child_alloc_main = ( child.style[f"margin_{main_start}"] + child.style[f"margin_{main_end}"] @@ -732,21 +737,21 @@ def _layout_children( # We now know the final min_main/main that accounts for flexible # sizing; add that to the overall. - # self._debug(f" add {child.layout.min_content(main_name)=}") - # self._debug(f" add {child.layout.content(main_name)=}") + # cls._debug(f" add {child.layout.min_content(main_name)=}") + # cls._debug(f" add {child.layout.content(main_name)=}") main += child.layout.content(main_name) min_main += child.layout.min_content(main_name) else: - # self._debug(f"- already laid out (intrinsic non-flex {main_name})") + # cls._debug(f"- already laid out (intrinsic non-flex {main_name})") pass - # self._debug(f"{main_name} {min_main=} {main=}") + # cls._debug(f"{main_name} {min_main=} {main=}") - # self._debug(f"PASS 2 COMPLETE; USED {main=} {main_name}") + # cls._debug(f"PASS 2 COMPLETE; USED {main=} {main_name}") if use_all_main: main = max(main, available_main) - # self._debug(f"COMPUTED {main_name} {min_main=} {main=}") + # cls._debug(f"COMPUTED {main_name} {min_main=} {main=}") # Pass 3: Set the main-axis position of each element, and establish box's # cross-axis dimension @@ -754,7 +759,7 @@ def _layout_children( cross = 0 min_cross = 0 for child in node.children: - # self._debug(f"PASS 3: {child} AT MAIN-AXIS OFFSET {offset}") + # cls._debug(f"PASS 3: {child} AT MAIN-AXIS OFFSET {offset}") if main_start == RIGHT: # Needs special casing, since it's still ultimately content_left that # needs to be set. @@ -767,7 +772,7 @@ def _layout_children( offset += child.layout.content(main_name) offset += child.style[f"margin_{main_end}"] - offset += self.gap + offset += node.style.gap child_cross = ( child.layout.content(cross_name) @@ -783,10 +788,10 @@ def _layout_children( ) min_cross = max(min_cross, min_child_cross) - # self._debug(f"{direction.upper()} {min_cross=} {cross=}") + # cls._debug(f"{direction.upper()} {min_cross=} {cross=}") if use_all_cross: cross = max(cross, available_cross) - # self._debug(f"FINAL {direction.upper()} {min_width=} {width=}") + # cls._debug(f"FINAL {direction.upper()} {min_width=} {width=}") # Pass 4: Set cross-axis position of each child. @@ -802,30 +807,30 @@ def _layout_children( align_items = START for child in node.children: - # self._debug(f"PASS 4: {child}") + # cls._debug(f"PASS 4: {child}") extra = cross - ( child.layout.content(cross_name) + child.style[f"margin_{cross_start}"] + child.style[f"margin_{cross_end}"] ) - # self._debug(f"- {direction} extra {cross_name} {extra}") + # cls._debug(f"- {direction} extra {cross_name} {extra}") if align_items == END: cross_start_value = extra + child.style[f"margin_{cross_start}"] - # self._debug(f" align {child} to {cross_end}") + # cls._debug(f" align {child} to {cross_end}") elif align_items == CENTER: cross_start_value = ( int(extra / 2) + child.style[f"margin_{cross_start}"] ) - # self._debug(f" align {child} to center") + # cls._debug(f" align {child} to center") else: cross_start_value = child.style[f"margin_{cross_start}"] - # self._debug(f" align {child} to {cross_start} ") + # cls._debug(f" align {child} to {cross_start} ") setattr(child.layout, f"content_{cross_start}", cross_start_value) - # self._debug(f" {child.layout.content(cross_start)=}") + # cls._debug(f" {child.layout.content(cross_start)=}") return min_main, main, min_cross, cross From 37a07213a528cb89e8c92ee7e1e3bd05f277849a Mon Sep 17 00:00:00 2001 From: Charles Whittington Date: Sat, 28 Dec 2024 13:08:25 -0500 Subject: [PATCH 07/16] Restore arguments and only do conditional check in one place --- core/src/toga/style/pack.py | 106 ++++++++++++++++++------------------ 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/core/src/toga/style/pack.py b/core/src/toga/style/pack.py index 8d314b82b9..e81f757d0a 100644 --- a/core/src/toga/style/pack.py +++ b/core/src/toga/style/pack.py @@ -60,6 +60,8 @@ # Define here, since they're not available in Travertino 0.3.0 START = "start" END = "end" +HEIGHT = "height" +WIDTH = "width" # Used in backwards compatibility section below ALIGNMENT = "alignment" @@ -394,24 +396,13 @@ def _layout_node( min_height = 0 if node.children: - if node.style.direction == COLUMN: - min_height, height, min_width, width = cls._layout_children( - node, - direction=COLUMN, - available_main=available_height, - available_cross=available_width, - use_all_main=use_all_height, - use_all_cross=use_all_width, - ) - else: - min_width, width, min_height, height = cls._layout_children( - node, - direction=ROW, - available_main=available_width, - available_cross=available_height, - use_all_main=use_all_width, - use_all_cross=use_all_height, - ) + min_width, width, min_height, height = cls._layout_children( + node, + available_width=available_width, + available_height=available_height, + use_all_width=use_all_width, + use_all_height=use_all_height, + ) # cls._debug(f"HAS CHILDREN {min_width=} {width=} {min_height=} {height=}") else: width = available_width @@ -468,35 +459,41 @@ def _layout_node_in_direction( def _layout_children( cls, node: Node, - direction: str, # ROW | COLUMN - available_main: int, - available_cross: int, - use_all_main: bool, - use_all_cross: bool, - ) -> tuple[int, int, int, int]: # min_main, main, min_cross, cross - # Pass 1: Lay out all children with a hard-specified main-axis dimension, or an - # intrinsic non-flexible dimension. While iterating, collect the flex - # total of remaining elements. - flex_total = 0 - min_flex = 0 - main = 0 - min_main = 0 - remaining_main = available_main - + available_width: int, + available_height: int, + use_all_width: bool, + use_all_height: bool, + ) -> tuple[int, int, int, int]: # min_width, width, min_height, height + # Assign the appropriate dimensions to main and cross axes, depending on row / + # column direction. horizontal = ( (LEFT, RIGHT) if node.style.text_direction == LTR else (RIGHT, LEFT) ) - if direction == COLUMN: - main_name, cross_name = "height", "width" + if node.style.direction == COLUMN: + available_main, available_cross = available_height, available_width + use_all_main, use_all_cross = use_all_height, use_all_width + main_name, cross_name = HEIGHT, WIDTH main_start, main_end = TOP, BOTTOM cross_start, cross_end = horizontal else: - main_name, cross_name = "width", "height" + available_main, available_cross = available_width, available_height + use_all_main, use_all_cross = use_all_width, use_all_height + main_name, cross_name = WIDTH, HEIGHT main_start, main_end = horizontal cross_start, cross_end = TOP, BOTTOM + flex_total = 0 + min_flex = 0 + main = 0 + min_main = 0 + remaining_main = available_main + + # Pass 1: Lay out all children with a hard-specified main-axis dimension, or an + # intrinsic non-flexible dimension. While iterating, collect the flex + # total of remaining elements. + # cls._debug( - # f"LAYOUT {direction.upper()} CHILDREN " + # f"LAYOUT {node.style.direction.upper()} CHILDREN " # f"{main_name=} {available_main=} {available_cross=}" # ) @@ -506,11 +503,11 @@ def _layout_children( # cls._debug(f"- fixed {main_name} {child.style[main_name]}") child.style._layout_node_in_direction( child, - direction=direction, + direction=node.style.direction, alloc_main=remaining_main, alloc_cross=available_cross, use_all_main=False, - use_all_cross=child.style.direction == direction, + use_all_cross=child.style.direction == node.style.direction, ) child_content_main = child.layout.content(main_name) @@ -544,11 +541,11 @@ def _layout_children( # ) child.style._layout_node_in_direction( child, - direction=direction, + direction=node.style.direction, alloc_main=0, alloc_cross=available_cross, use_all_main=False, - use_all_cross=child.style.direction == direction, + use_all_cross=child.style.direction == node.style.direction, ) child_content_main = child.layout.content(main_name) @@ -563,11 +560,11 @@ def _layout_children( # ) child.style._layout_node_in_direction( child, - direction=direction, + direction=node.style.direction, alloc_main=remaining_main, alloc_cross=available_cross, use_all_main=False, - use_all_cross=child.style.direction == direction, + use_all_cross=child.style.direction == node.style.direction, ) child_content_main = child.layout.content(main_name) @@ -588,11 +585,11 @@ def _layout_children( # cls._debug(f"- unspecified non-flex {main_name}") child.style._layout_node_in_direction( child, - direction=direction, + direction=node.style.direction, alloc_main=remaining_main, alloc_cross=available_cross, use_all_main=False, - use_all_cross=child.style.direction == direction, + use_all_cross=child.style.direction == node.style.direction, ) child_content_main = child.layout.content(main_name) min_child_content_main = child.layout.min_content(main_name) @@ -676,11 +673,11 @@ def _layout_children( child.style._layout_node_in_direction( child, - direction=direction, + direction=node.style.direction, alloc_main=child_alloc_main, alloc_cross=available_cross, use_all_main=True, - use_all_cross=child.style.direction == direction, + use_all_cross=child.style.direction == node.style.direction, ) # Our main-axis dimension calculation already takes into account # the intrinsic size; that has now expanded as a result of @@ -728,11 +725,11 @@ def _layout_children( child.style._layout_node_in_direction( child, - direction=direction, + direction=node.style.direction, alloc_main=child_alloc_main, alloc_cross=available_cross, use_all_main=True, - use_all_cross=child.style.direction == direction, + use_all_cross=child.style.direction == node.style.direction, ) # We now know the final min_main/main that accounts for flexible # sizing; add that to the overall. @@ -788,10 +785,10 @@ def _layout_children( ) min_cross = max(min_cross, min_child_cross) - # cls._debug(f"{direction.upper()} {min_cross=} {cross=}") + # cls._debug(f"{node.style.direction.upper()} {min_cross=} {cross=}") if use_all_cross: cross = max(cross, available_cross) - # cls._debug(f"FINAL {direction.upper()} {min_width=} {width=}") + # cls._debug(f"FINAL {node.style.direction.upper()} {min_width=} {width=}") # Pass 4: Set cross-axis position of each child. @@ -813,7 +810,7 @@ def _layout_children( + child.style[f"margin_{cross_start}"] + child.style[f"margin_{cross_end}"] ) - # cls._debug(f"- {direction} extra {cross_name} {extra}") + # cls._debug(f"- {node.style.direction} extra {cross_name} {extra}") if align_items == END: cross_start_value = extra + child.style[f"margin_{cross_start}"] @@ -832,7 +829,10 @@ def _layout_children( setattr(child.layout, f"content_{cross_start}", cross_start_value) # cls._debug(f" {child.layout.content(cross_start)=}") - return min_main, main, min_cross, cross + if node.style.direction == COLUMN: + return min_cross, cross, min_main, main + else: + return min_main, main, min_cross, cross def __css__(self) -> str: css = [] From 13116a61c643f11c8b6d9056e0968df2a9994e4d Mon Sep 17 00:00:00 2001 From: Charles Whittington Date: Sat, 28 Dec 2024 13:10:48 -0500 Subject: [PATCH 08/16] Restore debug/comment order --- core/src/toga/style/pack.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/toga/style/pack.py b/core/src/toga/style/pack.py index e81f757d0a..f7b2beaeb0 100644 --- a/core/src/toga/style/pack.py +++ b/core/src/toga/style/pack.py @@ -488,15 +488,15 @@ def _layout_children( min_main = 0 remaining_main = available_main - # Pass 1: Lay out all children with a hard-specified main-axis dimension, or an - # intrinsic non-flexible dimension. While iterating, collect the flex - # total of remaining elements. - # cls._debug( # f"LAYOUT {node.style.direction.upper()} CHILDREN " # f"{main_name=} {available_main=} {available_cross=}" # ) + # Pass 1: Lay out all children with a hard-specified main-axis dimension, or an + # intrinsic non-flexible dimension. While iterating, collect the flex + # total of remaining elements. + for i, child in enumerate(node.children): # cls._debug(f"PASS 1 {child}") if child.style[main_name] != NONE: From 7f9b4b79b37c3404fa558310b8270a9a1daedccf Mon Sep 17 00:00:00 2001 From: Charles Whittington Date: Sat, 28 Dec 2024 21:58:31 -0500 Subject: [PATCH 09/16] Ditched constants, underscore getters, regular methods without node arg --- core/src/toga/style/pack.py | 318 +++++++++--------- core/tests/style/pack/layout/test_beeliza.py | 2 +- .../pack/layout/test_column_alignment.py | 10 +- core/tests/style/pack/layout/test_fixed.py | 8 +- core/tests/style/pack/layout/test_flex.py | 20 +- core/tests/style/pack/layout/test_gap.py | 8 +- .../style/pack/layout/test_row_alignment.py | 10 +- core/tests/style/pack/layout/test_rtl.py | 12 +- .../tests/style/pack/layout/test_tutorial0.py | 4 +- .../tests/style/pack/layout/test_tutorial1.py | 2 +- .../tests/style/pack/layout/test_tutorial3.py | 2 +- 11 files changed, 196 insertions(+), 200 deletions(-) diff --git a/core/src/toga/style/pack.py b/core/src/toga/style/pack.py index f7b2beaeb0..9d2dfa6a0e 100644 --- a/core/src/toga/style/pack.py +++ b/core/src/toga/style/pack.py @@ -32,7 +32,6 @@ ) from travertino.declaration import BaseStyle, Choices from travertino.layout import BaseBox -from travertino.node import Node from travertino.size import BaseIntrinsicSize from toga.fonts import ( @@ -60,8 +59,6 @@ # Define here, since they're not available in Travertino 0.3.0 START = "start" END = "end" -HEIGHT = "height" -WIDTH = "width" # Used in backwards compatibility section below ALIGNMENT = "alignment" @@ -94,14 +91,14 @@ class Pack(BaseStyle): class Box(BaseBox): - def content(self, name): + def _content(self, name): return getattr(self, f"content_{name}") - def min_content(self, name): + def _min_content(self, name): return getattr(self, f"min_content_{name}") class IntrinsicSize(BaseIntrinsicSize): - def dim(self, name): + def _dim(self, name): return getattr(self, name) _depth = -1 @@ -303,60 +300,73 @@ def apply(self, prop: str, value: object) -> None: # so perform a refresh. self._applicator.refresh() - @classmethod - def layout(cls, node: Node, viewport: Any) -> None: - # cls._debug("=" * 80) - # cls._debug( + def layout(self, viewport: Any, _deprecated_usage=None) -> None: + ###################################################################### + # 2024-12: Backwards compatibility for Travertino 0.3.0 + ###################################################################### + + if _deprecated_usage is not None: + # Was called with (self, viewport) + viewport = _deprecated_usage + + ###################################################################### + # End backwards compatibility + ###################################################################### + + # self._debug("=" * 80) + # self._debug( # f"Layout root {node}, available {viewport.width}x{viewport.height}" # ) - cls._depth = -1 + self.__class__._depth = -1 - cls._layout_node( - node, + self._layout_node( alloc_width=viewport.width, alloc_height=viewport.height, use_all_height=True, # root node uses all height use_all_width=True, # root node uses all width ) - node.layout.content_top = node.style.margin_top - node.layout.content_bottom = node.style.margin_bottom - node.layout.content_left = node.style.margin_left - node.layout.content_right = node.style.margin_right + node = self._applicator.node + + node.layout.content_top = self.margin_top + node.layout.content_bottom = self.margin_bottom + + node.layout.content_left = self.margin_left + node.layout.content_right = self.margin_right - @classmethod def _layout_node( - cls, - node: Node, + self, alloc_width: int, alloc_height: int, use_all_width: bool, use_all_height: bool, ) -> None: - cls._depth += 1 - # cls._debug( + self.__class__._depth += 1 + # self._debug( # f"COMPUTE LAYOUT for {node} available " # f"{alloc_width}{'+' if use_all_width else ''}" # " x " # f"{alloc_height}{'+' if use_all_height else ''}" # ) + node = self._applicator.node + # Establish available width - if node.style.width != NONE: + if self.width != NONE: # If width is specified, use it - available_width = node.style.width - min_width = node.style.width - # cls._debug(f"SPECIFIED WIDTH {node.style.width}") + available_width = self.width + min_width = self.width + # self._debug(f"SPECIFIED WIDTH {self.width}") else: # If no width is specified, assume we're going to use all # the available width. If there is an intrinsic width, # use it to make sure the width is at least the amount specified. available_width = max( - 0, (alloc_width - node.style.margin_left - node.style.margin_right) + 0, (alloc_width - self.margin_left - self.margin_right) ) - # cls._debug(f"INITIAL {available_width=}") + # self._debug(f"INITIAL {available_width=}") if node.intrinsic.width is not None: - # cls._debug(f"INTRINSIC WIDTH {node.intrinsic.width}") + # self._debug(f"INTRINSIC WIDTH {node.intrinsic.width}") try: min_width = node.intrinsic.width.value available_width = max(available_width, min_width) @@ -364,25 +374,25 @@ def _layout_node( available_width = node.intrinsic.width min_width = node.intrinsic.width - # cls._debug(f"ADJUSTED {available_width=}") + # self._debug(f"ADJUSTED {available_width=}") else: - # cls._debug(f"AUTO {available_width=}") + # self._debug(f"AUTO {available_width=}") min_width = 0 # Establish available height - if node.style.height != NONE: + if self.height != NONE: # If height is specified, use it. - available_height = node.style.height - min_height = node.style.height - # cls._debug(f"SPECIFIED HEIGHT {node.style.height}") + available_height = self.height + min_height = self.height + # self._debug(f"SPECIFIED HEIGHT {self.height}") else: available_height = max( 0, - alloc_height - node.style.margin_top - node.style.margin_bottom, + alloc_height - self.margin_top - self.margin_bottom, ) - # cls._debug(f"INITIAL {available_height=}") + # self._debug(f"INITIAL {available_height=}") if node.intrinsic.height is not None: - # cls._debug(f"INTRINSIC HEIGHT {node.intrinsic.height}") + # self._debug(f"INTRINSIC HEIGHT {node.intrinsic.height}") try: min_height = node.intrinsic.height.value available_height = max(available_height, min_height) @@ -390,48 +400,45 @@ def _layout_node( available_height = node.intrinsic.height min_height = node.intrinsic.height - # cls._debug(f"ADJUSTED {available_height=}") + # self._debug(f"ADJUSTED {available_height=}") else: - # cls._debug(f"AUTO {available_height=}") + # self._debug(f"AUTO {available_height=}") min_height = 0 if node.children: - min_width, width, min_height, height = cls._layout_children( - node, + min_width, width, min_height, height = self._layout_children( available_width=available_width, available_height=available_height, use_all_width=use_all_width, use_all_height=use_all_height, ) - # cls._debug(f"HAS CHILDREN {min_width=} {width=} {min_height=} {height=}") + # self._debug(f"HAS CHILDREN {min_width=} {width=} {min_height=} {height=}") else: width = available_width height = available_height - # cls._debug(f"NO CHILDREN {min_width=} {width=} {min_height=} {height=}") + # self._debug(f"NO CHILDREN {min_width=} {width=} {min_height=} {height=}") # If an explicit width/height was given, that specification # overrides the width/height evaluated by the layout of children - if node.style.width != NONE: - width = node.style.width + if self.width != NONE: + width = self.width min_width = width - if node.style.height != NONE: - height = node.style.height + if self.height != NONE: + height = self.height min_height = height - # cls._debug(f"FINAL SIZE {min_width}x{min_height} {width}x{height}") + # self._debug(f"FINAL SIZE {min_width}x{min_height} {width}x{height}") node.layout.content_width = int(width) node.layout.content_height = int(height) node.layout.min_content_width = int(min_width) node.layout.min_content_height = int(min_height) - # cls._debug("END LAYOUT", node, node.layout) - cls._depth -= 1 + # self._debug("END LAYOUT", node, node.layout) + self.__class__._depth -= 1 - @classmethod def _layout_node_in_direction( - cls, - node: Node, + self, direction: str, # ROW | COLUMN alloc_main: int, alloc_cross: int, @@ -439,26 +446,22 @@ def _layout_node_in_direction( use_all_cross: bool, ) -> None: if direction == COLUMN: - cls._layout_node( - node, + self._layout_node( alloc_height=alloc_main, alloc_width=alloc_cross, use_all_height=use_all_main, use_all_width=use_all_cross, ) else: - cls._layout_node( - node, + self._layout_node( alloc_width=alloc_main, alloc_height=alloc_cross, use_all_width=use_all_main, use_all_height=use_all_cross, ) - @classmethod def _layout_children( - cls, - node: Node, + self, available_width: int, available_height: int, use_all_width: bool, @@ -466,30 +469,29 @@ def _layout_children( ) -> tuple[int, int, int, int]: # min_width, width, min_height, height # Assign the appropriate dimensions to main and cross axes, depending on row / # column direction. - horizontal = ( - (LEFT, RIGHT) if node.style.text_direction == LTR else (RIGHT, LEFT) - ) - if node.style.direction == COLUMN: + horizontal = (LEFT, RIGHT) if self.text_direction == LTR else (RIGHT, LEFT) + if self.direction == COLUMN: available_main, available_cross = available_height, available_width use_all_main, use_all_cross = use_all_height, use_all_width - main_name, cross_name = HEIGHT, WIDTH + main_name, cross_name = "height", "width" main_start, main_end = TOP, BOTTOM cross_start, cross_end = horizontal else: available_main, available_cross = available_width, available_height use_all_main, use_all_cross = use_all_width, use_all_height - main_name, cross_name = WIDTH, HEIGHT + main_name, cross_name = "width", "height" main_start, main_end = horizontal cross_start, cross_end = TOP, BOTTOM + node = self._applicator.node flex_total = 0 min_flex = 0 main = 0 min_main = 0 remaining_main = available_main - # cls._debug( - # f"LAYOUT {node.style.direction.upper()} CHILDREN " + # self._debug( + # f"LAYOUT {self.direction.upper()} CHILDREN " # f"{main_name=} {available_main=} {available_cross=}" # ) @@ -498,83 +500,80 @@ def _layout_children( # total of remaining elements. for i, child in enumerate(node.children): - # cls._debug(f"PASS 1 {child}") + # self._debug(f"PASS 1 {child}") if child.style[main_name] != NONE: - # cls._debug(f"- fixed {main_name} {child.style[main_name]}") + # self._debug(f"- fixed {main_name} {child.style[main_name]}") child.style._layout_node_in_direction( - child, - direction=node.style.direction, + direction=self.direction, alloc_main=remaining_main, alloc_cross=available_cross, use_all_main=False, - use_all_cross=child.style.direction == node.style.direction, + use_all_cross=child.style.direction == self.direction, ) - child_content_main = child.layout.content(main_name) + child_content_main = child.layout._content(main_name) # It doesn't matter how small the children can be laid out; we have an # intrinsic size; so don't use min_content.(main_name) - min_child_content_main = child.layout.content(main_name) + min_child_content_main = child.layout._content(main_name) - elif child.intrinsic.dim(main_name) is not None: - if hasattr(child.intrinsic.dim(main_name), "value"): + elif child.intrinsic._dim(main_name) is not None: + if hasattr(child.intrinsic._dim(main_name), "value"): if child.style.flex: - # cls._debug( + # self._debug( # f"- intrinsic flex {main_name} " - # f"{child.intrinsic.dim(main_name)=}" + # f"{child.intrinsic._dim(main_name)=}" # ) flex_total += child.style.flex # Final child content size will be computed in pass 2, after the # amount of flexible space is known. For now, set an initial # content main-axis size based on the intrinsic size, which # will be the minimum possible allocation. - child_content_main = child.intrinsic.dim(main_name).value - min_child_content_main = child.intrinsic.dim(main_name).value + child_content_main = child.intrinsic._dim(main_name).value + min_child_content_main = child.intrinsic._dim(main_name).value min_flex += ( child.style[f"margin_{main_start}"] + child_content_main + child.style[f"margin_{main_end}"] ) else: - # cls._debug( + # self._debug( # f"- intrinsic non-flex {main_name} " - # f"{child.intrinsic.dim(main_name)=}" + # f"{child.intrinsic._dim(main_name)=}" # ) child.style._layout_node_in_direction( - child, - direction=node.style.direction, + direction=self.direction, alloc_main=0, alloc_cross=available_cross, use_all_main=False, - use_all_cross=child.style.direction == node.style.direction, + use_all_cross=child.style.direction == self.direction, ) - child_content_main = child.layout.content(main_name) + child_content_main = child.layout._content(main_name) # It doesn't matter how small the children can be laid out; we # have an intrinsic size; so don't use - # layout.min_content(main_name) - min_child_content_main = child.layout.content(main_name) + # layout._min_content(main_name) + min_child_content_main = child.layout._content(main_name) else: - # cls._debug( - # f"- intrinsic {main_name} {child.intrinsic.dim(main_name)=}" + # self._debug( + # f"- intrinsic {main_name} {child.intrinsic._dim(main_name)=}" # ) child.style._layout_node_in_direction( - child, - direction=node.style.direction, + direction=self.direction, alloc_main=remaining_main, alloc_cross=available_cross, use_all_main=False, - use_all_cross=child.style.direction == node.style.direction, + use_all_cross=child.style.direction == self.direction, ) - child_content_main = child.layout.content(main_name) + child_content_main = child.layout._content(main_name) # It doesn't matter how small the children can be laid out; we have - # an intrinsic size; so don't use layout.min_content(main_name) - min_child_content_main = child.layout.content(main_name) + # an intrinsic size; so don't use layout._min_content(main_name) + min_child_content_main = child.layout._content(main_name) else: if child.style.flex: - # cls._debug(f"- unspecified flex {main_name}") + # self._debug(f"- unspecified flex {main_name}") flex_total += child.style.flex # Final child content size will be computed in pass 2, after the # amount of flexible space is known. For now, use 0 as the minimum, @@ -582,19 +581,18 @@ def _layout_children( child_content_main = 0 min_child_content_main = 0 else: - # cls._debug(f"- unspecified non-flex {main_name}") + # self._debug(f"- unspecified non-flex {main_name}") child.style._layout_node_in_direction( - child, - direction=node.style.direction, + direction=self.direction, alloc_main=remaining_main, alloc_cross=available_cross, use_all_main=False, - use_all_cross=child.style.direction == node.style.direction, + use_all_cross=child.style.direction == self.direction, ) - child_content_main = child.layout.content(main_name) - min_child_content_main = child.layout.min_content(main_name) + child_content_main = child.layout._content(main_name) + min_child_content_main = child.layout._min_content(main_name) - gap = 0 if i == 0 else node.style.gap + gap = 0 if i == 0 else self.gap child_main = ( child.style[f"margin_{main_start}"] + child_content_main @@ -610,8 +608,8 @@ def _layout_children( ) min_main += gap + min_child_main - # cls._debug(f" {min_child_main=} {min_main=} {min_flex=}") - # cls._debug(f" {child_main=} {main=} {remaining_main=}") + # self._debug(f" {min_child_main=} {min_main=} {min_flex=}") + # self._debug(f" {child_main=} {main=} {remaining_main=}") if flex_total > 0: quantum = (remaining_main + min_flex) / flex_total @@ -621,14 +619,14 @@ def _layout_children( # main-axis size for a balanced flex layout, they need to be removed from # the flex calculation. - # cls._debug(f"PASS 1a; {quantum=}") + # self._debug(f"PASS 1a; {quantum=}") for child in node.children: - child_intrinsic_main = child.intrinsic.dim(main_name) + child_intrinsic_main = child.intrinsic._dim(main_name) if child.style.flex and child_intrinsic_main is not None: try: ideal_main = quantum * child.style.flex if child_intrinsic_main.value > ideal_main: - # cls._debug(f"- {child} overflows ideal main dimension") + # self._debug(f"- {child} overflows ideal main dimension") flex_total -= child.style.flex min_flex -= ( child.style[f"margin_{main_start}"] @@ -646,38 +644,37 @@ def _layout_children( else: quantum = 0 - # cls._debug(f"END PASS 1; {min_main=} {main=} {min_flex=} {quantum=}") + # self._debug(f"END PASS 1; {min_main=} {main=} {min_flex=} {quantum=}") # Pass 2: Lay out children with an intrinsic flexible main-axis size, or no # main-axis size specification at all. for child in node.children: - # cls._debug(f"PASS 2 {child}") + # self._debug(f"PASS 2 {child}") if child.style[main_name] != NONE: - # cls._debug(f"- already laid out (explicit {main_name})") + # self._debug(f"- already laid out (explicit {main_name})") pass elif child.style.flex: - if child.intrinsic.dim(main_name) is not None: + if child.intrinsic._dim(main_name) is not None: try: child_alloc_main = ( child.style[f"margin_{main_start}"] - + child.intrinsic.dim(main_name).value + + child.intrinsic._dim(main_name).value + child.style[f"margin_{main_end}"] ) ideal_main = quantum * child.style.flex - # cls._debug( + # self._debug( # f"- flexible intrinsic {main_name} {child_alloc_main=}" # ) if ideal_main > child_alloc_main: - # cls._debug(f" {ideal_main=}") + # self._debug(f" {ideal_main=}") child_alloc_main = ideal_main child.style._layout_node_in_direction( - child, - direction=node.style.direction, + direction=self.direction, alloc_main=child_alloc_main, alloc_cross=available_cross, use_all_main=True, - use_all_cross=child.style.direction == node.style.direction, + use_all_cross=child.style.direction == self.direction, ) # Our main-axis dimension calculation already takes into account # the intrinsic size; that has now expanded as a result of @@ -686,69 +683,68 @@ def _layout_children( # itself have children, and those grandchildren have now been # laid out. - # cls._debug( - # f" sub {child.intrinsic.dim(main_name).value=}" + # self._debug( + # f" sub {child.intrinsic._dim(main_name).value=}" # ) - # cls._debug( - # f" add {child.layout.content(main_name)=}" + # self._debug( + # f" add {child.layout._content(main_name)=}" # ) - # cls._debug( - # f" add min {child.layout.min_content(main_name)=}" + # self._debug( + # f" add min {child.layout._min_content(main_name)=}" # ) main = ( main - - child.intrinsic.dim(main_name).value - + child.layout.content(main_name) + - child.intrinsic._dim(main_name).value + + child.layout._content(main_name) ) min_main = ( min_main - - child.intrinsic.dim(main_name).value - + child.layout.min_content(main_name) + - child.intrinsic._dim(main_name).value + + child.layout._min_content(main_name) ) except AttributeError: - # cls._debug( + # self._debug( # "- already laid out (fixed intrinsic main-axis dimension)" # ) pass else: if quantum: - # cls._debug( + # self._debug( # f"- unspecified flex {main_name} with {quantum=}" # ) child_alloc_main = quantum * child.style.flex else: - # cls._debug(f"- unspecified flex {main_name}") + # self._debug(f"- unspecified flex {main_name}") child_alloc_main = ( child.style[f"margin_{main_start}"] + child.style[f"margin_{main_end}"] ) child.style._layout_node_in_direction( - child, - direction=node.style.direction, + direction=self.direction, alloc_main=child_alloc_main, alloc_cross=available_cross, use_all_main=True, - use_all_cross=child.style.direction == node.style.direction, + use_all_cross=child.style.direction == self.direction, ) # We now know the final min_main/main that accounts for flexible # sizing; add that to the overall. - # cls._debug(f" add {child.layout.min_content(main_name)=}") - # cls._debug(f" add {child.layout.content(main_name)=}") - main += child.layout.content(main_name) - min_main += child.layout.min_content(main_name) + # self._debug(f" add {child.layout._min_content(main_name)=}") + # self._debug(f" add {child.layout._content(main_name)=}") + main += child.layout._content(main_name) + min_main += child.layout._min_content(main_name) else: - # cls._debug(f"- already laid out (intrinsic non-flex {main_name})") + # self._debug(f"- already laid out (intrinsic non-flex {main_name})") pass - # cls._debug(f"{main_name} {min_main=} {main=}") + # self._debug(f"{main_name} {min_main=} {main=}") - # cls._debug(f"PASS 2 COMPLETE; USED {main=} {main_name}") + # self._debug(f"PASS 2 COMPLETE; USED {main=} {main_name}") if use_all_main: main = max(main, available_main) - # cls._debug(f"COMPUTED {main_name} {min_main=} {main=}") + # self._debug(f"COMPUTED {main_name} {min_main=} {main=}") # Pass 3: Set the main-axis position of each element, and establish box's # cross-axis dimension @@ -756,7 +752,7 @@ def _layout_children( cross = 0 min_cross = 0 for child in node.children: - # cls._debug(f"PASS 3: {child} AT MAIN-AXIS OFFSET {offset}") + # self._debug(f"PASS 3: {child} AT MAIN-AXIS OFFSET {offset}") if main_start == RIGHT: # Needs special casing, since it's still ultimately content_left that # needs to be set. @@ -766,13 +762,13 @@ def _layout_children( else: offset += child.style[f"margin_{main_start}"] setattr(child.layout, f"content_{main_start}", offset) - offset += child.layout.content(main_name) + offset += child.layout._content(main_name) offset += child.style[f"margin_{main_end}"] - offset += node.style.gap + offset += self.gap child_cross = ( - child.layout.content(cross_name) + child.layout._content(cross_name) + child.style[f"margin_{cross_start}"] + child.style[f"margin_{cross_end}"] ) @@ -780,21 +776,21 @@ def _layout_children( min_child_cross = ( child.style[f"margin_{cross_start}"] - + child.layout.min_content(cross_name) + + child.layout._min_content(cross_name) + child.style[f"margin_{cross_end}"] ) min_cross = max(min_cross, min_child_cross) - # cls._debug(f"{node.style.direction.upper()} {min_cross=} {cross=}") + # self._debug(f"{self.direction.upper()} {min_cross=} {cross=}") if use_all_cross: cross = max(cross, available_cross) - # cls._debug(f"FINAL {node.style.direction.upper()} {min_width=} {width=}") + # self._debug(f"FINAL {self.direction.upper()} {min_width=} {width=}") # Pass 4: Set cross-axis position of each child. # Translate RTL into left-origin, which effectively flips start/end item # alignment. - align_items = node.style.align_items + align_items = self.align_items if cross_start == RIGHT: cross_start = LEFT @@ -804,32 +800,32 @@ def _layout_children( align_items = START for child in node.children: - # cls._debug(f"PASS 4: {child}") + # self._debug(f"PASS 4: {child}") extra = cross - ( - child.layout.content(cross_name) + child.layout._content(cross_name) + child.style[f"margin_{cross_start}"] + child.style[f"margin_{cross_end}"] ) - # cls._debug(f"- {node.style.direction} extra {cross_name} {extra}") + # self._debug(f"- {self.direction} extra {cross_name} {extra}") if align_items == END: cross_start_value = extra + child.style[f"margin_{cross_start}"] - # cls._debug(f" align {child} to {cross_end}") + # self._debug(f" align {child} to {cross_end}") elif align_items == CENTER: cross_start_value = ( int(extra / 2) + child.style[f"margin_{cross_start}"] ) - # cls._debug(f" align {child} to center") + # self._debug(f" align {child} to center") else: cross_start_value = child.style[f"margin_{cross_start}"] - # cls._debug(f" align {child} to {cross_start} ") + # self._debug(f" align {child} to {cross_start} ") setattr(child.layout, f"content_{cross_start}", cross_start_value) - # cls._debug(f" {child.layout.content(cross_start)=}") + # self._debug(f" {child.layout._content(cross_start)=}") - if node.style.direction == COLUMN: + if self.direction == COLUMN: return min_cross, cross, min_main, main else: return min_main, main, min_cross, cross diff --git a/core/tests/style/pack/layout/test_beeliza.py b/core/tests/style/pack/layout/test_beeliza.py index 985afbcf73..93499ebbb1 100644 --- a/core/tests/style/pack/layout/test_beeliza.py +++ b/core/tests/style/pack/layout/test_beeliza.py @@ -32,7 +32,7 @@ def test_beeliza(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (160, 125), diff --git a/core/tests/style/pack/layout/test_column_alignment.py b/core/tests/style/pack/layout/test_column_alignment.py index d3d2fd23e8..0978af73cf 100644 --- a/core/tests/style/pack/layout/test_column_alignment.py +++ b/core/tests/style/pack/layout/test_column_alignment.py @@ -26,7 +26,7 @@ def test_left(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (540, 300), @@ -72,7 +72,7 @@ def test_center(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (540, 300), @@ -118,7 +118,7 @@ def test_right(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (540, 300), @@ -151,7 +151,7 @@ def test_no_margin(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (540, 300), @@ -194,7 +194,7 @@ def test_column_box(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (430, 310), diff --git a/core/tests/style/pack/layout/test_fixed.py b/core/tests/style/pack/layout/test_fixed.py index 2c33bf0a32..29a19034cd 100644 --- a/core/tests/style/pack/layout/test_fixed.py +++ b/core/tests/style/pack/layout/test_fixed.py @@ -37,7 +37,7 @@ def test_row_expanding_intrinsic(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (200, 100), @@ -97,7 +97,7 @@ def test_row_fixed_intrinsic(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (150, 100), @@ -159,7 +159,7 @@ def test_column_expanding_intrinsic(): ) # Normal size - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (100, 200), @@ -216,7 +216,7 @@ def test_column_fixed_intrinsic(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (100, 150), diff --git a/core/tests/style/pack/layout/test_flex.py b/core/tests/style/pack/layout/test_flex.py index 682746163d..0563ab0f20 100644 --- a/core/tests/style/pack/layout/test_flex.py +++ b/core/tests/style/pack/layout/test_flex.py @@ -29,7 +29,7 @@ def test_row_flex_no_hints(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (50, 50), @@ -104,7 +104,7 @@ def test_row_flex(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (300, 100), @@ -184,7 +184,7 @@ def test_row_flex_insufficient_space(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (200, 100), @@ -249,7 +249,7 @@ def test_row_flex_insufficient_space_no_flex(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (220, 100), @@ -302,7 +302,7 @@ def test_row_flex_grandchild_min_size(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (120, 100), @@ -351,7 +351,7 @@ def test_column_flex_no_hints(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (50, 50), @@ -426,7 +426,7 @@ def test_column_flex(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (100, 300), @@ -507,7 +507,7 @@ def test_column_flex_insufficient_space(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (100, 200), @@ -573,7 +573,7 @@ def test_column_flex_insufficient_space_no_flex(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (100, 220), @@ -627,7 +627,7 @@ def test_column_flex_grandchild_min_size(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (100, 120), diff --git a/core/tests/style/pack/layout/test_gap.py b/core/tests/style/pack/layout/test_gap.py index cc81444b39..e79a4387ad 100644 --- a/core/tests/style/pack/layout/test_gap.py +++ b/core/tests/style/pack/layout/test_gap.py @@ -24,7 +24,7 @@ def test_gap(): ) # No gap - root.style.layout(root, viewport) + root.style.layout(viewport) assert_layout( root, (130, 100), @@ -45,7 +45,7 @@ def test_gap(): # Row, LTR root.style.update(direction="row", text_direction="ltr") - root.style.layout(root, viewport) + root.style.layout(viewport) assert_layout( root, (170, 100), @@ -63,7 +63,7 @@ def test_gap(): # Row, RTL root.style.update(direction="row", text_direction="rtl") - root.style.layout(root, viewport) + root.style.layout(viewport) assert_layout( root, (170, 100), @@ -81,7 +81,7 @@ def test_gap(): # Column root.style.update(direction="column") - root.style.layout(root, viewport) + root.style.layout(viewport) assert_layout( root, (100, 170), diff --git a/core/tests/style/pack/layout/test_row_alignment.py b/core/tests/style/pack/layout/test_row_alignment.py index c157085d1a..2550a48167 100644 --- a/core/tests/style/pack/layout/test_row_alignment.py +++ b/core/tests/style/pack/layout/test_row_alignment.py @@ -26,7 +26,7 @@ def test_top(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (300, 540), @@ -72,7 +72,7 @@ def test_center(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (300, 540), @@ -118,7 +118,7 @@ def test_bottom(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (300, 540), @@ -151,7 +151,7 @@ def test_no_margin(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (300, 540), @@ -194,7 +194,7 @@ def test_row_box(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (310, 430), diff --git a/core/tests/style/pack/layout/test_rtl.py b/core/tests/style/pack/layout/test_rtl.py index 8f740c5826..7141873c4d 100644 --- a/core/tests/style/pack/layout/test_rtl.py +++ b/core/tests/style/pack/layout/test_rtl.py @@ -19,7 +19,7 @@ def test_row_box_child_layout(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (210, 100), @@ -52,7 +52,7 @@ def test_column_box_child_layout(): ) # Normal size - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (100, 210), @@ -78,7 +78,7 @@ def test_align_items_top(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (60, 100), @@ -104,7 +104,7 @@ def test_align_items_bottom(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (60, 100), @@ -130,7 +130,7 @@ def test_align_items_left(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (100, 60), @@ -156,7 +156,7 @@ def test_align_items_right(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (100, 60), diff --git a/core/tests/style/pack/layout/test_tutorial0.py b/core/tests/style/pack/layout/test_tutorial0.py index 00d04eac06..ec9d1588a7 100644 --- a/core/tests/style/pack/layout/test_tutorial0.py +++ b/core/tests/style/pack/layout/test_tutorial0.py @@ -16,7 +16,7 @@ def test_tutorial_0(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (220, 130), @@ -41,7 +41,7 @@ def test_vertical(): ], ) - root.style.layout(root, ExampleViewport(480, 640)) + root.style.layout(ExampleViewport(480, 640)) assert_layout( root, (130, 220), diff --git a/core/tests/style/pack/layout/test_tutorial1.py b/core/tests/style/pack/layout/test_tutorial1.py index 7992cba5f2..24e7199fd1 100644 --- a/core/tests/style/pack/layout/test_tutorial1.py +++ b/core/tests/style/pack/layout/test_tutorial1.py @@ -51,7 +51,7 @@ def test_tutorial_1(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (380, 120), diff --git a/core/tests/style/pack/layout/test_tutorial3.py b/core/tests/style/pack/layout/test_tutorial3.py index 2d7b0d1563..8d1c5d8302 100644 --- a/core/tests/style/pack/layout/test_tutorial3.py +++ b/core/tests/style/pack/layout/test_tutorial3.py @@ -30,7 +30,7 @@ def test_tutorial_3(): ], ) - root.style.layout(root, ExampleViewport(640, 480)) + root.style.layout(ExampleViewport(640, 480)) assert_layout( root, (170, 125), From e415f8b1c84c6cc8704c178ce78354d4d653b86b Mon Sep 17 00:00:00 2001 From: Charles Whittington Date: Sat, 28 Dec 2024 22:27:16 -0500 Subject: [PATCH 10/16] Switched back to getattr --- core/src/toga/style/pack.py | 90 ++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/core/src/toga/style/pack.py b/core/src/toga/style/pack.py index 9d2dfa6a0e..2ffd21d341 100644 --- a/core/src/toga/style/pack.py +++ b/core/src/toga/style/pack.py @@ -91,15 +91,10 @@ class Pack(BaseStyle): class Box(BaseBox): - def _content(self, name): - return getattr(self, f"content_{name}") - - def _min_content(self, name): - return getattr(self, f"min_content_{name}") + pass class IntrinsicSize(BaseIntrinsicSize): - def _dim(self, name): - return getattr(self, name) + pass _depth = -1 @@ -510,26 +505,27 @@ def _layout_children( use_all_main=False, use_all_cross=child.style.direction == self.direction, ) - child_content_main = child.layout._content(main_name) + child_content_main = getattr(child.layout, f"content_{main_name}") # It doesn't matter how small the children can be laid out; we have an # intrinsic size; so don't use min_content.(main_name) - min_child_content_main = child.layout._content(main_name) + min_child_content_main = getattr(child.layout, f"content_{main_name}") - elif child.intrinsic._dim(main_name) is not None: - if hasattr(child.intrinsic._dim(main_name), "value"): + elif getattr(child.intrinsic, main_name) is not None: + if hasattr(getattr(child.intrinsic, main_name), "value"): if child.style.flex: # self._debug( # f"- intrinsic flex {main_name} " - # f"{child.intrinsic._dim(main_name)=}" + # f"{getattr(child.intrinsic, main_name)=}" # ) flex_total += child.style.flex # Final child content size will be computed in pass 2, after the # amount of flexible space is known. For now, set an initial # content main-axis size based on the intrinsic size, which # will be the minimum possible allocation. - child_content_main = child.intrinsic._dim(main_name).value - min_child_content_main = child.intrinsic._dim(main_name).value + child_content_main = getattr(child.intrinsic, main_name).value + min_child_content_main = child_content_main + min_flex += ( child.style[f"margin_{main_start}"] + child_content_main @@ -538,7 +534,7 @@ def _layout_children( else: # self._debug( # f"- intrinsic non-flex {main_name} " - # f"{child.intrinsic._dim(main_name)=}" + # f"{getattr(child.intrinsic, main_name)=}" # ) child.style._layout_node_in_direction( direction=self.direction, @@ -548,15 +544,18 @@ def _layout_children( use_all_cross=child.style.direction == self.direction, ) - child_content_main = child.layout._content(main_name) + child_content_main = getattr( + child.layout, f"content_{main_name}" + ) # It doesn't matter how small the children can be laid out; we # have an intrinsic size; so don't use # layout._min_content(main_name) - min_child_content_main = child.layout._content(main_name) + min_child_content_main = child_content_main else: # self._debug( - # f"- intrinsic {main_name} {child.intrinsic._dim(main_name)=}" + # f"- intrinsic {main_name} " + # f"{getattr(child.intrinsic, main_name)=}" # ) child.style._layout_node_in_direction( direction=self.direction, @@ -566,11 +565,11 @@ def _layout_children( use_all_cross=child.style.direction == self.direction, ) - child_content_main = child.layout._content(main_name) + child_content_main = getattr(child.layout, f"content_{main_name}") # It doesn't matter how small the children can be laid out; we have # an intrinsic size; so don't use layout._min_content(main_name) - min_child_content_main = child.layout._content(main_name) + min_child_content_main = child_content_main else: if child.style.flex: # self._debug(f"- unspecified flex {main_name}") @@ -589,8 +588,10 @@ def _layout_children( use_all_main=False, use_all_cross=child.style.direction == self.direction, ) - child_content_main = child.layout._content(main_name) - min_child_content_main = child.layout._min_content(main_name) + child_content_main = getattr(child.layout, f"content_{main_name}") + min_child_content_main = getattr( + child.layout, f"min_content_{main_name}" + ) gap = 0 if i == 0 else self.gap child_main = ( @@ -621,7 +622,7 @@ def _layout_children( # self._debug(f"PASS 1a; {quantum=}") for child in node.children: - child_intrinsic_main = child.intrinsic._dim(main_name) + child_intrinsic_main = getattr(child.intrinsic, main_name) if child.style.flex and child_intrinsic_main is not None: try: ideal_main = quantum * child.style.flex @@ -654,11 +655,11 @@ def _layout_children( # self._debug(f"- already laid out (explicit {main_name})") pass elif child.style.flex: - if child.intrinsic._dim(main_name) is not None: + if getattr(child.intrinsic, main_name) is not None: try: child_alloc_main = ( child.style[f"margin_{main_start}"] - + child.intrinsic._dim(main_name).value + + getattr(child.intrinsic, main_name).value + child.style[f"margin_{main_end}"] ) ideal_main = quantum * child.style.flex @@ -684,23 +685,24 @@ def _layout_children( # laid out. # self._debug( - # f" sub {child.intrinsic._dim(main_name).value=}" + # f" sub {getattr(child.intrinsic, main_name).value=}" # ) # self._debug( - # f" add {child.layout._content(main_name)=}" + # f" add {getattr(child.layout, f'content_{main_name}')=}" # ) # self._debug( - # f" add min {child.layout._min_content(main_name)=}" + # f" add min " + # f"{getattr(child.layout, f'min_content_{main_name}')=}" # ) main = ( main - - child.intrinsic._dim(main_name).value - + child.layout._content(main_name) + - getattr(child.intrinsic, main_name).value + + getattr(child.layout, f"content_{main_name}") ) min_main = ( min_main - - child.intrinsic._dim(main_name).value - + child.layout._min_content(main_name) + - getattr(child.intrinsic, main_name).value + + getattr(child.layout, f"min_content_{main_name}") ) except AttributeError: # self._debug( @@ -730,10 +732,14 @@ def _layout_children( # We now know the final min_main/main that accounts for flexible # sizing; add that to the overall. - # self._debug(f" add {child.layout._min_content(main_name)=}") - # self._debug(f" add {child.layout._content(main_name)=}") - main += child.layout._content(main_name) - min_main += child.layout._min_content(main_name) + # self._debug( + # f" add {getattr(child.layout, f'min_content_{main_name}')=}" + # ) + # self._debug( + # f" add {getattr(child.layout, f'content_{main_name}')=}" + # ) + main += getattr(child.layout, f"content_{main_name}") + min_main += getattr(child.layout, f"min_content_{main_name}") else: # self._debug(f"- already laid out (intrinsic non-flex {main_name})") @@ -762,13 +768,13 @@ def _layout_children( else: offset += child.style[f"margin_{main_start}"] setattr(child.layout, f"content_{main_start}", offset) - offset += child.layout._content(main_name) + offset += getattr(child.layout, f"content_{main_name}") offset += child.style[f"margin_{main_end}"] offset += self.gap child_cross = ( - child.layout._content(cross_name) + getattr(child.layout, f"content_{cross_name}") + child.style[f"margin_{cross_start}"] + child.style[f"margin_{cross_end}"] ) @@ -776,7 +782,7 @@ def _layout_children( min_child_cross = ( child.style[f"margin_{cross_start}"] - + child.layout._min_content(cross_name) + + getattr(child.layout, f"min_content_{cross_name}") + child.style[f"margin_{cross_end}"] ) min_cross = max(min_cross, min_child_cross) @@ -784,7 +790,7 @@ def _layout_children( # self._debug(f"{self.direction.upper()} {min_cross=} {cross=}") if use_all_cross: cross = max(cross, available_cross) - # self._debug(f"FINAL {self.direction.upper()} {min_width=} {width=}") + # self._debug(f"FINAL {self.direction.upper()} {min_cross=} {cross=}") # Pass 4: Set cross-axis position of each child. @@ -802,7 +808,7 @@ def _layout_children( for child in node.children: # self._debug(f"PASS 4: {child}") extra = cross - ( - child.layout._content(cross_name) + getattr(child.layout, f"content_{cross_name}") + child.style[f"margin_{cross_start}"] + child.style[f"margin_{cross_end}"] ) @@ -823,7 +829,7 @@ def _layout_children( # self._debug(f" align {child} to {cross_start} ") setattr(child.layout, f"content_{cross_start}", cross_start_value) - # self._debug(f" {child.layout._content(cross_start)=}") + # self._debug(f" {getattr(child.layout, f'content_{cross_start}')=}") if self.direction == COLUMN: return min_cross, cross, min_main, main From 78c2055fcede78f4daecfac2c7a6a1d5eecec221 Mon Sep 17 00:00:00 2001 From: Charles Whittington Date: Sat, 28 Dec 2024 22:43:39 -0500 Subject: [PATCH 11/16] Fixed deprecation warning and added test --- core/src/toga/style/pack.py | 6 ++++++ .../style/pack/layout/test_deprecated_layout.py | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 core/tests/style/pack/layout/test_deprecated_layout.py diff --git a/core/src/toga/style/pack.py b/core/src/toga/style/pack.py index 2ffd21d341..8b518018d1 100644 --- a/core/src/toga/style/pack.py +++ b/core/src/toga/style/pack.py @@ -302,6 +302,12 @@ def layout(self, viewport: Any, _deprecated_usage=None) -> None: if _deprecated_usage is not None: # Was called with (self, viewport) + warnings.warn( + "Pack.layout() should be called with the viewport as the only " + "argument; the (node, viewport) signature is deprecated.", + DeprecationWarning, + stacklevel=3, + ) viewport = _deprecated_usage ###################################################################### diff --git a/core/tests/style/pack/layout/test_deprecated_layout.py b/core/tests/style/pack/layout/test_deprecated_layout.py new file mode 100644 index 0000000000..6090879c87 --- /dev/null +++ b/core/tests/style/pack/layout/test_deprecated_layout.py @@ -0,0 +1,16 @@ +import pytest + +from toga.style.pack import Pack + +from ..utils import ExampleNode, ExampleViewport + + +def test_deprecated_layout_signature(): + """Calling .layout() with two arguments causes a deprecation warning.""" + + node = ExampleNode(name="Old Fogey", style=Pack()) + with pytest.warns( + DeprecationWarning, + match=r"\(node, viewport\) signature is deprecated", + ): + node.style.layout(node, ExampleViewport(50, 50)) From e76c8e676a245ab67789749a73cf5b39b6591083 Mon Sep 17 00:00:00 2001 From: Charles Whittington Date: Sat, 28 Dec 2024 22:52:39 -0500 Subject: [PATCH 12/16] Removed deprecation warning? --- core/src/toga/style/pack.py | 6 ------ .../style/pack/layout/test_deprecated_layout.py | 16 ---------------- 2 files changed, 22 deletions(-) delete mode 100644 core/tests/style/pack/layout/test_deprecated_layout.py diff --git a/core/src/toga/style/pack.py b/core/src/toga/style/pack.py index 8b518018d1..2ffd21d341 100644 --- a/core/src/toga/style/pack.py +++ b/core/src/toga/style/pack.py @@ -302,12 +302,6 @@ def layout(self, viewport: Any, _deprecated_usage=None) -> None: if _deprecated_usage is not None: # Was called with (self, viewport) - warnings.warn( - "Pack.layout() should be called with the viewport as the only " - "argument; the (node, viewport) signature is deprecated.", - DeprecationWarning, - stacklevel=3, - ) viewport = _deprecated_usage ###################################################################### diff --git a/core/tests/style/pack/layout/test_deprecated_layout.py b/core/tests/style/pack/layout/test_deprecated_layout.py deleted file mode 100644 index 6090879c87..0000000000 --- a/core/tests/style/pack/layout/test_deprecated_layout.py +++ /dev/null @@ -1,16 +0,0 @@ -import pytest - -from toga.style.pack import Pack - -from ..utils import ExampleNode, ExampleViewport - - -def test_deprecated_layout_signature(): - """Calling .layout() with two arguments causes a deprecation warning.""" - - node = ExampleNode(name="Old Fogey", style=Pack()) - with pytest.warns( - DeprecationWarning, - match=r"\(node, viewport\) signature is deprecated", - ): - node.style.layout(node, ExampleViewport(50, 50)) From f3faf7402b11c5879b12ee570dd8e70cda1cc752 Mon Sep 17 00:00:00 2001 From: Charles Whittington Date: Wed, 1 Jan 2025 10:49:18 -0500 Subject: [PATCH 13/16] Inlined _initial_content now that it's only called once --- core/src/toga/style/pack.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/core/src/toga/style/pack.py b/core/src/toga/style/pack.py index 8ef8488760..65ae1ffa32 100644 --- a/core/src/toga/style/pack.py +++ b/core/src/toga/style/pack.py @@ -756,7 +756,13 @@ def _layout_children( # Pass 3: Set the main-axis position of each element, and establish box's # cross-axis dimension - offset = self._initial_offset(extra) + if self.justify_content == END: + offset = extra + elif self.justify_content == CENTER: + offset = extra / 2 + else: # START + offset = 0 + cross = 0 min_cross = 0 @@ -839,14 +845,6 @@ def _layout_children( else: return min_main, main, min_cross, cross - def _initial_offset(self, extra): - if self.justify_content == END: - return extra - elif self.justify_content == CENTER: - return extra / 2 - else: # START - return 0 - def __css__(self) -> str: css = [] # display From 422ac12759cf0ff9963f0dd0d06ce1572fd4eb67 Mon Sep 17 00:00:00 2001 From: Charles Whittington Date: Wed, 1 Jan 2025 11:41:06 -0500 Subject: [PATCH 14/16] Added changenote about layout signature change --- changes/{3016.misc.rst => 3016.misc.1.rst} | 0 changes/3061.misc.2.rst | 1 + 2 files changed, 1 insertion(+) rename changes/{3016.misc.rst => 3016.misc.1.rst} (100%) create mode 100644 changes/3061.misc.2.rst diff --git a/changes/3016.misc.rst b/changes/3016.misc.1.rst similarity index 100% rename from changes/3016.misc.rst rename to changes/3016.misc.1.rst diff --git a/changes/3061.misc.2.rst b/changes/3061.misc.2.rst new file mode 100644 index 0000000000..f4ca3b46e7 --- /dev/null +++ b/changes/3061.misc.2.rst @@ -0,0 +1 @@ +Pack.layout() now has a single argument -- the viewport -- instead of (node, viewport). From 630da0109f70e7e8bac57100dfcd230853f679a5 Mon Sep 17 00:00:00 2001 From: Charles Whittington Date: Wed, 1 Jan 2025 22:16:55 -0500 Subject: [PATCH 15/16] Added layer of 'translation' values for horizontal cross-axis in RTL --- core/pyproject.toml | 2 +- core/src/toga/style/pack.py | 34 +++++++++++++++++++++------------- tox.ini | 2 +- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/core/pyproject.toml b/core/pyproject.toml index f0bba62dcd..322f2e4a70 100644 --- a/core/pyproject.toml +++ b/core/pyproject.toml @@ -56,7 +56,7 @@ classifiers = [ "Topic :: Software Development :: Widget Sets", ] dependencies = [ - "travertino >= 0.3.0, < 0.4.0", + "travertino", ] [project.optional-dependencies] diff --git a/core/src/toga/style/pack.py b/core/src/toga/style/pack.py index 65ae1ffa32..eafeed4e37 100644 --- a/core/src/toga/style/pack.py +++ b/core/src/toga/style/pack.py @@ -803,31 +803,39 @@ def _layout_children( # Pass 4: Set cross-axis position of each child. - # Translate RTL into left-origin, which effectively flips start/end item - # alignment. - align_items = self.align_items + # The "effective" start, end, and align-items values are normally their "real" + # values. However, if the cross-axis is horizontal and text-direction RTL, + # they're flipped. This is necessary because final positioning is always set + # using a top-left origin, even if the "real" start is on the right. + effective_align_items = self.align_items + if cross_start == RIGHT: - cross_start = LEFT + effective_cross_start = LEFT + effective_cross_end = RIGHT + + if self.align_items == START: + effective_align_items = END + elif self.align_items == END: + effective_align_items = START - if align_items == START: - align_items = END - elif align_items == END: - align_items = START + else: + effective_cross_start = cross_start + effective_cross_end = cross_end for child in node.children: # self._debug(f"PASS 4: {child}") extra = cross - ( getattr(child.layout, f"content_{cross_name}") - + child.style[f"margin_{cross_start}"] - + child.style[f"margin_{cross_end}"] + + child.style[f"margin_{effective_cross_start}"] + + child.style[f"margin_{effective_cross_end}"] ) # self._debug(f"- {self.direction} extra {cross_name} {extra}") - if align_items == END: + if effective_align_items == END: cross_start_value = extra + child.style[f"margin_{cross_start}"] # self._debug(f" align {child} to {cross_end}") - elif align_items == CENTER: + elif effective_align_items == CENTER: cross_start_value = ( int(extra / 2) + child.style[f"margin_{cross_start}"] ) @@ -837,7 +845,7 @@ def _layout_children( cross_start_value = child.style[f"margin_{cross_start}"] # self._debug(f" align {child} to {cross_start} ") - setattr(child.layout, f"content_{cross_start}", cross_start_value) + setattr(child.layout, f"content_{effective_cross_start}", cross_start_value) # self._debug(f" {getattr(child.layout, f'content_{cross_start}')=}") if self.direction == COLUMN: diff --git a/tox.ini b/tox.ini index 847c4a8107..e51a26763f 100644 --- a/tox.ini +++ b/tox.ini @@ -36,7 +36,7 @@ allowlist_externals = commands = # TOGA_INSTALL_COMMAND is set to a bash command by the CI workflow # Install as editable so parallel runs don't clobber the build directory for each other - {env:TOGA_INSTALL_COMMAND:python -m pip install {tox_root}{/}core[dev] {tox_root}{/}dummy} + {env:TOGA_INSTALL_COMMAND:python -m pip install {tox_root}{/}core[dev] {tox_root}{/}dummy} {/}Users{/}charles{/}toga_dev{/}travertino !cov: python -m pytest {posargs:-vv --color yes} cov : python -m coverage run -m pytest {posargs:-vv --color yes} From 0930e819ff5b995159880fbfd0961d21c9aadc1f Mon Sep 17 00:00:00 2001 From: Charles Whittington Date: Wed, 1 Jan 2025 22:39:59 -0500 Subject: [PATCH 16/16] Removed debugging setup --- core/pyproject.toml | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/pyproject.toml b/core/pyproject.toml index 322f2e4a70..f0bba62dcd 100644 --- a/core/pyproject.toml +++ b/core/pyproject.toml @@ -56,7 +56,7 @@ classifiers = [ "Topic :: Software Development :: Widget Sets", ] dependencies = [ - "travertino", + "travertino >= 0.3.0, < 0.4.0", ] [project.optional-dependencies] diff --git a/tox.ini b/tox.ini index e51a26763f..847c4a8107 100644 --- a/tox.ini +++ b/tox.ini @@ -36,7 +36,7 @@ allowlist_externals = commands = # TOGA_INSTALL_COMMAND is set to a bash command by the CI workflow # Install as editable so parallel runs don't clobber the build directory for each other - {env:TOGA_INSTALL_COMMAND:python -m pip install {tox_root}{/}core[dev] {tox_root}{/}dummy} {/}Users{/}charles{/}toga_dev{/}travertino + {env:TOGA_INSTALL_COMMAND:python -m pip install {tox_root}{/}core[dev] {tox_root}{/}dummy} !cov: python -m pytest {posargs:-vv --color yes} cov : python -m coverage run -m pytest {posargs:-vv --color yes}