Skip to content

Commit

Permalink
Merge pull request #5097 from Textualize/constrain-tooltips
Browse files Browse the repository at this point in the history
constrain both axis
  • Loading branch information
willmcgugan authored Oct 8, 2024
2 parents d7eae41 + 9935b64 commit 4d4fb73
Show file tree
Hide file tree
Showing 16 changed files with 327 additions and 133 deletions.
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

0 comments on commit 4d4fb73

Please sign in to comment.