Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

constrain both axis #5097

Merged
merged 5 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,22 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## Unreleased


### Added

- Added support for A-F to Digits widget https://github.com/Textualize/textual/pull/5094
- Added `Region.constrain` https://github.com/Textualize/textual/pull/5097
- Added support for A-F to Digits widget https://github.com/Textualize/textual/pull/5094

### Changed

- `Screen.ALLOW_IN_MAXIMIZED_VIEW` will now default to `App.ALLOW_IN_MAXIMIZED_VIEW` https://github.com/Textualize/textual/pull/5088
- Widgets matching `.-textual-system` will now be included in the maximize view by default https://github.com/Textualize/textual/pull/5088
- Digits are now thin by default, style with text-style: bold to get bold digits https://github.com/Textualize/textual/pull/5094
- Made `Widget.absolute_offset` public https://github.com/Textualize/textual/pull/5097
- Tooltips are now displayed directly below the mouse cursor https://github.com/Textualize/textual/pull/5097
- `Region.inflect` will now assume that margins overlap https://github.com/Textualize/textual/pull/5097
- `Pilot.click` and friends will now accept a widget, in addition to a selector https://github.com/Textualize/textual/pull/5095

### Added

- Added support for A-F to Digits widget https://github.com/Textualize/textual/pull/5094

## [0.82.0] - 2024-10-03

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ pytest-textual-snapshot = "^1.0.0"
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
addopts = "--strict-markers"
addopts = "--strict-markers -vv"
markers = [
"syntax: marks tests that require syntax highlighting (deselect with '-m \"not syntax\"')",
]
Expand Down
69 changes: 26 additions & 43 deletions src/textual/_compositor.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
if TYPE_CHECKING:
from typing_extensions import TypeAlias

from textual.css.styles import RenderStyles
from textual.screen import Screen
from textual.widget import Widget

Expand Down Expand Up @@ -522,38 +521,6 @@ def visible_widgets(self) -> dict[Widget, tuple[Region, Region]]:
}
return self._visible_widgets

def _constrain(
self, styles: RenderStyles, region: Region, constrain_region: Region
) -> Region:
"""Applies constrain logic to a Region.

Args:
styles: The widget's styles.
region: The region to constrain.
constrain_region: The outer region.

Returns:
New region.
"""
constrain = styles.constrain
if constrain == "inflect":
inflect_margin = styles.margin
margin_region = region.grow(inflect_margin)
region = region.inflect(
(-1 if margin_region.right > constrain_region.right else 0),
(-1 if margin_region.bottom > constrain_region.bottom else 0),
inflect_margin,
)
region = region.translate_inside(constrain_region, True, True)
elif constrain != "none":
# Constrain to avoid clipping
region = region.translate_inside(
constrain_region,
constrain in ("x", "both"),
constrain in ("y", "both"),
)
return region

def _arrange_root(
self, root: Widget, size: Size, visible_only: bool = True
) -> tuple[CompositorMap, set[Widget]]:
Expand Down Expand Up @@ -688,10 +655,17 @@ def add_widget(

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

if overlay and sub_widget.styles.constrain != "none":
widget_region = self._constrain(
sub_widget.styles, widget_region, no_clip
)
if overlay:
styles = sub_widget.styles
has_rule = styles.has_rule
if has_rule("constrain_x") or has_rule("constrain_y"):
widget_region = widget_region.constrain(
styles.constrain_x,
styles.constrain_y,
styles.margin,
no_clip,
)

if widget._cover_widget is None:
add_widget(
sub_widget,
Expand Down Expand Up @@ -739,13 +713,22 @@ def add_widget(

widget_region = region + layout_offset

if widget._absolute_offset is not None:
widget_region = widget_region.reset_offset.translate(
widget._absolute_offset + widget.styles.margin.top_left
if widget.absolute_offset is not None:
margin = styles.margin
widget_region = widget_region.at_offset(
widget.absolute_offset + margin.top_left
)
widget_region = widget_region.translate(
styles.offset.resolve(widget_region.grow(margin).size, size)
)
has_rule = styles.has_rule
if has_rule("constrain_x") or has_rule("constrain_y"):
widget_region = widget_region.constrain(
styles.constrain_x,
styles.constrain_y,
styles.margin,
size.region,
)

if styles.constrain != "none":
widget_region = self._constrain(styles, widget_region, no_clip)

map[widget._render_widget] = _MapGeometry(
widget_region,
Expand Down
11 changes: 7 additions & 4 deletions src/textual/css/_style_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@

PropertyGetType = TypeVar("PropertyGetType")
PropertySetType = TypeVar("PropertySetType")
EnumType = TypeVar("EnumType", covariant=True)


class GenericProperty(Generic[PropertyGetType, PropertySetType]):
Expand Down Expand Up @@ -773,7 +774,7 @@ def __set__(
obj.refresh(layout=True)


class StringEnumProperty:
class StringEnumProperty(Generic[EnumType]):
"""Descriptor for getting and setting string properties and ensuring that the set
value belongs in the set of valid values.

Expand All @@ -787,7 +788,7 @@ class StringEnumProperty:
def __init__(
self,
valid_values: set[str],
default: str,
default: EnumType,
layout: bool = False,
refresh_children: bool = False,
refresh_parent: bool = False,
Expand All @@ -801,7 +802,9 @@ def __init__(
def __set_name__(self, owner: StylesBase, name: str) -> None:
self.name = name

def __get__(self, obj: StylesBase, objtype: type[StylesBase] | None = None) -> str:
def __get__(
self, obj: StylesBase, objtype: type[StylesBase] | None = None
) -> EnumType:
"""Get the string property, or the default value if it's not set.

Args:
Expand All @@ -816,7 +819,7 @@ def __get__(self, obj: StylesBase, objtype: type[StylesBase] | None = None) -> s
def _before_refresh(self, obj: StylesBase, value: str | None) -> None:
"""Do any housekeeping before asking for a layout refresh after a value change."""

def __set__(self, obj: StylesBase, value: str | None = None):
def __set__(self, obj: StylesBase, value: EnumType | None = None):
"""Set the string property and ensure it is in the set of allowed values.

Args:
Expand Down
34 changes: 34 additions & 0 deletions src/textual/css/_styles_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,40 @@ def process_overlay(self, name: str, tokens: list[Token]) -> None:
self.styles._rules[name] = value # type: ignore

def process_constrain(self, name: str, tokens: list[Token]) -> None:
if len(tokens) == 1:
try:
value = self._process_enum(name, tokens, VALID_CONSTRAIN)
except StyleValueError:
self.error(
name,
tokens[0],
string_enum_help_text(name, VALID_CONSTRAIN, context="css"),
)
else:
self.styles._rules["constrain_x"] = value # type: ignore
self.styles._rules["constrain_y"] = value # type: ignore
elif len(tokens) == 2:
constrain_x, constrain_y = self._process_enum_multiple(
name, tokens, VALID_CONSTRAIN, 2
)
self.styles._rules["constrain_x"] = constrain_x # type: ignore
self.styles._rules["constrain_y"] = constrain_y # type: ignore
else:
self.error(name, tokens[0], "one or two values expected here")

def process_constrain_x(self, name: str, tokens: list[Token]) -> None:
try:
value = self._process_enum(name, tokens, VALID_CONSTRAIN)
except StyleValueError:
self.error(
name,
tokens[0],
string_enum_help_text(name, VALID_CONSTRAIN, context="css"),
)
else:
self.styles._rules[name] = value # type: ignore

def process_constrain_y(self, name: str, tokens: list[Token]) -> None:
try:
value = self._process_enum(name, tokens, VALID_CONSTRAIN)
except StyleValueError:
Expand Down
2 changes: 1 addition & 1 deletion src/textual/css/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
"nocolor",
}
VALID_OVERLAY: Final = {"none", "screen"}
VALID_CONSTRAIN: Final = {"x", "y", "both", "inflect", "none"}
VALID_CONSTRAIN: Final = {"inflect", "inside", "none"}
VALID_KEYLINE: Final = {"none", "thin", "heavy", "double"}
VALID_HATCH: Final = {"left", "right", "cross", "vertical", "horizontal"}
HATCHES: Final = {
Expand Down
24 changes: 20 additions & 4 deletions src/textual/css/styles.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ class RulesMap(TypedDict, total=False):
hatch: tuple[str, Color] | Literal["none"]

overlay: Overlay
constrain: Constrain
constrain_x: Constrain
constrain_y: Constrain


RULE_NAMES = list(RulesMap.__annotations__.keys())
Expand Down Expand Up @@ -450,7 +451,12 @@ class StylesBase:
overlay = StringEnumProperty(
VALID_OVERLAY, "none", layout=True, refresh_parent=True
)
constrain = StringEnumProperty(VALID_CONSTRAIN, "none")
constrain_x: StringEnumProperty[Constrain] = StringEnumProperty(
VALID_CONSTRAIN, "none"
)
constrain_y: StringEnumProperty[Constrain] = StringEnumProperty(
VALID_CONSTRAIN, "none"
)

def __textual_animation__(
self,
Expand Down Expand Up @@ -1172,8 +1178,18 @@ def append_declaration(name: str, value: str) -> None:
append_declaration("subtitle-text-style", str(self.border_subtitle_style))
if "overlay" in rules:
append_declaration("overlay", str(self.overlay))
if "constrain" in rules:
append_declaration("constrain", str(self.constrain))
if "constrain_x" in rules and "constrain_y" in rules:
if self.constrain_x == self.constrain_y:
append_declaration("constrain", self.constrain_x)
else:
append_declaration(
"constrain", f"{self.constrain_x} {self.constrain_y}"
)
elif "constrain_x" in rules:
append_declaration("constrain-x", self.constrain_x)
elif "constrain_y" in rules:
append_declaration("constrain-y", self.constrain_y)

if "keyline" in rules:
keyline_type, keyline_color = self.keyline
if keyline_type != "none":
Expand Down
2 changes: 1 addition & 1 deletion src/textual/css/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
Overflow = Literal["scroll", "hidden", "auto"]
EdgeStyle = Tuple[EdgeType, Color]
TextAlign = Literal["left", "start", "center", "right", "end", "justify"]
Constrain = Literal["none", "x", "y", "both"]
Constrain = Literal["none", "inflect", "inside"]
Overlay = Literal["none", "screen"]

Specificity3 = Tuple[int, int, int]
Expand Down
Loading
Loading