Skip to content

Commit 9b2fe24

Browse files
committed
constrain both axis
1 parent 1e208b4 commit 9b2fe24

File tree

6 files changed

+91
-27
lines changed

6 files changed

+91
-27
lines changed

src/textual/_compositor.py

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -535,23 +535,32 @@ def _constrain(
535535
Returns:
536536
New region.
537537
"""
538-
constrain = styles.constrain
539-
if constrain == "inflect":
540-
inflect_margin = styles.margin
541-
margin_region = region.grow(inflect_margin)
542-
region = region.inflect(
543-
(-1 if margin_region.right > constrain_region.right else 0),
544-
(-1 if margin_region.bottom > constrain_region.bottom else 0),
545-
inflect_margin,
546-
)
547-
region = region.translate_inside(constrain_region, True, True)
548-
elif constrain != "none":
549-
# Constrain to avoid clipping
550-
region = region.translate_inside(
551-
constrain_region,
552-
constrain in ("x", "both"),
553-
constrain in ("y", "both"),
554-
)
538+
constrain_x = styles.constrain_x
539+
constrain_y = styles.constrain_y
540+
541+
inflect_margin = styles.margin
542+
margin_region = region.grow(inflect_margin)
543+
544+
region = region.inflect(
545+
(
546+
(-1 if margin_region.right > constrain_region.right else 0)
547+
if constrain_x == "inflect"
548+
else 0
549+
),
550+
(
551+
(-1 if margin_region.bottom > constrain_region.bottom else 0)
552+
if constrain_y == "inflect"
553+
else 0
554+
),
555+
inflect_margin,
556+
)
557+
558+
region = region.translate_inside(
559+
constrain_region.shrink(styles.margin),
560+
constrain_x == "limit",
561+
constrain_y == "limit",
562+
)
563+
555564
return region
556565

557566
def _arrange_root(
@@ -688,7 +697,10 @@ def add_widget(
688697

689698
widget_order = order + ((layer_index, z, layer_order),)
690699

691-
if overlay and sub_widget.styles.constrain != "none":
700+
if overlay and (
701+
sub_widget.styles.constrain_x != "none"
702+
or sub_widget.styles.constrain_y != "none"
703+
):
692704
widget_region = self._constrain(
693705
sub_widget.styles, widget_region, no_clip
694706
)
@@ -740,11 +752,15 @@ def add_widget(
740752
widget_region = region + layout_offset
741753

742754
if widget._absolute_offset is not None:
755+
margin = styles.margin
743756
widget_region = widget_region.reset_offset.translate(
744-
widget._absolute_offset + widget.styles.margin.top_left
757+
widget._absolute_offset + margin.top_left
758+
)
759+
widget_region = widget_region.translate(
760+
styles.offset.resolve(widget_region.grow(margin).size, size)
745761
)
746762

747-
if styles.constrain != "none":
763+
if styles.constrain_x != "none" or styles.constrain_y != "none":
748764
widget_region = self._constrain(styles, widget_region, no_clip)
749765

750766
map[widget._render_widget] = _MapGeometry(

src/textual/css/_styles_builder.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,6 +1047,40 @@ def process_overlay(self, name: str, tokens: list[Token]) -> None:
10471047
self.styles._rules[name] = value # type: ignore
10481048

10491049
def process_constrain(self, name: str, tokens: list[Token]) -> None:
1050+
if len(tokens) == 1:
1051+
try:
1052+
value = self._process_enum(name, tokens, VALID_CONSTRAIN)
1053+
except StyleValueError:
1054+
self.error(
1055+
name,
1056+
tokens[0],
1057+
string_enum_help_text(name, VALID_CONSTRAIN, context="css"),
1058+
)
1059+
else:
1060+
self.styles._rules["constrain_x"] = value # type: ignore
1061+
self.styles._rules["constrain_y"] = value # type: ignore
1062+
elif len(tokens) == 2:
1063+
constrain_x, constrain_y = self._process_enum_multiple(
1064+
name, tokens, VALID_CONSTRAIN, 2
1065+
)
1066+
self.styles._rules["constrain_x"] = constrain_x # type: ignore
1067+
self.styles._rules["constrain_y"] = constrain_y # type: ignore
1068+
else:
1069+
self.error(name, tokens[0], "one or two values here")
1070+
1071+
def process_constrain_x(self, name: str, tokens: list[Token]) -> None:
1072+
try:
1073+
value = self._process_enum(name, tokens, VALID_CONSTRAIN)
1074+
except StyleValueError:
1075+
self.error(
1076+
name,
1077+
tokens[0],
1078+
string_enum_help_text(name, VALID_CONSTRAIN, context="css"),
1079+
)
1080+
else:
1081+
self.styles._rules[name] = value # type: ignore
1082+
1083+
def process_constrain_y(self, name: str, tokens: list[Token]) -> None:
10501084
try:
10511085
value = self._process_enum(name, tokens, VALID_CONSTRAIN)
10521086
except StyleValueError:

src/textual/css/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
"nocolor",
7575
}
7676
VALID_OVERLAY: Final = {"none", "screen"}
77-
VALID_CONSTRAIN: Final = {"x", "y", "both", "inflect", "none"}
77+
VALID_CONSTRAIN: Final = {"inflect", "limit", "none"}
7878
VALID_KEYLINE: Final = {"none", "thin", "heavy", "double"}
7979
VALID_HATCH: Final = {"left", "right", "cross", "vertical", "horizontal"}
8080
HATCHES: Final = {

src/textual/css/styles.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,8 @@ class RulesMap(TypedDict, total=False):
190190
hatch: tuple[str, Color] | Literal["none"]
191191

192192
overlay: Overlay
193-
constrain: Constrain
193+
constrain_x: Constrain
194+
constrain_y: Constrain
194195

195196

196197
RULE_NAMES = list(RulesMap.__annotations__.keys())
@@ -450,7 +451,8 @@ class StylesBase:
450451
overlay = StringEnumProperty(
451452
VALID_OVERLAY, "none", layout=True, refresh_parent=True
452453
)
453-
constrain = StringEnumProperty(VALID_CONSTRAIN, "none")
454+
constrain_x = StringEnumProperty(VALID_CONSTRAIN, "none")
455+
constrain_y = StringEnumProperty(VALID_CONSTRAIN, "none")
454456

455457
def __textual_animation__(
456458
self,
@@ -1172,8 +1174,18 @@ def append_declaration(name: str, value: str) -> None:
11721174
append_declaration("subtitle-text-style", str(self.border_subtitle_style))
11731175
if "overlay" in rules:
11741176
append_declaration("overlay", str(self.overlay))
1175-
if "constrain" in rules:
1176-
append_declaration("constrain", str(self.constrain))
1177+
if "constrain_x" in rules and "constrain_y" in rules:
1178+
if self.constrain_x == self.constrain_y:
1179+
append_declaration("constrain", self.constrain_x)
1180+
else:
1181+
append_declaration(
1182+
"constrain", f"{self.constrain_x} {self.constrain_y}"
1183+
)
1184+
elif "constrain_x" in rules:
1185+
append_declaration("constrain-x", self.constrain_x)
1186+
elif "constrain_y" in rules:
1187+
append_declaration("constrain-y", self.constrain_y)
1188+
11771189
if "keyline" in rules:
11781190
keyline_type, keyline_color = self.keyline
11791191
if keyline_type != "none":

src/textual/css/types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
Overflow = Literal["scroll", "hidden", "auto"]
3737
EdgeStyle = Tuple[EdgeType, Color]
3838
TextAlign = Literal["left", "start", "center", "right", "end", "justify"]
39-
Constrain = Literal["none", "x", "y", "both"]
39+
Constrain = Literal["none", "inflect", "limit"]
4040
Overlay = Literal["none", "screen"]
4141

4242
Specificity3 = Tuple[int, int, int]

src/textual/widgets/_tooltip.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ class Tooltip(Static, inherit_css=False):
88
Tooltip {
99
layer: _tooltips;
1010
margin: 1 2;
11+
1112
padding: 1 2;
1213
background: $background;
1314
width: auto;
1415
height: auto;
15-
constrain: inflect;
16+
constrain: limit inflect;
1617
max-width: 40;
1718
display: none;
19+
offset-x: -50%;
1820
}
1921
"""
2022
DEFAULT_CLASSES = "-textual-system"

0 commit comments

Comments
 (0)