From a49207d2145ece348af4437b7a40ece694da08cb Mon Sep 17 00:00:00 2001 From: Ivan Liang Date: Wed, 23 Aug 2023 20:26:57 -0400 Subject: [PATCH 1/4] Add ARENAUI classes - card - prompt - buttonpanel - button --- arena/objects/ui.py | 82 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 arena/objects/ui.py diff --git a/arena/objects/ui.py b/arena/objects/ui.py new file mode 100644 index 00000000..a8a7c7e7 --- /dev/null +++ b/arena/objects/ui.py @@ -0,0 +1,82 @@ +from .arena_object import Object + + +class Button: + """ + Class for Buttons of a ButtonPanel in the ARENA UI. + """ + + def __init__(self, name="Button", **kwargs): + """ + :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) + """ + self.name = name + self.__dict__.update(kwargs) + + +class ButtonPanel(Object): + """ + Class for Button Panels in the ARENA UI. + """ + object_type = "arenaui-button-panel" + + def __init__(self, **kwargs): + """ + :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) + """ + self.buttons = [] + 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" + json_payload = {k: v for k, v in vars(self).items() if k != "buttons"} + json_payload["buttons"] = [vars(button) for button in self.buttons] + json_payload.update(kwargs) + return json_payload + + +class Prompt(Object): + """ + Class for popup Prompts in the ARENA UI. + """ + object_type = "arenaui-prompt" + + def __init__(self, **kwargs): + """ + :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) + """ + super().__init__(object_type=Prompt.object_type, **kwargs) + + +class Card(Object): + """ + Class for Text/Image Cards in the ARENA UI. + """ + object_type = "arenaui-card" + + def __init__(self, **kwargs): + """ + :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 imgAlign: Left or Right image alignment vs body text [ 'left', 'right'] (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) + """ + super().__init__(object_type=Card.object_type, **kwargs) From 5032212de0e9b0d635c9cb061b05f1e96154f3f7 Mon Sep 17 00:00:00 2001 From: Ivan Liang Date: Thu, 24 Aug 2023 02:27:27 -0400 Subject: [PATCH 2/4] Actually import ui classes, fix json preprocess --- arena/objects/__init__.py | 1 + arena/objects/ui.py | 88 +++++++++++++++++++++------------------ 2 files changed, 49 insertions(+), 40 deletions(-) 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 index a8a7c7e7..0125caeb 100644 --- a/arena/objects/ui.py +++ b/arena/objects/ui.py @@ -3,80 +3,88 @@ class Button: """ - Class for Buttons of a ButtonPanel in the ARENA UI. + 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): - """ - :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) - """ self.name = name self.__dict__.update(kwargs) class ButtonPanel(Object): """ - Class for Button Panels in the ARENA UI. + 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): - """ - :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) - """ - self.buttons = [] 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" - json_payload = {k: v for k, v in vars(self).items() if k != "buttons"} - json_payload["buttons"] = [vars(button) for button in self.buttons] + 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): """ - Class for popup Prompts in the ARENA UI. + 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): - """ - :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) - """ super().__init__(object_type=Prompt.object_type, **kwargs) class Card(Object): """ - Class for Text/Image Cards in the ARENA UI. + 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): - """ - :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 imgAlign: Left or Right image alignment vs body text [ 'left', 'right'] (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) - """ super().__init__(object_type=Card.object_type, **kwargs) From a651d5964ec85d4e03cadf96583352ac05035638 Mon Sep 17 00:00:00 2001 From: Ivan Liang Date: Thu, 24 Aug 2023 02:33:45 -0400 Subject: [PATCH 3/4] Add example for ui --- examples/objects/ui.py | 77 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 examples/objects/ui.py diff --git a/examples/objects/ui.py b/examples/objects/ui.py new file mode 100644 index 00000000..8c842038 --- /dev/null +++ b/examples/objects/ui.py @@ -0,0 +1,77 @@ +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) + + # 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 + + # Legacy buttons list supports strings only + # first_buttonset = ["Prompt A", "Option B", "More"] + # second_buttonset = ["Button D", "Button E", "Back"] + + first_buttonset = [Button(name="Prompt A"), Button(name="Option B"), Button("More")] + second_buttonset = [Button("Button D"), Button("Button E"), Button("Back")] + + def button_handler(_scene, evt, _msg): + global prompt + if evt.type == "buttonClick": + if evt.data.buttonName in ["Option B", "Button D", "Button E"]: + 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.buttonName == "Back": # switch back to first button 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() From c44bee82c79ed8b2624d41997e260b29fd4d4693 Mon Sep 17 00:00:00 2001 From: Ivan Liang Date: Fri, 25 Aug 2023 17:09:29 -0400 Subject: [PATCH 4/4] Update ui example --- examples/objects/ui.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/examples/objects/ui.py b/examples/objects/ui.py index 8c842038..157ba780 100644 --- a/examples/objects/ui.py +++ b/examples/objects/ui.py @@ -24,6 +24,18 @@ def setup_scene(): ) 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): @@ -34,17 +46,13 @@ def prompt_handler(_scene, evt, _msg): # Add a button panel, with two sets of buttons - # Legacy buttons list supports strings only - # first_buttonset = ["Prompt A", "Option B", "More"] - # second_buttonset = ["Button D", "Button E", "Back"] - first_buttonset = [Button(name="Prompt A"), Button(name="Option B"), Button("More")] - second_buttonset = [Button("Button D"), Button("Button E"), Button("Back")] + 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", "Button D", "Button E"]: + 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( @@ -58,7 +66,7 @@ def button_handler(_scene, evt, _msg): 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.buttonName == "Back": # switch back to first button set + elif evt.data.buttonIndex == 3: # compare buttonIndex, switch 1st set scene.update_object(button_panel, buttons=first_buttonset) button_panel = ButtonPanel(