Source code for annie.pages.visibility
"""Which tab a browser shows, so key handlers and pollers run only while theirs is visible.
NiceGUI keeps every tab panel alive once built, and a ``ui.keyboard`` listens on the whole
document. Without this, the Annotator's keys (Space, the arrows, digits) still acted while
the reviewer typed or scrolled on Browse, and the 10 Hz playhead poll kept talking to the
browser from a hidden tab.
Elements with an ``active`` flag (``ui.keyboard``, ``ui.timer``) are registered with
:func:`only_while_shown`; :func:`show` (called on every tab switch) turns them on for the
visible tab and off for the others.
"""
from __future__ import annotations
from typing import Protocol
from nicegui import context
class _Switchable(Protocol):
"""An element that can be switched on and off (``ui.keyboard``, ``ui.timer``)."""
active: bool
@property
def is_deleted(self) -> bool:
"""Whether the element was removed from its page."""
...
#: The tab each browser tab shows, keyed by client id.
_shown: dict[str, str] = {}
#: Per client: the registered elements and the tab each belongs to.
_members: dict[str, list[tuple[str, _Switchable]]] = {}
[docs]
def shown_tab() -> str | None:
"""The tab the current browser tab shows (``None`` before the first :func:`show`)."""
return _shown.get(context.client.id)
[docs]
def show(tab: str) -> None:
"""Record that the current browser tab now shows ``tab``; switch members on or off."""
client = context.client
if client.id not in _shown:
client.on_delete(lambda: (_shown.pop(client.id, None), _members.pop(client.id, None)))
_shown[client.id] = tab
members = [(name, el) for name, el in _members.get(client.id, []) if not el.is_deleted]
_members[client.id] = members
for name, element in members:
element.active = name == tab
[docs]
def only_while_shown[T: _Switchable](element: T, tab: str) -> T:
"""Keep ``element`` active only while ``tab`` is the shown tab of its browser tab.
Args:
element: A ``ui.keyboard`` or ``ui.timer`` built inside ``tab``.
tab: The tab name it belongs to (e.g. ``"annotator"``).
Returns:
The element, already switched to match the tab shown now.
"""
cid = context.client.id
live = [(name, el) for name, el in _members.get(cid, []) if not el.is_deleted]
_members[cid] = [*live, (tab, element)] # rebuilds replace members: drop deleted ones
element.active = _shown.get(cid, tab) == tab
return element