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

Changed to updated Pack.layout() signature (without node) #244

Merged
merged 19 commits into from
Jan 3, 2025
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
1 change: 1 addition & 0 deletions changes/244.removal.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The API for ``Style.layout()`` has been formally specified as part of the Travertino API. The initial ``node`` argument is no longer required as part of the ``layout()`` method. A ``Style`` instance can interrogate ``self._applicator.node`` to retrieve the node to which the style is being applied.
5 changes: 5 additions & 0 deletions src/travertino/declaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,11 @@ def apply(self, property, value):
"Style must define an apply method"
) # pragma: no cover

def layout(self, viewport):
raise NotImplementedError(
"Style must define a layout method"
) # pragma: no cover

######################################################################
# Provide a dict-like interface
######################################################################
Expand Down
18 changes: 17 additions & 1 deletion src/travertino/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,24 @@ def refresh(self, viewport):
if self._root:
self._root.refresh(viewport)
else:
self.style.layout(self, viewport)
if self.applicator:

######################################################################
# 2024-12: Backwards compatibility for Toga <= 0.4.8
######################################################################
# Accommodate the earlier signature of layout(), which included the node
# as a parameter.
try:
self.style.layout(viewport)
except TypeError as error:
if "layout() missing 1 required positional argument:" in str(error):
self.style.layout(self, viewport)
else:
raise
######################################################################
# End backwards compatibility
######################################################################

self.applicator.set_bounds()

def _set_root(self, node, root):
Expand Down
52 changes: 44 additions & 8 deletions tests/test_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,32 @@ class IntrinsicSize(BaseIntrinsicSize):
class Box(BaseBox):
pass

def layout(self, root, viewport):
def layout(self, viewport):
# A simple layout scheme that allocates twice the viewport size.
root.layout.content_width = viewport.width * 2
root.layout.content_height = viewport.height * 2
self._applicator.node.layout.content_width = viewport.width * 2
self._applicator.node.layout.content_height = viewport.height * 2


@prep_style_class
class OldStyle(Style):
# Uses two-argument layout(), as in Toga <= 0.4.8
def layout(self, node, viewport):
# A simple layout scheme that allocates twice the viewport size.
super().layout(viewport)


@prep_style_class
class TypeErrorStyle(Style):
# Uses the correct signature, but raises an unrelated TypeError in layout
def layout(self, viewport):
raise TypeError("An unrelated TypeError has occurred somewhere in layout()")


@prep_style_class
class OldTypeErrorStyle(Style):
# Just to be extra safe...
def layout(self, node, viewport):
raise TypeError("An unrelated TypeError has occurred somewhere in layout()")


@prep_style_class
Expand All @@ -38,10 +60,10 @@ class IntrinsicSize(BaseIntrinsicSize):
class Box(BaseBox):
pass

def layout(self, root, viewport):
def layout(self, viewport):
# A simple layout scheme that allocates twice the viewport size.
root.layout.content_width = viewport.width * 2
root.layout.content_height = viewport.height * 2
self._applicator.node.layout.content_width = viewport.width * 2
self._applicator.node.layout.content_height = viewport.height * 2


class AttributeTestStyle(BaseStyle):
Expand Down Expand Up @@ -130,7 +152,8 @@ def test_create_node():
assert child3.root == new_node


def test_refresh():
@pytest.mark.parametrize("StyleClass", [Style, OldStyle])
def test_refresh(StyleClass):
"""The layout can be refreshed, and the applicator invoked"""

# Define an applicator that tracks the node being rendered and its size
Expand All @@ -155,7 +178,7 @@ def __init__(self, style, children=None):
)

# Define a simple 2 level tree of nodes.
style = Style()
style = StyleClass()
child1 = TestNode(style=style)
child2 = TestNode(style=style)
child3 = TestNode(style=style)
Expand Down Expand Up @@ -184,6 +207,19 @@ def __init__(self, style, children=None):
assert child3.applicator.tasks == []


@pytest.mark.parametrize("StyleClass", [TypeErrorStyle, OldTypeErrorStyle])
def test_type_error_in_layout(StyleClass):
"""The shim shouldn't hide unrelated TypeErrors."""

class Applicator:
def set_bounds(self):
pass

node = Node(style=StyleClass(), applicator=Applicator())
with pytest.raises(TypeError, match=r"unrelated TypeError"):
node.refresh(Viewport(50, 50))


def test_add():
"""Nodes can be added as children to another node"""

Expand Down
Loading