chwar / XRUI

Unity Extension for Responsive UI in XR
https://chwar.github.io/xrui/
MIT License
21 stars 2 forks source link
csharp ui unity unity3d unity3d-plugin xr

XRUI Framework

release Unity 2021.2+ MIT Coverage

2dlandscape 2dportrait 3d
2D Landscape (PC) 2D Portrait (Android) World Space (Meta Quest 2)

XRUI is a responsive UI framework for making cross-platform XR applications with the Unity 3D editor. Its purpose is to assist users in creating efficient and adaptive UIs that can easily be adjusted to be rendered in 2D (for environments with a 2D screen, e.g. PC, smartphone, tablet) and 3D (i.e., rendered in world space, required to render UI in XR (AR/MR/VR)). This way, XRUI users only need to design and implement their UI once for all platforms. This can also provide memorability and familiarity to end-users that use XRUI enhanced apps on different platforms, thus increasing usability.

XRUI is based on Unity's new UI system, UI Toolkit. Internally, it uses UXML and USS, so a basic knowledge and understanding of these technologies are required to use this framework.

Getting Started

  1. In the package manager, click on Add package from git URL and insert the repository's URL: https://github.com/chwar/XRUI.git

    • Alternatively, you can unzip and import the package manually.
  2. Add the XRUI controller to your scene by navigating to XRUI > Add XRUI Controller. You can also create an empty game object and attach the main XRUI script (XRUI.cs). This script is a singleton flagged as DontDestroyOnLoad. It contains the main API that can be easily accessed through the instance:

using com.chwar.xrui;

void Start(){
    // XRUI.Instance...
}
  1. The package uses a default configuration that references the default UXML templates for UI elements. You can create your own by navigating to Assets > Create > XRUI > Create XRUI Configuration asset. You can then override the default templates for UI elements with your own (see Custom UI Elements). Don't forget to reference your own XRUI configuration asset to the XRUI controller.
  2. You can have a look at the provided Demo scenes to get a better idea of how XRUI works.

UI Elements

2dlandscape 3d
2D Landscape Overview 3D (World Space) Overview

XRUI provides a few UI Elements. The style is minimalistic and inspired from Bootstrap. You can add them in your project by navigating from the Unity menu to XRUI > Add XRUI Element. This creates a game object containing a UIDocument (which contains the UXML template and USS styles) and an XRUI script that matches the element. Add your own scripts to this object with a reference to the XRUI script to define the behaviour of the UI.

XRUI elements are thought as basic containers for user content. Given the hierarchic nature of UXML, it is easy to append content within the UI elements at runtime. To easily access your UXML contents and append them into various XRUI elements, reference them in the intended UI Elements list within the XRUI controller:

xruiController

The list of UI elements is accessible within the XRUI controller's instance. Use the GetUIElement method for easy access:

// Use the name of the VisualTreeAsset you put in the inspector list 
VisualTreeAsset myElement = XRUI.Instance.GetUIElement("MyElement");

XRUI Element

Click to expand! When adding UI Elements through the XRUI menu in Unity, the system uses the template referenced in the XRUI Configuration asset (see [Installation](#installation)). Each XRUI element script inherits from the `XRUIElement` class, which comes with some useful generic methods. To add or remove visual elements from the UI element, call these methods: ```csharp VisualElement myElement = someVisualTreeAsset.Instantiate(); XRUICard card = GetComponent(); // Appends a visual element inside a parent element card.AddUIElement(myElement, "xrui-card__container"); card.RemoveUIElement(myElement); ``` Get an XRUI related visual element from the UI element: ```csharp XRUICard card = FindObjectOfType(); XRUIMenu menu = FindObjectOfType(); // Get a generic Visual Element from the card var cardHeader = card.GetXRUIVisualElement("xrui-card__header"); // Get the title Label from the menu var menuTitle = menu.GetXRUIVisualElement

XRUI Menu

Click to expand!
2dlandscape 2dportrait 3d
2D Landscape 2D Portrait World Space
The provided XRUI Menu template is designed as a side menu that collapses out of the view frustum. It can be configured in the inspector (see screenshot above). The list element template is the UXML template that is used to create entries. You can provide a template with a simple button, or more complex compositions with images, text, buttons, etc. to suit your needs. Add entries to your menu: ```csharp var menu = GetComponent(); // The menu returns the created entry to be configured var element = menu.AddElement(); element.Q

XRUI List

Click to expand!
2dlandscape 2dportrait 3d
2D Landscape 2D Portrait World Space
The XRUI List works in the same way as the menu: ```csharp var list = GetComponent(); // The list returns the created entry to be configured var element = list.AddElement(); element.Q

XRUI Navbar

Click to expand!
2dlandscape 2dportrait 3d
2D Landscape 2D Portrait World Space
The provided navbar is a very simple dark top bar. Since XRUI does not provide any third-party assets, it is provided empty. However, the default template contains a row of buttons (three justified on the left side, one justified on the right side) to get you started. Since navbar designs can be very different, the adopted solution was to propose a very generic template to fit the most users. You could use the template as a base to add your own elements (buttons, dropdowns, labels) to tailor the navbar to your needs.

XRUI Card

Click to expand!
2dlandscape 2dportrait 3d
2D Landscape 2D Portrait World Space
The XRUI Card is floating on the right corner in the 2D landscape format, and sticks to the bottom of the screen in portrait format. Use the `AddUIElement` method (see [XRUI Element](#xrui-element)) to fill the card with content.

XRUI Modals

Click to expand!
2dlandscape 2dportrait 3d
2D Landscape 2D Portrait World Space
XRUI creates modals at runtime rather than requiring you to create all of them in the editor in order to save resources. Given the hierarchic nature of UXML, modals are easy to reproduce. XRUI provides a modal template, which consists of a title, empty container, two buttons (main and secondary) sticking at the bottom, and a closing button in the top right corner. You can use this template and fill its container dynamically at runtime. In Unity, you can reference your modals in the intended list: modals The name given to each modal entry can be used to find the matching template and create a modal from it, with the `CreateModal` method: ```csharp // Adapt the namespace to your own Type t = Type.GetType("myModalScript"); XRUI.Instance.ShowModal("DemoModal", t); ``` > Note: The user script type has to be passed outside of the XRUI package, because Unity packages can't access the Assembly-CSharp assembly, i.e. can't find user namespaces, and hence, can't find user scripts located in the Assets automatically. It's also not possible to reference it through the inspector, as it only accepts instances of a script and not the script itself. This creates a modal game object on which the `XRUIModal` script is attached, as well as a `UIDocument` script that contains the main template. You can access the modal system's API through the `XRUIModal` script. The user script type is used to create an instance of said script when the modal is created. This lets you define the behaviour of your elements. One approach is to create one method per page, and to setup event handlers on your buttons to navigate them. To create modal pages, use the `UpdateModalFlow` method. Its last parameter is a callback function that is fired once upon the page's creation. ```csharp private XRUIModal _xruiModal; private UIDocument _uiDocument; void Start() { _xruiModal = GetComponent(); _uiDocument = GetComponent(); StartPage(); } void StartPage() { _xruiModal.UpdateModalFlow("MyModalPage", "MainContainer", () => { // This callback is only fired once, when the page is created for the first time // Put here initialization code, event subscriptions, etc. Button myButton = RootElement.Q

XRUI Alerts

Click to expand!
2dlandscape 2dportrait 3d
2D Landscape - Primary 2D Portrait - Success World Space - Warning
![Peek 2021-08-03 00-03](https://user-images.githubusercontent.com/25299178/127934108-1784dc2d-36d3-4452-8119-3f910f9a258a.gif) ![Peek 2021-08-03 00-05](https://user-images.githubusercontent.com/25299178/127934111-57e1859b-5900-4487-995f-9d3f55e8da68.gif) The provided alert template creates floating cards in 2D landscape and 3D formats, and as notifications at the top of the screen in 2D portrait mode. They also come with animations to attract the attention of users. You can show alerts for different purposes; the types of alerts are inspired from [Bootstrap](https://getbootstrap.com/docs/5.0/components/alerts/). Show alerts using the `ShowAlert` method: ```csharp XRUI.Instance.ShowAlert(XRUIAlert.AlertType.Primary, "Primary message."); XRUI.Instance.ShowAlert(XRUIAlert.AlertType.Success, "Success message."); XRUI.Instance.ShowAlert(XRUIAlert.AlertType.Warning, "Warning message."); XRUI.Instance.ShowAlert(XRUIAlert.AlertType.Danger, "Error message."); XRUI.Instance.ShowAlert(XRUIAlert.AlertType.Info, "Info message."); ``` You can also provide a title: ```csharp XRUI.Instance.ShowAlert(XRUI.AlertType.Primary, "Title", "Primary message."); ``` You can also give a callback, which will be triggered upon clicking the alert: ```csharp XRUI.Instance.ShowAlert(XRUI.AlertType.Primary, "Click me!", "Click to trigger callback", () => MyCallback()); ``` Or, you can set a countdown after which the alert will disappear: ```csharp XRUI.Instance.ShowAlert(XRUI.AlertType.Primary, "Title", "This alert will disappear in 5 seconds", 5); ```

XRUI Contextual Menu

Click to expand!
2dlandscape 2portrait
2D Landscape 2D Portrait
XRUI can create contextual menus dynamically. The contextual menu is shown as a floating list. Similarly to the menu and list templates, a menu element template is also given to create entries in the contextual menu. Because the entries are context-dependent, they need to be generated dynamically at runtime. The `ShowContextualMenu` method needs at least the x and y coordinates of the parent element (i.e. the element that was interacted which caused the contextual menu to appear), and a boolean to indicate whether or not the styling should include an arrow pointing at the parent element. A first overload gives the possibility to provide a custom template. A second overload lets developers provide left and right offsets for finer tuning of the menu’s position. The method returns a `XRUIContextualMenu` instance, which is required to add entries to the menu. ```csharp var myElement = GetComponent(); var myBtn = myElement.rootVisualElement.Q

XR Adaptation

Global XRUI Format

XRUI's main functionality is to provide responsiveness for different XR variants. This is done by setting the chosen XRUI format during the app's initialization, which all XRUI Elements (both static and dynamic) adopt thanks to USS styles.

To change the global XRUI format, change the related value in the XRUI controller:

The XRUI API provides methods to assess the current XRUI format. You can use it to do target-specific manipulations like so:

if(XRUI.IsGlobalXRUIFormat(XRUI.XRUIFormat.ThreeDimensional)) {
    // 3D UI specific code here
}

XRUIFormat format = XRUI.GetGlobalXRUIFormat();

Overriding the Global XRUI Format for Specific Elements

It is also possible for specific UI elements to override the global format defined in the XRUI controller. This way, hybrid 2D and 3D / World UI elements can be rendered in the same scene. Each XRUI Element possesses the xruiFormatOverride property, which can be set to the following values:

The API provides methods to check the format of any XRUI Element:

var card = GetComponent<XRUICard>();
if(card.IsXRUIFormat(XRUI.XRUIFormat.ThreeDimensional) {
    // 3D UI specific code for this given element
}

XRUIFormat format = card.GetXRUIFormat(); 

Note: When integrating World UI elements while using 2D as a global format, don't forget to change the Panel Settings of the UI Document to an asset that is fit for World UI (you can use the provided DefaultWorldUIPanelSettings asset).

Two Dimensional Format

For 2D UI, additional USS styles are provided to adapt for both landscape and portrait orientations. These classes are automatically added when the device (i.e., a smartphone) changes orientation. For ease of use, you can force the portrait mode by checking the Force Two Dimensional Format to Portrait checkbox in the XRUI controller.

Three Dimensional Format (World Space UI)

When XRUI is set to Three Dimensional format, UI is rendered on panels in world space. Each XRUI Element contains a set of World UI Parameters which can alter the way it is rendered in world space.

worlduiparameters

XRUI Grid System

In order to organize easily and efficiently UI elements on screen, XRUI makes use of a grid system. You can use it by navigating to XRUI > Add XRUI Grid. In the Unity editor, you can group UI components inside rows through the scene hierarchy. The XRUIGridController component is attached to the root of the grid, and contains the list of all rows. A weighting system allows you to define which rows should take which amount of space (this uses the flex-grow attribute of CSS/USS Flexbox).

For example, a top navbar can be setup in one row, with a weight of 0, i.e., it should not "grow"--as in, take space--more than its initial size. A second row containing the rest of the on-screen UI can have a weight of 1, i.e. it should take more of the available space than what its initial size requires. Since there are two rows and the first row has a weight of 0, this results in the second row using all remaining screen space. Horizontally, elements are contained in absolute containers, which mean they all take the entire horizontal space and can therefore overlap.

pc pc

Note: In case all UI elements within a row are absolute, the row's height becomes zero, because its USS property is set to height: auto. You should then indicate a minimum height in the indicated field to obtain the expected behaviour.

Custom UI Elements

You can create your own UXML templates and refer them in the XRUI Configuration asset. You should however be careful in naming your elements, should you want to inherit the functionalities provided by the default UI elements. You can check them with Unity's UI Builder, or you can simply duplicate the UXML files and start working from here.

Also, the root visual element of your custom templates must have the .xrui USS class.

USS Styles

XRUI comes with its own set of styles that are imported just after Unity's in UI Toolkit's pipeline. They are imported through a theme file which is used in the provided Panel Settings assets (also linked in the XRUI Configuration asset). You can add your own root styles to this theme file, override the root XRUI styles, or remove some of the imported assets if you don't need them. Should you want to inherit some of the XRUI styles for your own UI elements, you can add the related USS classes to the desired visual elements.

Additionally, when creating your custom elements based on existing ones, it is recommended that you add the following USS classes to keep the XRUI functionalities (e.g., updating the title from the inspector). They are the following:

XRUI Element Root USS Class Sub USS Classes
Menu .xrui-menu .xrui-menu__title
.xrui-menu__subtitle
.xrui-menu__container
.xrui-menu__btn-container
.xrui-menu__close-btn
.xrui-menu__main-btn
Menu item .xrui-menu-item
List .xrui-list .xrui-list__title
.xrui-list__add-btn
.xrui-list__container
List item .xrui-list-item .xrui-list-item__icon
.xrui-list-item__text
Navbar .xrui-navbar
Card .xrui-card .xrui-card__title
.xrui-card__subtitle
.xrui-card__container
.xrui-card__close-btn
Alert .xrui-alert .xrui-alert__title
.xrui-alert__content
Modal .xrui-modal .xrui-modal__title
.xrui-modal__close-btn
.xrui-modal__container
.xrui-modal__btn-container
.xrui-modal__cancel-btn
.xrui-modal__validate-btn
Contextual Menu .xrui-contextual-menu .xrui-contextual-menu__arrow
.xrui-contextual-menu__container
Contextual Menu Element .xrui-contextual-menu-element .xrui-contextual-menu-element__text
Icons .xrui-icon .xrui-icon--white
.xrui-icon--black
Templates .xrui-templates__btn
.xrui-templates__separator
.xrui-templates__textfield
Backgrounds .xrui-background--primary
.xrui-background--secondary
.xrui-background--warning
.xrui-background--success
.xrui-background--danger
.xrui-background--info
.xrui-background--light-grey
.xrui-background--dark

XR Interactions

See the XRUIDemoInteraction scene in the Demo folder. To enable interactions with world UI (i.e., make your UI react to MR/VR pointers), you need to add a few objects from the XR Interaction package in your scene:

Your controllers need:

XRUI automatically adds the Tracked Device Physics Raycaster component to World UI game objects.

You can also specifically disable XR interactions on specific XRUI Elements, to avoid adding unnecessary Game Objects to your scene at runtime. To do so, check the Disable XR Interaction checkbox in the World UI Parameters section of the XRUI Element.

Acknowledgements

Roadmap

Known bugs