diff --git a/arena/objects/__init__.py b/arena/objects/__init__.py index 0feb9fc3..ddc32402 100644 --- a/arena/objects/__init__.py +++ b/arena/objects/__init__.py @@ -22,6 +22,7 @@ from .torus import Torus from .torus_knot import TorusKnot from .triangle import Triangle +from .ui import * OBJECT_TYPE_MAP = { "box": Box, diff --git a/arena/objects/ui.py b/arena/objects/ui.py new file mode 100644 index 00000000..0125caeb --- /dev/null +++ b/arena/objects/ui.py @@ -0,0 +1,90 @@ +from .arena_object import Object + + +class Button: + """ + Buttons of a ButtonPanel in the ARENA UI. + + :param str name: Button Name + :param str img: Image URl for image buttons (optional) + :param float size: Size of image button (optional) + :param float height: Height of image button, overrides size (optional) + :param float: Width of image button, overrides size (optional) + :param float borderRadius: Border radius of image button (optional) + """ + + def __init__(self, name="Button", **kwargs): + self.name = name + self.__dict__.update(kwargs) + + +class ButtonPanel(Object): + """ + Button Panel in the ARENA UI. + + :param list[Button] buttons: List of Button objects + :param str title: Title of Button Panel (optional) + :param bool vertical: Whether to display buttons vertically (optional) + :param str font: Font of button panel ['Roboto', 'Roboto-Mono'] (optional) + """ + + object_type = "arenaui-button-panel" + + def __init__(self, **kwargs): + super().__init__(object_type=ButtonPanel.object_type, **kwargs) + + def json_preprocess(self, **kwargs): + # kwargs are for additional param to add to json, like "action":"create" + skipped_keys = [ + "evt_handler", + "update_handler", + "animations", + "delayed_prop_tasks", + ] + json_payload = {k: v for k, v in vars(self).items() if k not in skipped_keys} + json_payload["data"]["buttons"] = [ + vars(button) if hasattr(button, "__dict__") else button + for button in json_payload["data"]["buttons"] + ] + json_payload.update(kwargs) + return json_payload + + +class Prompt(Object): + """ + Popup Prompt in the ARENA UI. + + :param str title: Title of Prompt (optional) + :param str description: Additional desc text of prompt (optional) + :param list[str] buttons: List of button name strings (optional) + :param str width: Width of prompt (optional) + :param str font: Font of button panel ['Roboto', 'Roboto-Mono'] (optional) + """ + + object_type = "arenaui-prompt" + + def __init__(self, **kwargs): + super().__init__(object_type=Prompt.object_type, **kwargs) + + +class Card(Object): + """ + Text/Image Card in the ARENA UI. + + :param str title: Title of Card (optional) + :param str body: Body text of Card (optional) + :param str bodyAlign: Text alignment of body text ['center', 'left', 'right', 'justify'] (optional) + :param str img: Image URL of Card (optional) + :param str imgCaption: Image caption (optional) + :param str imgDirection: Left or Right image placement vs body text [ 'left', 'right'] (optional) + :param str imgSize: Container size fitting of image ['cover', 'contain'] (optional) + :param float fontSize: Font size of card, scales both title and body (optional) + :param float widthScale: Width of card as a factor of the default (optional) + :param bool closeButton: Whether to display a close button (optional) + :param str font: Font of card ['Roboto', 'Roboto-Mono'] (optional) + """ + + object_type = "arenaui-card" + + def __init__(self, **kwargs): + super().__init__(object_type=Card.object_type, **kwargs) diff --git a/examples/objects/ui.py b/examples/objects/ui.py new file mode 100644 index 00000000..157ba780 --- /dev/null +++ b/examples/objects/ui.py @@ -0,0 +1,85 @@ +from arena import * + +scene = Scene(host="arenaxr.org", scene="example") + +prompt = None + + +@scene.run_once +def setup_scene(): + # Add a simple info card with text and image + intro_card = Card( + object_id="intro-card", + persist=True, + title="Welcome to ARENA", + body="ARENA is a framework designed to both simplify and host collaborative " + "multi-user XR applications. It makes it easy to combine virtual and " + "physical components like sensors, actuators, and digital interfaces in " + "an immersive 3D environment.", + bodyAlign="left", + imgDirection="left", + img="/static/landing/images/xr-logo-v8.png", + imgSize="contain", + position=Position(0, 2, -2.5), + ) + scene.add_object(intro_card) + + hello_card = Card( + object_id="hello_card", + persist=True, + title="Hello World", + body="Please applaud", + bodyAlign="center", + position=Position(-2, 2, -2.5), + widthScale=0.25, + look_at="#my-camera" + ) + scene.add_object(hello_card) + + # Add a popup prompt with single button + + def prompt_handler(_scene, evt, _msg): + if evt.type == "buttonClick": + if evt.data.buttonName == "OK": + print("OK clicked!") + scene.delete_object(prompt) + + # Add a button panel, with two sets of buttons + + first_buttonset = [Button(name="Prompt A"), Button(name="Option B"), Button("More")] + second_buttonset = [Button("D"), Button("E"), Button("F"), Button("Back")] + + def button_handler(_scene, evt, _msg): + global prompt + if evt.type == "buttonClick": + if evt.data.buttonName in ["Option B", "D", "E", "F"]: # Compare buttonName + print(f"{evt.data.buttonName} clicked!") + elif evt.data.buttonName == "Prompt A": # Show prompt + prompt = Prompt( + object_id="promptA", + title="Prompt A", + description="This is a prompt with a description.", + buttons=["OK"], + position=Position(2, 2, -2), + evt_handler=prompt_handler, + ) + scene.add_object(prompt) + elif evt.data.buttonName == "More": # switch to second button set + scene.update_object(button_panel, buttons=second_buttonset) + elif evt.data.buttonIndex == 3: # compare buttonIndex, switch 1st set + scene.update_object(button_panel, buttons=first_buttonset) + + button_panel = ButtonPanel( + object_id="button-panel", + persist=True, + title="Option Buttons", + buttons=first_buttonset, + vertical=True, + font="Roboto-Mono", + position=Position(2, 2, -2.5), + evt_handler=button_handler, + ) + scene.add_object(button_panel) + + +scene.run_tasks()