Skip to content
Adrian edited this page Feb 5, 2021 · 19 revisions

[Outdated documentation. To be updated]

Overview

In order to have more control over the creative output, Zenith makes use of a custom UI solution in lieu of a standard UI library. This allows us to integrate custom UI components and animations without having to leave the familiar territory of the engine.

The UI subsystem is not unlike many of the subsystems we've already seen. It includes standard, non-parametrized Initialize and Draw methods, which we won't bother with since they're handled directly by the engine.

We can add UI elements with the AddElement and AddElements methods. The first is much like the ZGame AddGameObject method, but it takes a shared pointer to a ZUIElement instance as opposed to a ZGameObject. The latter is a convenience method that takes an initializer list of elements:

    ZEngine::UI()->AddElement(button);
    ZEngine::UI()->AddElements({panel1, panel2, panel3});

We can use the UI system to register external font's with the engine, mostly for use by ZUIText elements:

    ZEngine::UI()->RegisterFont("Assets/Fonts/roboto.ttf");

If we have a ZUICursor we'd like to use, it would be best to use the SetCursor method to ensure that it is internally drawn above other UI elements:

    ZEngine::UI()->SetCursor(myCursor);

Certain getters might be of use:

    // Get the ZUICursor if one is set
    ZEngine::UI()->Cursor();
    // Get a map of all top level UI elements
    ZEngine::UI()->Elements(); 
    // The shader we use to draw text elements
    ZEngine::UI()->TextShader();
    // The shader we use to draw most other UI elements
    ZEngine::UI()->UIShader(); 

We can also use the templated FindElement method to find a UI element of a certain type, passing in the object id as the sole parameter:

    auto backgroundImage = ZEngine::UI()->FindElement<ZUIImage>("ZUI_15");

UI Elements

The UI system operates in a hierarchical fashion. All UI elements can have multiple children, all children should only have a single parent, and only the top level root elements are stored within the UI subsystem. The parent child hierarchy is walked down in order to render UI elements and update element states (e.g. If a parent is hidden, all children should be hidden as well).

All element positions and scales are specified in pixel coordinates. The dimensions are internally resolved so that sizes and positions stay consistent across different screen resolutions and sizes.

Several useful methods help us query UI element states, such as the Hidden, Enabled and Selected methods. We can also query UI transform properties using the Position, Size and Angle getters:

    if (element->Enabled()) {
        glm::vec2 pos = element->Position();
        float angle = element->Angle();
        // Do something with position and angle...
    }

The corresponding setters for the transform properties are SetPosition, SetSize and SetRotation.

An interesting operation to note is the SetTranslationBounds method, which allows us to specify bounds beyond which a UI element cannot move:

    element->SetTranslationBounds(0.f, 300.f, 300.f, 0.f);

The above creates an invisible box 300 pixels wide and tall beyond which the UI element's movement is effectively clamped.

We can set the size, position and rotation of the element relative to it's current transform properties by using Scale, Translate and Rotate instead of the transform property setters above, which transform the object relative to it's starting reference frame.

We can use Hide, Show, Enable, Disable, Select and Deselect to set the corresponding element states.

Adding child elements is trivial, as all we need to do is call AddChild and pass in a shared pointer to another UI element. Removing a child should be second nature:

    std::shared_ptr<ZUIButton> button(new ZUIButton);
    element->AddChild(button);
    element->RemoveChild(button);

We can use the templated Child method to query for the direct UI children of an element:

    auto label = element->Child<ZUIText>("ZUI_52");

Two other methods of interest are the TrySelect and Contains methods. Contains simply checks whether the provided position lies within the element. TrySelect attempts to select the element at a given position by using the position to call Contains, also checking if the element is enabled. If both conditions pass, the element is selected.

    glm::vec3 position(155.0f, 10.0f, 0.0f);
    if (element->Contains(position)) {
        // Do some nice hit detection stuff here
    }
    anotherElement->TrySelect(position); // Might or might not select the element

Zenith comes with a few ready to use UI elements:

ZUIText

ZUIButton

ZUICheckbox

ZUIImage

ZUICursor

ZUIPanel

ZUIListPanel

Clone this wiki locally