Skip to content

Commit

Permalink
Notification prop refactor (#283)
Browse files Browse the repository at this point in the history
* Use scene node props pattern for notifications

* Add missing handle imports

* Nits
  • Loading branch information
brentyi authored Sep 12, 2024
1 parent b3c9c53 commit 652c8ab
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 178 deletions.
2 changes: 2 additions & 0 deletions src/viser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
from ._scene_handles import SceneNodeHandle as SceneNodeHandle
from ._scene_handles import SceneNodePointerEvent as SceneNodePointerEvent
from ._scene_handles import ScenePointerEvent as ScenePointerEvent
from ._scene_handles import SplineCatmullRomHandle as SplineCatmullRomHandle
from ._scene_handles import SplineCubicBezierHandle as SplineCubicBezierHandle
from ._scene_handles import SpotLightHandle as SpotLightHandle
from ._scene_handles import TransformControlsHandle as TransformControlsHandle
from ._viser import CameraHandle as CameraHandle
Expand Down
13 changes: 12 additions & 1 deletion src/viser/_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,23 @@ class NotificationMessage(Message):

mode: Literal["show", "update"]
id: str
props: NotificationProps


@dataclasses.dataclass
class NotificationProps:
title: str
"""Title of the notification. For handles, synchronized automatically when assigned."""
body: str
"""Body text of the notification. For handles, synchronized automatically when assigned."""
loading: bool
"""Whether to show a loading indicator. For handles, synchronized automatically when assigned."""
with_close_button: bool
"""Whether to show a close button. For handles, synchronized automatically when assigned."""
auto_close: Union[int, Literal[False]]
"""Time in milliseconds after which the notification should auto-close, or False to disable auto-close. For handles, synchronized automatically when assigned."""
color: Optional[Color]
"""Color of the notification. For handles, synchronized automatically when assigned."""


@dataclasses.dataclass
Expand Down Expand Up @@ -970,7 +981,7 @@ def redundancy_key(self) -> str:

@dataclasses.dataclass
class SceneNodeUpdateMessage(Message):
"""Sent client<->server when any property of a GUI component is changed."""
"""Sent client<->server when any property of a scene node is changed."""

name: str
updates: Annotated[
Expand Down
129 changes: 26 additions & 103 deletions src/viser/_notification_handle.py
Original file line number Diff line number Diff line change
@@ -1,124 +1,47 @@
from __future__ import annotations

import dataclasses
from typing import Literal
from typing import TYPE_CHECKING, Any, Literal

from ._gui_api import Color
from ._messages import NotificationMessage, RemoveNotificationMessage
from ._messages import NotificationMessage, NotificationProps, RemoveNotificationMessage
from .infra._infra import WebsockClientConnection


@dataclasses.dataclass
class _NotificationHandleState:
websock_interface: WebsockClientConnection
id: str
title: str
body: str
loading: bool
with_close_button: bool
auto_close: int | Literal[False]
color: Color | None
props: NotificationProps


@dataclasses.dataclass
class NotificationHandle:
class NotificationHandle(NotificationProps):
"""Handle for a notification in our visualizer."""

_impl: _NotificationHandleState

def _sync_with_client(self, first: bool = False) -> None:
m = NotificationMessage(
"show" if first else "update",
self._impl.id,
self._impl.title,
self._impl.body,
self._impl.loading,
self._impl.with_close_button,
self._impl.auto_close,
self._impl.color,
)
self._impl.websock_interface.queue_message(m)

@property
def title(self) -> str:
"""Title to display on the notification."""
return self._impl.title

@title.setter
def title(self, title: str) -> None:
if title == self._impl.title:
return

self._impl.title = title
self._sync_with_client()

@property
def body(self) -> str:
"""Message to display on the notification body."""
return self._impl.body

@body.setter
def body(self, body: str) -> None:
if body == self._impl.body:
return

self._impl.body = body
self._sync_with_client()

@property
def loading(self) -> bool:
"""Whether the notification shows loading icon."""
return self._impl.loading

@loading.setter
def loading(self, loading: bool) -> None:
if loading == self._impl.loading:
return

self._impl.loading = loading
self._sync_with_client()

@property
def with_close_button(self) -> bool:
"""Whether the notification can be manually closed."""
return self._impl.with_close_button

@with_close_button.setter
def with_close_button(self, with_close_button: bool) -> None:
if with_close_button == self._impl.with_close_button:
return

self._impl.with_close_button = with_close_button
self._sync_with_client()

@property
def auto_close(self) -> int | Literal[False]:
"""Time in ms before the notification automatically closes;
otherwise False such that the notification never closes on its own."""
return self._impl.auto_close

@auto_close.setter
def auto_close(self, auto_close: int | Literal[False]) -> None:
if auto_close == self._impl.auto_close:
return
def __init__(self, impl: _NotificationHandleState) -> None:
self._impl = impl

self._impl.auto_close = auto_close
self._sync_with_client()
# Support property-style read/write. Similar to `_OverridableScenePropApi`.
if not TYPE_CHECKING:

@property
def color(self) -> Color | None:
"""Color of the notification."""
return self._impl.color
def __setattr__(self, name: str, value: Any) -> None:
if name in NotificationProps.__annotations__:
setattr(self._impl.props, name, value)
self._sync_with_client("update")
else:
return object.__setattr__(self, name, value)

@color.setter
def color(self, color: Color | None) -> None:
if color == self._impl.color:
return
def __getattr__(self, name: str) -> Any:
if name in NotificationProps.__annotations__:
return getattr(self._impl.props, name)
else:
raise AttributeError(
f"'{self.__class__.__name__}' object has no attribute '{name}'"
)

self._impl.color = color
self._sync_with_client()
def _sync_with_client(self, mode: Literal["show", "update"]) -> None:
msg = NotificationMessage(mode, self._impl.id, self._impl.props)
self._impl.websock_interface.queue_message(msg)

def remove(self) -> None:
self._impl.websock_interface.queue_message(
RemoveNotificationMessage(self._impl.id)
)
msg = RemoveNotificationMessage(self._impl.id)
self._impl.websock_interface.queue_message(msg)
Loading

0 comments on commit 652c8ab

Please sign in to comment.