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

Add progressbar component #238

Merged
merged 6 commits into from
Jul 16, 2024
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ htmlcov
.DS_Store
.envrc
.vite
build
src/viser/client/build
src/viser/client/.nodeenv
4 changes: 3 additions & 1 deletion examples/02_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ def main() -> None:
initial_value=0,
disabled=True,
)

gui_slider = server.gui.add_slider(
"Slider",
min=0,
Expand All @@ -27,6 +26,7 @@ def main() -> None:
initial_value=0,
disabled=True,
)
gui_progress = server.gui.add_progress_bar(25, animated=True)

with server.gui.add_folder("Editable"):
gui_vector2 = server.gui.add_vector2(
Expand Down Expand Up @@ -108,6 +108,8 @@ def _(_) -> None:
point_shape="circle",
)

gui_progress.value = float((counter % 100))

# We can use `.visible` and `.disabled` to toggle GUI elements.
gui_text.visible = not gui_checkbox_hide.value
gui_button.visible = not gui_checkbox_hide.value
Expand Down
96 changes: 62 additions & 34 deletions src/viser/_gui_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
GuiMarkdownHandle,
GuiModalHandle,
GuiPlotlyHandle,
GuiProgressBarHandle,
GuiTabGroupHandle,
GuiUploadButtonHandle,
SupportsRemoveProtocol,
Expand All @@ -57,6 +58,22 @@
TLiteralString = TypeVar("TLiteralString", bound=LiteralString)
T = TypeVar("T")
LengthTenStrTuple: TypeAlias = Tuple[str, str, str, str, str, str, str, str, str, str]
Color: TypeAlias = Literal[
"dark",
"gray",
"red",
"pink",
"grape",
"violet",
"indigo",
"blue",
"cyan",
"green",
"lime",
"yellow",
"orange",
"teal",
]


def _hex_from_hls(h: float, l: float, s: float) -> str:
Expand Down Expand Up @@ -637,23 +654,7 @@ def add_button(
disabled: bool = False,
visible: bool = True,
hint: str | None = None,
color: Literal[
"dark",
"gray",
"red",
"pink",
"grape",
"violet",
"indigo",
"blue",
"cyan",
"green",
"lime",
"yellow",
"orange",
"teal",
]
| None = None,
color: Color | None = None,
icon: IconName | None = None,
order: float | None = None,
) -> GuiButtonHandle:
Expand Down Expand Up @@ -701,23 +702,7 @@ def add_upload_button(
disabled: bool = False,
visible: bool = True,
hint: str | None = None,
color: Literal[
"dark",
"gray",
"red",
"pink",
"grape",
"violet",
"indigo",
"blue",
"cyan",
"green",
"lime",
"yellow",
"orange",
"teal",
]
| None = None,
color: Color | None = None,
icon: IconName | None = None,
mime_type: str = "*/*",
order: float | None = None,
Expand Down Expand Up @@ -1176,6 +1161,49 @@ def add_dropdown(
_impl_options=tuple(options),
)

def add_progress_bar(
self,
value: float,
visible: bool = True,
animated: bool = False,
color: Color | None = None,
order: float | None = None,
) -> GuiProgressBarHandle:
"""Add a progress bar to the GUI.

Args:
value: Value of the progress bar. (0 - 100)
visible: Whether the progress bar is visible.
animated: Whether the progress bar is in a loading state (animated, striped).
color: The color of the progress bar.
order: Optional ordering, smallest values will be displayed first.

Returns:
A handle that can be used to interact with the GUI element.
"""
assert value >= 0 and value <= 100
handle = GuiProgressBarHandle(
_gui_api=self,
_id=_make_unique_id(),
_visible=visible,
_animated=animated,
_parent_container_id=self._get_container_id(),
_order=_apply_default_order(order),
_value=value,
)
self._websock_interface.queue_message(
_messages.GuiAddProgressBarMessage(
order=handle._order,
id=handle._id,
value=value,
animated=animated,
color=color,
container_id=handle._parent_container_id,
visible=visible,
)
)
return handle

def add_slider(
self,
label: str,
Expand Down
84 changes: 81 additions & 3 deletions src/viser/_gui_handles.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,14 +559,92 @@ def _parse_markdown(markdown: str, image_root: Path | None) -> str:
return markdown


@dataclasses.dataclass
class GuiProgressBarHandle:
"""Use to remove markdown."""

_gui_api: GuiApi
_id: str
_visible: bool
_animated: bool
_parent_container_id: str
_order: float
_value: float

@property
def value(self) -> float:
"""Current content of this progress bar element, 0 - 100. Synchronized
automatically when assigned."""
return self._value

@value.setter
def value(self, value: float) -> None:
assert value >= 0 and value <= 100
self._value = value
self._gui_api._websock_interface.queue_message(
GuiUpdateMessage(
self._id,
{"value": value},
)
)

@property
def animated(self) -> bool:
"""Show this progress bar as loading (animated, striped)."""
return self._animated

@animated.setter
def animated(self, animated: bool) -> None:
self._animated = animated
self._gui_api._websock_interface.queue_message(
GuiUpdateMessage(
self._id,
{"animated": animated},
)
)

@property
def order(self) -> float:
"""Read-only order value, which dictates the position of the GUI element."""
return self._order

@property
def visible(self) -> bool:
"""Temporarily show or hide this GUI element from the visualizer. Synchronized
automatically when assigned."""
return self._visible

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

self._gui_api._websock_interface.queue_message(
GuiUpdateMessage(self._id, {"visible": visible})
)
self._visible = visible

def __post_init__(self) -> None:
"""We need to register ourself after construction for callbacks to work."""
parent = self._gui_api._container_handle_from_id[self._parent_container_id]
parent._children[self._id] = self

def remove(self) -> None:
"""Permanently remove this progress bar from the visualizer."""
self._gui_api._websock_interface.queue_message(GuiRemoveMessage(self._id))

parent = self._gui_api._container_handle_from_id[self._parent_container_id]
parent._children.pop(self._id)


@dataclasses.dataclass
class GuiMarkdownHandle:
"""Use to remove markdown."""

_gui_api: GuiApi
_id: str
_visible: bool
_parent_container_id: str # Parent.
_parent_container_id: str
_order: float
_image_root: Path | None
_content: str | None
Expand Down Expand Up @@ -628,7 +706,7 @@ class GuiPlotlyHandle:
_gui_api: GuiApi
_id: str
_visible: bool
_parent_container_id: str # Parent.
_parent_container_id: str
_order: float
_figure: go.Figure | None
_aspect: float | None
Expand Down Expand Up @@ -696,7 +774,7 @@ def __post_init__(self) -> None:
parent._children[self._id] = self

def remove(self) -> None:
"""Permanently remove this markdown from the visualizer."""
"""Permanently remove this figure from the visualizer."""
self._gui_api._websock_interface.queue_message(GuiRemoveMessage(self._id))
parent = self._gui_api._container_handle_from_id[self._parent_container_id]
parent._children.pop(self._id)
66 changes: 30 additions & 36 deletions src/viser/_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,22 @@
from . import infra, theme

GuiSliderMark = TypedDict("GuiSliderMark", {"value": float, "label": NotRequired[str]})
Color = Literal[
"dark",
"gray",
"red",
"pink",
"grape",
"violet",
"indigo",
"blue",
"cyan",
"green",
"lime",
"yellow",
"orange",
"teal",
]


class Message(infra.Message):
Expand Down Expand Up @@ -483,6 +499,18 @@ class GuiAddMarkdownMessage(Message):
visible: bool


@tag_class("GuiAddComponentMessage")
@dataclasses.dataclass
class GuiAddProgressBarMessage(Message):
order: float
id: str
value: float
animated: bool
color: Optional[Color]
container_id: str
visible: bool


@tag_class("GuiAddComponentMessage")
@dataclasses.dataclass
class GuiAddPlotlyMessage(Message):
Expand Down Expand Up @@ -538,48 +566,14 @@ class GuiAddButtonMessage(_GuiAddInputBase):
# All GUI elements currently need an `value` field.
# This makes our job on the frontend easier.
value: bool
color: Optional[
Literal[
"dark",
"gray",
"red",
"pink",
"grape",
"violet",
"indigo",
"blue",
"cyan",
"green",
"lime",
"yellow",
"orange",
"teal",
]
]
color: Optional[Color]
icon_html: Optional[str]


@tag_class("GuiAddComponentMessage")
@dataclasses.dataclass
class GuiAddUploadButtonMessage(_GuiAddInputBase):
color: Optional[
Literal[
"dark",
"gray",
"red",
"pink",
"grape",
"violet",
"indigo",
"blue",
"cyan",
"green",
"lime",
"yellow",
"orange",
"teal",
]
]
color: Optional[Color]
icon_html: Optional[str]
mime_type: str

Expand Down
3 changes: 3 additions & 0 deletions src/viser/client/src/ControlPanel/Generated.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import TabGroupComponent from "../components/TabGroup";
import FolderComponent from "../components/Folder";
import MultiSliderComponent from "../components/MultiSlider";
import UploadButtonComponent from "../components/UploadButton";
import ProgressBarComponent from "../components/ProgressBar";

/** Root of generated inputs. */
export default function GeneratedGuiContainer({
Expand Down Expand Up @@ -119,6 +120,8 @@ function GeneratedInput(props: { guiId: string }) {
return <RgbaComponent {...conf} />;
case "GuiAddButtonGroupMessage":
return <ButtonGroupComponent {...conf} />;
case "GuiAddProgressBarMessage":
return <ProgressBarComponent {...conf} />;
default:
assertNeverType(conf);
}
Expand Down
Loading
Loading