[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/andevsr/pythonnative/main/src/pythonnative/virtual_rows.py [Back]  [Original]

"""Row subtrees for natively virtualized lists.

Native virtualized lists (Android ``RecyclerView`` via
``PNVirtualListView``, iOS ``UITableView``) own row lifecycle on the
platform side: the platform decides when a row enters the viewport,
hands PythonNative an empty native container, and recycles that
container when the row scrolls away.

Each visible row hosts a full PythonNative subtree driven by its own
nested [`Reconciler`][pythonnative.reconciler.Reconciler]. The
[`RowSubtree`][pythonnative.virtual_rows.RowSubtree] class wraps that
per-row reconciler: it mounts the row element into fresh native views,
re-reconciles in place when the platform rebinds the same container to
a different row index (recycling), and tears everything down when the
list is destroyed.

State inside rows works: hooks like ``use_state`` mark the row's own
reconciler dirty and a guarded flush re-renders just that row.
"""

from typing import Any, Callable, Dict

from .element import Element

__all__ = ["RowSubtree", "RowHostPool"]


class RowSubtree:
    """One list row's mounted PythonNative subtree.

    Owns a nested reconciler whose viewport is the row's cell size.
    The native root view produced by
    [`mount`][pythonnative.virtual_rows.RowSubtree.mount] is what the
    platform handler attaches to the recycled cell container.
    """

    def __init__(self) -> None:
        from .native_views import get_registry
        from .reconciler import Reconciler

        self._reconciler = Reconciler(get_registry())
        # Guarded synchronous flush so ``use_state`` inside a row
        # re-renders the row. Re-entrant requests (a setter firing
        # during the flush itself) are queued and drained after.
        self._flushing = False
        self._flush_queued = False
        self._reconciler._screen_re_render = self._request_flush
        self.native_root: Any = None

    def _request_flush(self) -> None:
        if self._flushing:
            self._flush_queued = True
            return
        self._flushing = True
        try:
            for _ in range(8):
                self._flush_queued = False
                self.native_root = self._reconciler.flush_dirty()
                if not self._flush_queued:
                    break
        finally:
            self._flushing = False

    def mount(self, element: Element, width: float, height: float) -> Any:
        """Mount ``element`` at the given cell size; returns the native root."""
        self.native_root = self._reconciler.mount(element)
        self._reconciler.set_viewport_size(float(width), float(height))
        return self.native_root

    def rebind(self, element: Element, width: float, height: float) -> Any:
        """Reconcile the subtree to ``element`` (cell recycled to a new row).

        Cheaper than unmount + mount when consecutive rows share a
        shape, which is the overwhelmingly common case in lists.
        Returns the (possibly replaced) native root.
        """
        self._reconciler.set_viewport_size(float(width), float(height))
        self.native_root = self._reconciler.reconcile(element)
        return self.native_root

    def unmount(self) -> None:
        """Destroy the subtree and release its native views."""
        try:
            self._reconciler.unmount()
        except Exception:
            pass
        self.native_root = None


class RowHostPool:
    """Per-list bookkeeping of row subtrees keyed by native container.

    Platform handlers create one pool per virtualized list view. Keys
    are platform-stable container identities (Java identity hash on
    Android, ``contentView`` pointer on iOS).
    """

    def __init__(self) -> None:
        self._subtrees: Dict[int, RowSubtree] = {}

    def bind(
        self,
        container_key: int,
        make_element: Callable[[], Element],
        width: float,
        height: float,
    ) -> Any:
        """Mount or rebind the subtree for ``container_key``.

        Returns the subtree's native root. The caller must (re)attach
        it to the container: platforms strip a recycled cell's
        children before rebinding, so even an unchanged root needs
        re-attachment.
        """
        element = make_element()
        subtree = self._subtrees.get(container_key)
        if subtree is None:
            subtree = RowSubtree()
            self._subtrees[container_key] = subtree
            return subtree.mount(element, width, height)
        return subtree.rebind(element, width, height)

    def release(self, container_key: int) -> None:
        """Unmount the subtree for one recycled container, if any."""
        subtree = self._subtrees.pop(container_key, None)
        if subtree is not None:
            subtree.unmount()

    def release_all(self) -> None:
        """Unmount every subtree (list destroyed)."""
        subtrees = list(self._subtrees.values())
        self._subtrees.clear()
        for subtree in subtrees:
            subtree.unmount()

    def __len__(self) -> int:
        return len(self._subtrees)

Web Proxy Viewer  |  New URL  |  Original Page