mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
WIP: Starting work on adding before/after mount directions
This is still a work-in-progress, but this feels like a good point to commit for safe keeping. This is a non-working WIP.
This commit is contained in:
@@ -822,16 +822,29 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
self._require_stylesheet_update.add(self.screen if node is None else node)
|
self._require_stylesheet_update.add(self.screen if node is None else node)
|
||||||
self.check_idle()
|
self.check_idle()
|
||||||
|
|
||||||
def mount(self, *widgets: Widget) -> AwaitMount:
|
def mount(
|
||||||
"""Mount the given widgets.
|
self, *widgets: Widget, before: MountSpot = None, after: MountSpot = None
|
||||||
|
) -> AwaitMount:
|
||||||
|
"""Mount the given widgets relative to the app's screen.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
*widgets (Widget): The widget(s) to mount.
|
*widgets (Widget): The widget(s) to mount.
|
||||||
|
before (MountSpot, optional): Optional location to mount before.
|
||||||
|
after (MountSpot, optional): Optional location to mount after.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
AwaitMount: An awaitable object that waits for widgets to be mounted.
|
AwaitMount: An awaitable object that waits for widgets to be mounted.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
MountError: If there is a problem with the mount request.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Only one of ``before`` or ``after`` can be provided. If both are
|
||||||
|
provided a ``MountError`` will be raised.
|
||||||
"""
|
"""
|
||||||
return AwaitMount(self._register(self.screen, *widgets))
|
return AwaitMount(
|
||||||
|
self._register(self.screen, *widgets, before=before, after=after)
|
||||||
|
)
|
||||||
|
|
||||||
def mount_all(self, widgets: Iterable[Widget]) -> AwaitMount:
|
def mount_all(self, widgets: Iterable[Widget]) -> AwaitMount:
|
||||||
"""Mount widgets from an iterable.
|
"""Mount widgets from an iterable.
|
||||||
@@ -1300,12 +1313,20 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _register(self, parent: DOMNode, *widgets: Widget) -> list[Widget]:
|
def _register(
|
||||||
|
self,
|
||||||
|
parent: DOMNode,
|
||||||
|
*widgets: Widget,
|
||||||
|
before: MountSpot = None,
|
||||||
|
after: MountSpot = None,
|
||||||
|
) -> list[Widget]:
|
||||||
"""Register widget(s) so they may receive events.
|
"""Register widget(s) so they may receive events.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
parent (DOMNode): Parent node.
|
parent (DOMNode): Parent node.
|
||||||
*widgets: The widget(s) to register.
|
*widgets: The widget(s) to register.
|
||||||
|
before (MountSpot, optional): Optional location to mount before.
|
||||||
|
after (MountSpot, optional): Optional location to mount after.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list[Widget]: List of modified widgets.
|
list[Widget]: List of modified widgets.
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from typing import (
|
|||||||
TypeVar,
|
TypeVar,
|
||||||
cast,
|
cast,
|
||||||
overload,
|
overload,
|
||||||
|
Union,
|
||||||
)
|
)
|
||||||
|
|
||||||
import rich.repr
|
import rich.repr
|
||||||
@@ -894,3 +895,56 @@ class DOMNode(MessagePump):
|
|||||||
|
|
||||||
def refresh(self, *, repaint: bool = True, layout: bool = False) -> None:
|
def refresh(self, *, repaint: bool = True, layout: bool = False) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
DOMSpot = Union[int, str, "DOMQuery[Widget]", "Widget", None]
|
||||||
|
"""The type of a relative location of a node in the DOM."""
|
||||||
|
|
||||||
|
def _find_spot(self, spot: DOMSpot) -> tuple["DOMNode", int]:
|
||||||
|
"""Collapse a number of DOM location identifiers into a parent/child-index pair.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
spot (DOMSpot): The spot to find.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple[DOMNode, int]: The parent and the location in its child list.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If a given node can't be located amongst children.
|
||||||
|
|
||||||
|
The rules of this method are:
|
||||||
|
|
||||||
|
- Given ``None``, parent is ``self`` and location is ``-1``.
|
||||||
|
- Given an integer, parent is ``self`` and location is the integer value.
|
||||||
|
- Given a DOMNode, parent is the node's parent and location is
|
||||||
|
where the widget is found in the parent's ``children``. If it
|
||||||
|
can't be found a ``ValueError`` will be raised.
|
||||||
|
- Given a query result, the ``first`` node is used. The code then
|
||||||
|
falls to acting as if a DOMNode were given.
|
||||||
|
- Given a string, it is used to perform a query and then the result
|
||||||
|
is used as if a query result were given.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .widget import Widget
|
||||||
|
|
||||||
|
# None pretty much means "at the end of our child list."
|
||||||
|
if spot is None:
|
||||||
|
return cast(Widget, self), -1
|
||||||
|
|
||||||
|
# A numeric location means at that point in our child list.
|
||||||
|
if isinstance(spot, int):
|
||||||
|
return cast(Widget, self), spot
|
||||||
|
|
||||||
|
# We've got a widget that has a parent; let's look for that in our children.
|
||||||
|
if isinstance(spot, DOMNode):
|
||||||
|
try:
|
||||||
|
return cast(DOMNode, spot.parent), spot.parent.children._index(spot)
|
||||||
|
except ValueError:
|
||||||
|
raise DOMError(f"{spot!r} is not a child of {self!r}") from None
|
||||||
|
|
||||||
|
# Do we have a string? If we do, cast that into a query.
|
||||||
|
if isinstance(spot, str):
|
||||||
|
spot = self.query(spot)
|
||||||
|
|
||||||
|
# At this point, we should have a query of some description. So
|
||||||
|
# let's now descend into it and see.
|
||||||
|
return spot.first(DOMNode).parent._find_spot(spot.first(DOMNode))
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ from .reactive import Reactive
|
|||||||
from .render import measure
|
from .render import measure
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .app import App, ComposeResult
|
from .app import App, ComposeResult, MountSpot
|
||||||
from .scrollbar import (
|
from .scrollbar import (
|
||||||
ScrollBar,
|
ScrollBar,
|
||||||
ScrollBarCorner,
|
ScrollBarCorner,
|
||||||
@@ -375,16 +375,29 @@ class Widget(DOMNode):
|
|||||||
if self._scrollbar_corner is not None:
|
if self._scrollbar_corner is not None:
|
||||||
yield self._scrollbar_corner
|
yield self._scrollbar_corner
|
||||||
|
|
||||||
def mount(self, *widgets: Widget) -> AwaitMount:
|
def mount(
|
||||||
"""Mount child widgets (making this widget a container).
|
self, *widgets: Widget, before: MountSpot = None, after: MountSpot = None
|
||||||
|
) -> AwaitMount:
|
||||||
|
"""Mount widgets below this widget (making this widget a container).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
*widgets (Widget): The widget(s) to mount.
|
*widgets (Widget): The widget(s) to mount.
|
||||||
|
before (MountSpot, optional): Optional location to mount before.
|
||||||
|
after (MountSpot, optional): Optional location to mount after.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
AwaitMount: An awaitable object that waits for widgets to be mounted.
|
AwaitMount: An awaitable object that waits for widgets to be mounted.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
MountError: If there is a problem with the mount request.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Only one of ``before`` or ``after`` can be provided. If both are
|
||||||
|
provided a ``MountError`` will be raised.
|
||||||
"""
|
"""
|
||||||
return AwaitMount(self.app._register(self, *widgets))
|
return AwaitMount(
|
||||||
|
self.app._register(self, *widgets, before=before, after=after)
|
||||||
|
)
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
"""Called by Textual to create child widgets.
|
"""Called by Textual to create child widgets.
|
||||||
|
|||||||
22
tests/test_dom_spot.py
Normal file
22
tests/test_dom_spot.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
from textual.widget import Widget
|
||||||
|
|
||||||
|
class Content(Widget):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Body(Widget):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_find_dom_spot():
|
||||||
|
screen = Widget(name="Screen")
|
||||||
|
header = Widget(name="Header", id="header")
|
||||||
|
body = Body(id="body")
|
||||||
|
content = [ Content(id=f"item{n}") for n in range(1000)]
|
||||||
|
body._add_children(*content)
|
||||||
|
footer = Widget(name="Footer", id="footer")
|
||||||
|
screen._add_children(header, body, footer)
|
||||||
|
assert list(screen.children) == [header,body,footer]
|
||||||
|
assert screen._find_spot(None) == (screen,-1)
|
||||||
|
assert screen._find_spot(1) == (screen, 1)
|
||||||
|
assert screen._find_spot(body) == screen._find_spot(1)
|
||||||
|
assert screen._find_spot("Body") == screen._find_spot(body)
|
||||||
|
assert screen._find_spot("#body") == screen._find_spot(1)
|
||||||
Reference in New Issue
Block a user