1111 TYPE_CHECKING ,
1212 Any ,
1313 Collection ,
14+ Literal ,
1415 NamedTuple ,
1516 Tuple ,
1617 TypeVar ,
@@ -986,20 +987,23 @@ def inflect(
986987 A positive value will move the region right or down, a negative value will move
987988 the region left or up. A value of `0` will leave that axis unmodified.
988989
990+ If a margin is provided, it will add space between the resulting region.
991+
992+ Note that if margin is specified it *overlaps*, so the space will be the maximum
993+ of two edges, and not the total.
994+
989995 ```
990996 ╔══════════╗ │
991997 ║ ║
992998 ║ Self ║ │
993999 ║ ║
9941000 ╚══════════╝ │
9951001
996- ─ ─ ─ ─ ─ ─ ─ ─ ┼ ─ ─ ─ ─ ─ ─ ─ ─
997-
998- │ ┌──────────┐
999- │ │
1000- │ │ Result │
1001- │ │
1002- │ └──────────┘
1002+ ─ ─ ─ ─ ─ ─ ─ ─ ┌──────────┐
1003+ │ │
1004+ │ Result │
1005+ │ │
1006+ └──────────┘
10031007 ```
10041008
10051009 Args:
@@ -1013,11 +1017,89 @@ def inflect(
10131017 inflect_margin = NULL_SPACING if margin is None else margin
10141018 x , y , width , height = self
10151019 if x_axis :
1016- x += (width + inflect_margin .width ) * x_axis
1020+ x += (width + inflect_margin .max_width ) * x_axis
10171021 if y_axis :
1018- y += (height + inflect_margin .height ) * y_axis
1022+ y += (height + inflect_margin .max_height ) * y_axis
10191023 return Region (x , y , width , height )
10201024
1025+ def constrain (
1026+ self ,
1027+ constrain_x : Literal ["none" , "inside" , "inflect" ],
1028+ constrain_y : Literal ["none" , "inside" , "inflect" ],
1029+ margin : Spacing ,
1030+ container : Region ,
1031+ ) -> Region :
1032+ """Constrain a region to fit within a container, using different methods per axis.
1033+
1034+ Args:
1035+ constrain_x: Constrain method for the X-axis.
1036+ constrain_y: Constrain method for the Y-axis.
1037+ margin: Margin to maintain around region.
1038+ container: Container to constrain to.
1039+
1040+ Returns:
1041+ New widget, that fits inside the container (if possible).
1042+ """
1043+ margin_region = self .grow (margin )
1044+ region = self
1045+
1046+ def compare_span (
1047+ span_start : int , span_end : int , container_start : int , container_end : int
1048+ ) -> int :
1049+ """Compare a span with a container
1050+
1051+ Args:
1052+ span_start: Start of the span.
1053+ span_end: end of the span.
1054+ container_start: Start of the container.
1055+ container_end: End of the container.
1056+
1057+ Returns:
1058+ 0 if the span fits, -1 if it is less that the container, otherwise +1
1059+ """
1060+ if span_start >= container_start and span_end <= container_end :
1061+ return 0
1062+ if span_start < container_start :
1063+ return - 1
1064+ return + 1
1065+
1066+ # Apply any inflected constraints
1067+ if constrain_x == "inflect" or constrain_y == "inflect" :
1068+ region = region .inflect (
1069+ (
1070+ - compare_span (
1071+ margin_region .x ,
1072+ margin_region .right ,
1073+ container .x ,
1074+ container .right ,
1075+ )
1076+ if constrain_x == "inflect"
1077+ else 0
1078+ ),
1079+ (
1080+ - compare_span (
1081+ margin_region .y ,
1082+ margin_region .bottom ,
1083+ container .y ,
1084+ container .bottom ,
1085+ )
1086+ if constrain_y == "inflect"
1087+ else 0
1088+ ),
1089+ margin ,
1090+ )
1091+
1092+ # Apply translate inside constrains
1093+ # Note this is also applied, if a previous inflect constrained has been applied
1094+ # This is so that the origin is always inside the container
1095+ region = region .translate_inside (
1096+ container .shrink (margin ),
1097+ constrain_x != "none" ,
1098+ constrain_y != "none" ,
1099+ )
1100+
1101+ return region
1102+
10211103
10221104class Spacing (NamedTuple ):
10231105 """Stores spacing around a widget, such as padding and border.
@@ -1072,6 +1154,18 @@ def height(self) -> int:
10721154 """Total space in the y axis."""
10731155 return self .top + self .bottom
10741156
1157+ @property
1158+ def max_width (self ) -> int :
1159+ """The space between regions in the X direction if margins overlap, i.e. `max(self.left, self.right)`."""
1160+ _top , right , _bottom , left = self
1161+ return left if left > right else right
1162+
1163+ @property
1164+ def max_height (self ) -> int :
1165+ """The space between regions in the Y direction if margins overlap, i.e. `max(self.top, self.bottom)`."""
1166+ top , _right , bottom , _left = self
1167+ return top if top > bottom else bottom
1168+
10751169 @property
10761170 def top_left (self ) -> tuple [int , int ]:
10771171 """A pair of integers for the left, and top space."""
0 commit comments