Magnum::Ui::AbstractUserInterface class new in Git master

Base implementation of the main user interface.

Owns the whole user interface, providing everything from input event handling to animation and drawing.

Setting up a user interface instance

Unless you're building a UI fully consisting of custom widgets, you'll want to instantiate the user interface through the UserInterfaceGL subclass, which by default includes everything that's needed by builtin widgets. The constructor takes a UI size, in respect to which all contents as well as input events get positioned, and a style instance describing how the widgets will look like. At the moment, McssDarkStyle is the only style provided by the library itself.

Ui::UserInterfaceGL ui{{800, 600}, Ui::McssDarkStyle{}};

Then, at the very least, the UI needs to be (re)drawn when needed. The renderer uses a premultiplied alpha workflow, so it expects an appropriate GL::Renderer::setBlendFunction() setup, shown below. If all other rendering uses a premultiplied alpha as well, it's enough to call it just once, otherwise you need to make sure it's set up every time the UI is drawn.

GL::Renderer::setBlendFunction(
    GL::Renderer::BlendFunction::One,
    GL::Renderer::BlendFunction::OneMinusSourceAlpha);

The draw() function draws to the currently bound framebuffer, clearing is left upon the application so it can draw other contents underneath the UI. The actual enablement of blending and other state during a draw() is taken care of by a RendererGL instance internally.

GL::defaultFramebuffer.clear(GL::FramebufferClear::Color);



ui.draw();

For actual interactivity, the user interface needs to receive input events via pointerPressEvent(), pointerReleaseEvent(), pointerMoveEvent(), keyPressEvent(), keyReleaseEvent() and textInputEvent(). They take a PointerEvent, PointerMoveEvent, KeyEvent or TextInputEvent instances containing the actual event data such as the pointer or key being pressed, and in case of pointer events also a position at which the event happens. For example:

Ui::PointerEvent event{{},
    Ui::PointerEventSource::Mouse,
    Ui::Pointer::MouseLeft, true, 0};
if(!ui.pointerPressEvent({123, 456}, event)) {
    // Not handled by the UI, pass further ...
}

Integration with application libraries

Unless you're using a custom windowing toolkit, you'll likely want to make the UI drawing and event handling directly tied to a Platform::*Application class. By including Magnum/Ui/Application.h it's possible to pass the application and event instances directly to the user interface without needing to translate and pass through individual data. The constructor can take an application instance to figure out an appropriate DPI-aware size, and similarly you can pass the viewport event to setSize() to make the UI perform a relayout. Then, assuming nothing else needs to be drawn besides the UI, a redraw is again scheduled only when needed:


#include <Magnum/Ui/Application.h>

class MyApplication: public Platform::Application {
    public:
        explicit MyApplication(const Arguments& arguments):
            Platform::Application{arguments}, _ui{*this, } {  }

        void viewportEvent(ViewportEvent& event) override {
            _ui.setSize(event);
        }

        void drawEvent() override {
            GL::defaultFramebuffer.clear(GL::FramebufferClear::Color);

            _ui.draw();

            swapBuffers();
            if(_ui.state())
                redraw();
        }

        

    private:
        Ui::UserInterfaceGL _ui;
};

With the code below, events coming from the application get internally converted to a corresponding PointerEvent, PointerMoveEvent, KeyEvent or TextInputEvent instance with a subset of information given application provides. Then, if the event is accepted by the user interface, matching Platform::*Application::PointerEvent::setAccepted() etc. get called as well, to prevent it from propagating further in certain circumstances (such as to the browser window when compiling for the web). To make sure the UI is appropriately redrawn after handling an event, each of these again checks against state():

void MyApplication::pointerPressEvent(PointerEvent& event) {
    if(!_ui.pointerPressEvent(event)) {
        
    }
    if(_ui.state()) redraw();
}
void MyApplication::pointerReleaseEvent(PointerEvent& event) {
    if(!_ui.pointerReleaseEvent(event)) {
        
    }
    if(_ui.state()) redraw();
}
void MyApplication::pointerMoveEvent(PointerMoveEvent& event) {
    if(!_ui.pointerMoveEvent(event)) {
        
    }
    if(_ui.state()) redraw();
}
void MyApplication::keyPressEvent(KeyEvent& event) {
    if(!_ui.keyPressEvent(event)) {
        
    }
    if(_ui.state()) redraw();
}
void MyApplication::keyReleaseEvent(KeyEvent& event) {
    if(!_ui.keyReleaseEvent(event)) {
        
    }
    if(_ui.state()) redraw();
}
void MyApplication::textInputEvent(TextInputEvent& event) {
    if(!_ui.textInputEvent(event)) {
        
    }
    if(_ui.state()) redraw();
}

Note that in general the UI needing a redraw is unrelated to whether an event was accepted by it, so the checks are separate — for example a Home key press may be accepted by a text input, but if the cursor is already at the begin of the text, it doesn't cause any visual change and thus there's no need to redraw anything.

Handles and resource ownership

Unlike traditional UI toolkits, which commonly use pointer-like abstractions to reference data, all resources in the Ui library are referenced by handles, such as a NodeHandle or an AnimatorHandle. Much like a raw pointer, a handle is a trivially copyable type (an enum in this case), but compared to a pointer it's not usable on its own. Getting the actual resource means passing the handle to a corresponding interface that actually owns given resource.

The handle consists of an index and a generation counter. Internally, the index points into a contiguous array (or several arrays) containing data for resources of given type. When a new resource is created, the handle gets an index of the next free item in that array; when it's removed, item at given index gets put back into a list of free items. Additionally, the generation counter gets incremented on every removal, which in turn causes existing handles pointing to the same index but with old generation counter to be treated as invalid.

Node handle Index Generation 2 71 generation = 23offset = …size = … Data array Node 0 generation = 1offset = …size = … Node 1 generation = 71offset = …size = … Node 2 generation = 6offset = …size = … Node 3 handle is valid if equal

Compared to pointers that can point basically anywhere, handles have several advantages. The contiguous array simplifies memory management and makes batch processing efficient, and the handle types can be only large enough to store the typical amount of data (for example NodeHandle is just 32 bits, AnimatorHandle just 16). The clear ownership model together with the generation counter then solves the problem of dangling references and resource leaks.

While in most cases you'll treat handles as opaque identifiers, it's possible to make use of the contiguous nature of their indices when storing any extra data associated with a particular handle type. For example, in case you'd want to name node handles, instead of creating a (hash)map with NodeHandle as a key, you can extract the handle index with nodeHandleId() and use a plain array instead:

struct Name {
    Containers::String name;
    UnsignedInt generation = 0;
};
Containers::Array<Name> names;



void setNodeName(Ui::NodeHandle node, Containers::StringView name) {
    UnsignedInt id = Ui::nodeHandleId(node);
    if(id >= names.size())
        arrayResize(names, id + 1);

    names[id].name = name;
    names[id].generation = Ui::nodeHandleGeneration(node);
}

Containers::StringView nodeName(Ui::NodeHandle node) {
    UnsignedInt id = Ui::nodeHandleId(node);
    if(id < names.size() && names[id].generation == Ui::nodeHandleGeneration(node))
        return names[id].name;
    return {};
}

The above snippet also makes use of the handle generation, extracted with nodeHandleGeneration(), to ensure stale names aren't returned for handles that recycle a previously used index. The generation is remembered when setting the name, and it's compared against the handle when retrieving. If it doesn't match, it's the same as if the name wouldn't be present at all. The generation counter is 0 only for NodeHandle::Null, so the default-constructed Name entries above aren't referring to valid nodes either.

Node hierarchy

At the core of the user interface, defining both the visuals and event propagation, is a hierarchy of nodes. A node is by itself just a rectangle defined by a size, an offset relative to its parent, opacity and a few behavior flags. Everything else — visuals, interactivity, layouting behavior, animation, persistent state — is handled by data, layouts and animations optionally attached to particular nodes. Those are explained further below.

A node is created using createNode() by passing an offset and size. You get a NodeHandle back, which can be then used to query and modify the node properties such as setNodeOffset() or setNodeSize().

Ui::NodeHandle panel = ui.createNode({50, 50}, {200, 150});
Ui::NodeHandle title = ui.createNode(panel, {10, 10}, {180, 20});
Ui::NodeHandle content = ui.createNode(panel, {10, 40}, {180, 100});
Panel Title Content

The first line creates a root node, which is positioned relatively to the UI itself. The other nodes then specify it as a parent, and are positioned relatively to it, so e.g. content is at offset {60, 90}. Besides positioning hierarchy, the parent/child relationship also has effect on lifetime. While a root node exists until removeNode() is called on it or until the end of the UI instance lifetime, child nodes additionally get removed if their parent is removed. Continuing from the above, if you remove the panel, the title and content will get subsequently cleaned up as well. Currently, node parent is specified during creation and cannot be subsequently changed.

Node opacity

Node opacity, controlled with setNodeOpacity(), has similar nesting behavior as node offsets. By default, all nodes are fully opaque, and the opacity gets multiplied with the parent's. In the snippet below, the effective opacity of title will be 0.6f and content will inherit the 0.8f opacity from the panel. This feature is mainly for convenient fade-in / fade-out of various parts of the UI without having to laboriously update each and every individual UI element.

ui.setNodeOpacity(panel, 0.8f);
ui.setNodeOpacity(title, 0.75f);

Top-level node hierarchies and visibility order

A root node along with all its children — in this case panel along with title and contents — is called a top-level node hierarchy. Each top-level node hierarchy is ordered relatively to other top-level hierarchies, allowing stacking of various UI elements such as dialogs, tooltips and menus. When a root node is created, it's by default put in front of all other top-level node hierarchies. Its order can be then adjusted using setNodeOrder() where the second argument is a top-level node behind which the node should be placed. Specifying NodeHandle::Null puts it in front.

Ui::NodeHandle anotherPanel = ui.createNode({200, 130}, {120, 80});

/* Put the new panel behind the first one, instead of being on top */
ui.setNodeOrder(anotherPanel, panel);
Anotherpanel Panel Title Content

It's also possible to completely remove the hierarchy from the top-level node order with clearNodeOrder(). Doing so doesn't remove any nodes, just excludes then from the visible set, and calling setNodeOrder() with that top-level node puts it back into the visibility order. This is a useful alternative to recreating a short-lived popup many times over, for example.

ui.clearNodeOrder(panel);


/* Show the panel again, on top of everything else */
ui.setNodeOrder(panel, Ui::NodeHandle::Null);

In addition to the top-level node order, visibility is defined by the node hierarchy. Parents are always behind their children (so, from the snippet above, the panel background will be visible underneath the title and content), however there's no order specified between siblings (i.e., one can't rely on title being drawn before content or vice versa). The assumption here is that majority of UIs are non-overlapping hierarchies for which such an ordering would be a costly extra step without significant benefits.

Nested top-level node hierarchies

Besides root nodes, nested nodes can be made top-level as well. They'll stay positioned relative to the parent node offset, which makes it convenient for placing various popups and tooltips next to UI elements they come from. As with root nodes, a nested node can be made top-level by calling setNodeOrder(), clearNodeOrder() then again excludes it from the visibility order.

/* Tooltip rectangle overlapping the title, shown on the top */
Ui::NodeHandle titleTooltip = ui.createNode(title, {105, 25}, {100, 20});
ui.setNodeOrder(titleTooltip, Ui::NodeHandle::Null);


/* Hide the tooltip when no longer meant to be visible */
ui.clearNodeOrder(titleTooltip);
Anotherpanel Panel Title Content Title tooltip

Compared to root nodes, nested top-level nodes are tied to the node hierarchy they come from — in particular, they can be only ordered relative to other nested top-level nodes under the same top-level node hierarchy. Changing order of the enclosing top-level nodes then brings the nested top-level hierarchies along. In the above example, assuming the tooltip wouldn't get hidden, raising the anotherPanel in front of panel would make it ordered in front of the tooltip as well.

Top-level hierarchies, nested or otherwise, can be also used to circumvent the visibility order limitations in cases where UI elements actually end up overlapping each other. One such case might be drag & drop, or various list reordering animations. Affected node sub-hierarchy can be made top-level for the time it's being moved and when it's back in place, flattenNodeOrder() puts it back to the visibility order defined by the hierarchy.

Node behavior flags

Finally, nodes can have various flags set that affect their visibility and event handling behavior. By passing NodeFlag::Clip to either createNode() or to addNodeFlags(), contents of the node and all nested nodes that overflow the node area will be clipped, clearNodeFlags() then does an inverse. The assumption is again that most UI elements don't overflow and clipping every node would be relatively expensive, so by default overflowing contents are visible.

Visibility of individual nodes can be toggled with NodeFlag::Hidden. When set, all nested nodes are hidden as well. This can also be used as an alternative to clearNodeOrder() for top-level nodes because hiding and un-hiding preserves the previous top-level node order.

NodeFlag::Disabled makes a node disabled, which effectively makes it not respond to events anymore and may also affect the visual look, depending on whether given UI element provides a disabled visual style. NodeFlag::NoEvents is a subset of that, affecting only the events but not visuals, and can be used for example when animating a gradual transition from/to a disabled state, to not have the elements react to input until the animation finishes. Both of these again propagate to all nested nodes as well, so disabling a part of the UI can be done only on the enclosing node and not on each and every child.

Event handling

Commonly, the UI is visible on top of any other content in the application (such as a game, or a model / image in case of an editor or viewer) and so the expectations is that input events should go to the UI first, as shown in the snippets above. The event handler then returns true if the event was consumed by the UI (such as a button being pressed by a pointer, or a key press causing an action on a focused input), false if not and it should fall through to the rest of the application or to the OS, browser etc.

void MyApplication::pointerMoveEvent(PointerMoveEvent& event) {
    if(_ui.pointerMoveEvent(event))
        return;

    if(_modelLoaded && event.pointers()) {
        rotateModel(event.relativePosition());
        event.setAccepted();
        return;
    }

    /* Otherwise propagating the event to the OS */
}

Node hierarchy event propagation

Inside the UI, pointer and position-dependent key events are directed to concrete nodes based on their position. Top-level nodes get iterated in a front-to-back order, and if the (appropriately scaled) event position is within the rectangle of any of those, it recurses into given hierarchy, checking the position against a rectangle of each child. The search continues until a leaf node under given position is found, to which the event is directed. If given event is accepted on that node via PointerEvent::setAccepted() etc., the event propagation stops there. If not, the propagation continues through sibling nodes that are under given position, then back up the hierarchy and then to other top-level node hierarchies until it's accepted. Example propagation of a pointer event marked blue:

Anotherpanel Panel Title Content Title tooltip

If the event is accepted on any node, that particular event function returns true. If it isn't accepted on any node, or if there isn't any node at given position in the first place, false is returned.

Pointer capture, pressed and hovered node tracking

Especially with touch input where the aiming precision is rather coarse, it commonly happens that the finger moves between a tap press and a release, sometimes even outside of the tapped element. Another similar is when a mouse cursor gets dragged outside of the narrow area of a scrollbar. In such cases it would be annoying if the UI would cancel the action or, worse, performed some other action entirely, and that's what pointer capture is designed to solve.

Without pointer capture With pointer capture A B A B Hovered on A Not pressed Not pressed Not hovered Pressed on A Hovered on A Pressed on A Not hovered Hovered on A Pressed on A Hovered on A Pressed on A Not pressed Hovered on A Not sent Sent to A Captured on A Captured on A Captured on A Captured on A Sent to A Hovered on A Not pressed Not pressed Not hovered Pressed on A Hovered on A Not pressed Hovered on B Hovered on A Not pressed Hovered on A Not pressed Not pressed Hovered on A Not sent Sent to A Sent to A Sent to B Sent to A Sent to A Sent to A

When a node press happens as a result of pointerPressEvent(), the node gets remembered. By default that enables pointer capture, so if a pointerMoveEvent() then drags the pointer outside, the events are still sent to the original node (marked as A in the above diagram), and A is still considered to be pressed, but not hovered. In comparison, with capture disabled, a drag outside would lose the pressed state, and the events would be sent to whatever other node is underneath (marked as B above), and said to be hovered on that node instead. Note that B isn't considered to be pressed in that case in order to distinguish drags that originated on given node from drags that originated outside.

When the (still pressed) pointer returns to A, in the captured case it's the same as if it would never leave. A release from a pointerReleaseEvent() could then generate a tap or click in given event handler, for example. With capture disabled, a tap or click would happen only if the pointer would never leave. Ultimately, pointer release implicitly removes the capture again.

All this state is exposed via PointerEvent::isNodePressed(), isNodeHovered() and isCaptured() and similar properties in other event classes for use by event handler implementations. For diagnostic purposes it's also exposed via currentPressedNode(), currentHoveredNode() and currentCapturedNode(). Additionally, position of the previous pointer event is tracked in currentGlobalPointerPosition() and is used to fill in the value of PointerMoveEvent::relativePosition().

While pointer capture is a good default, in certain use cases such as drag & drop it's desirable to know the node the pointer is being dragged to instead of the events being always sent to the originating node. For that, the press and move event handlers can toggle the capture using PointerEvent::setCaptured() / PointerMoveEvent::setCaptured().

Key and text input and node focus

By default, keyPressEvent() and keyReleaseEvent() is directed to a node under pointer, based on the location at which the last pointer event happened. This is useful for implementing workflows common in 2D/3D editing software, where simply hovering a particular UI element and pressing a key performs an action, without having to activate the element first by a click. As with pointer events, if KeyEvent::setAccepted() isn't called by the handler, the functions return false, signalling that the event should be propagated elsewhere.

Text editing however has usually a different workflow — clicking an input element makes it focused, after which it accepts keyboard input regardless of pointer position. Only nodes that are marked with NodeFlag::Focusable can be focused. Focus is then activated either by a pointer press on given node, or programmatically by passing given node handle to focusEvent(). Then keyPressEvent(), keyReleaseEvent() as well as textInputEvent() are all directed to that node with no propagation anywhere else. The node is then blurred by a pointer press outside of its area, or programmatically by passing NodeHandle::Null to focusEvent(), after which key events again go only to the node under cursor and textInputEvent() is ignored completely, returning false always.

Information about whether a node the event is called on is focused is available via KeyEvent::isNodeFocused() and similarly on other event classes. For diagnostic purposes it's also available through currentFocusedNode().

Pointer event fallthrough

By default, a pointer event is directed only to a single node, either as a result of the hierarchy propagation, or because given node captures the event, and as soon as the event is accepted it's not sent to any other node.

Sometimes it's however desirable to have events fall through to outer nodes, for example to allow scrolling an area that's otherwise full of active elements. This can be achieved by marking given node with NodeFlag::FallthroughPointerEvents. When a pointer event happens on any child of such a node, the event is then sent to it as well, with PointerEvent::isFallthrough() / PointerMoveEvent::isFallthrough() being true.

The node can then either just observe the event without accepting, in which case the event outcome isn't any different compared to when the node isn't marked with NodeFlag::FallthroughPointerEvents. For a fallthrough event the PointerEvent::isNodePressed(), isNodeHovered() and isCaptured() properties are inherited from the original node, with the logic that if a child node was pressed or hovered, its parent (whose area includes the child node) is pressed or hovered as well.

Or, instead of observing, the fallthrough event can be accepted. Accepting the fallthrough event makes the node inherit the pressed, hovered and captured properties from the original one, and the original node gets a PointerCancelEvent in exchange. The fallthrough works across nested top-level nodes as well. Continuing from the event propagation example above and assuming the panel node is marked as fallthrough, such as to implement a "pull down to refresh" behavior, the event handling could look like this:

Anotherpanel Panel Title Content Title tooltip

It's possible for multiple nodes in a hierarchy chain to be marked with NodeFlag::FallthroughPointerEvents and the fallthrough events get sent to all of them in reverse hierarchy order. Accepting then always sends the cancel event to the node that previously accepted the event, either the original one or a previous fallthrough event, and the pressed / hovered / captured node ends up being set to the last node in the chain that accepted the event.

DPI awareness

If you use the application integration shown above, in particular the constructor and setSize() overloads taking an application instance, the UI is already made DPI-aware and no extra steps need to be done. Nevertheless, it may be useful to know what happens underneath. There are three separate concepts for DPI-aware UI rendering:

  • UI size — size of the user interface to which all widgets are positioned
  • Window size — size of the window to which all input events are related
  • Framebuffer size — size of the framebuffer the UI is being rendered to

Depending on the platform and use case, each of these three values can be different. For example, a game menu screen can have the UI size the same regardless of window size. Or on Retina macOS you can have different window and framebuffer size and the UI size might be related to window size but independent on the framebuffer size.

With the Platform::*Application classes, you usually have three values at your disposal — windowSize(), framebufferSize() and dpiScaling(). What the application integration does internally is equivalent to the following — the UI size scales with window size and it's scaled based on either the framebuffer / window size ratio or the DPI scaling value:

Ui::UserInterfaceGL ui{
    Vector2{windowSize()}/dpiScaling(),
    Vector2{windowSize()}, framebufferSize(), };

Or, for example, to have the UI size derived from window size while gracefully dealing with extremely small or extremely large windows, you can apply Math::clamp() on top with some defined bounds. Windows outside of the reasonable size range will then get a scaled version of the UI instead:

Ui::UserInterfaceGL ui{
    Math::clamp({640.0f, 360.0f}, {1920.0f, 1080.0f},
        Vector2{windowSize()}/dpiScaling()),
    Vector2{windowSize()}, framebufferSize(), };

Ultimately, if you want the UI to have the same size and just scale with bigger window sizes, pass a fixed value to the UI size:

Ui::UserInterfaceGL ui(
    {800, 600},
    Vector2{windowSize()}, framebufferSize(), );

With all variants above, the input events will still be dealt with correctly, being scaled from the window coordinates to the actual UI size.

Derived classes

class UserInterface new in Git master
Main user interface.

Constructors, destructors, conversion operators

AbstractUserInterface(NoCreateT) explicit
Construct without creating the user interface with concrete parameters.
AbstractUserInterface(const Vector2& size, const Vector2& windowSize, const Vector2i& framebufferSize) explicit
Construct.
template<class Application, class = decltype(Implementation::ApplicationSizeConverter<Application>::set(std::declval<AbstractUserInterface&>(), std::declval<const Application&>()))>
AbstractUserInterface(const Application& application) explicit
Construct with properties taken from an application instance.
AbstractUserInterface(const Vector2i& size) explicit
Construct with an unscaled size.
AbstractUserInterface(const AbstractUserInterface&) deleted
Copying is not allowed.
AbstractUserInterface(AbstractUserInterface&&) noexcept
Move constructor.

Public functions

auto operator=(const AbstractUserInterface&) -> AbstractUserInterface& deleted
Copying is not allowed.
auto operator=(AbstractUserInterface&&) -> AbstractUserInterface& noexcept
Move assignment.
auto size() const -> Vector2
User interface size.
auto windowSize() const -> Vector2
Window size.
auto framebufferSize() const -> Vector2i
Framebuffer size.
auto setSize(const Vector2& size, const Vector2& windowSize, const Vector2i& framebufferSize) -> AbstractUserInterface&
Set user interface size.
template<class ApplicationOrViewportEvent, class = decltype(Implementation::ApplicationSizeConverter<ApplicationOrViewportEvent>::set(std::declval<AbstractUserInterface&>(), std::declval<const ApplicationOrViewportEvent&>()))>
auto setSize(const ApplicationOrViewportEvent& applicationOrViewportEvent) -> AbstractUserInterface&
Set user interface size from an application or a viewport event instance.
auto setSize(const Vector2i& size) -> AbstractUserInterface&
Set unscaled user interface size.
auto state() const -> UserInterfaceStates
User interface state.
auto animationTime() const -> Nanoseconds
Animation time.
auto clean() -> AbstractUserInterface&
Clean orphaned nodes, data and no longer valid data attachments.
auto advanceAnimations(Nanoseconds time) -> AbstractUserInterface&
Advance active animations.
auto update() -> AbstractUserInterface&
Update node hierarchy, data order and data contents for drawing and event processing.
auto draw() -> AbstractUserInterface&
Draw the user interface.
auto pointerPressEvent(const Vector2& globalPosition, PointerEvent& event) -> bool
Handle a pointer press event.
template<class Event, class ... Args, class = decltype(Implementation::PointerEventConverter<Event>::press(std::declval<AbstractUserInterface&>(), std::declval<Event&>(), std::declval<Args>()...))>
auto pointerPressEvent(Event& event, Args && ... args) -> bool
Handle an external pointer press event.
auto pointerReleaseEvent(const Vector2& globalPosition, PointerEvent& event) -> bool
Handle a pointer release event.
template<class Event, class ... Args, class = decltype(Implementation::PointerEventConverter<Event>::release(std::declval<AbstractUserInterface&>(), std::declval<Event&>(), std::declval<Args>()...))>
auto pointerReleaseEvent(Event& event, Args && ... args) -> bool
Handle an external pointer release event.
auto pointerMoveEvent(const Vector2& globalPosition, PointerMoveEvent& event) -> bool
Handle a pointer move event.
template<class Event, class ... Args, class = decltype(Implementation::PointerMoveEventConverter<Event>::move(std::declval<AbstractUserInterface&>(), std::declval<Event&>(), std::declval<Args>()...))>
auto pointerMoveEvent(Event& event, Args && ... args) -> bool
Handle an external pointer move event.
auto focusEvent(NodeHandle node, FocusEvent& event) -> bool
Handle a focus event.
auto keyPressEvent(KeyEvent& event) -> bool
Handle a key press event.
template<class Event, class ... Args, class = decltype(Implementation::KeyEventConverter<Event>::press(std::declval<AbstractUserInterface&>(), std::declval<Event&>(), std::declval<Args>()...))>
auto keyPressEvent(Event& event, Args && ... args) -> bool
Handle an external key press event.
auto keyReleaseEvent(KeyEvent& event) -> bool
Handle a key release event.
template<class Event, class ... Args, class = decltype(Implementation::KeyEventConverter<Event>::release(std::declval<AbstractUserInterface&>(), std::declval<Event&>(), std::declval<Args>()...))>
auto keyReleaseEvent(Event& event, Args && ... args) -> bool
Handle an external key release event.
auto textInputEvent(TextInputEvent& event) -> bool
Handle a text input event.
template<class Event, class ... Args, class = decltype(Implementation::TextInputEventConverter<Event>::trigger(std::declval<AbstractUserInterface&>(), std::declval<Event&>(), std::declval<Args>()...))>
auto textInputEvent(Event& event, Args && ... args) -> bool
Handle an external text input event.
auto currentPressedNode() const -> NodeHandle
Node pressed by last pointer event.
auto currentCapturedNode() const -> NodeHandle
Node captured by last pointer event.
auto currentHoveredNode() const -> NodeHandle
Node hovered by last pointer event.
auto currentFocusedNode() const -> NodeHandle
Node focused by last pointer or focus event.
auto currentGlobalPointerPosition() const -> Containers::Optional<Vector2>
Position of last pointer event.

Renderer management

auto setRendererInstance(Containers::Pointer<AbstractRenderer>&& instance) -> AbstractRenderer&
Set renderer instance.
template<class T>
auto setRendererInstance(Containers::Pointer<T>&& instance) -> T&
auto hasRenderer() const -> bool
Whether a renderer instance has been set.
auto renderer() -> AbstractRenderer&
Renderer instance.
auto renderer() const -> const AbstractRenderer&
template<class T>
auto renderer() -> T&
Renderer instance in a concrete type.
template<class T>
auto renderer() const -> const T&

Layer and data management

auto layerCapacity() const -> std::size_t
Capacity of the layer storage.
auto layerUsedCount() const -> std::size_t
Count of used items in the layer storage.
auto isHandleValid(LayerHandle handle) const -> bool
Whether a layer handle is valid.
auto isHandleValid(DataHandle handle) const -> bool
Whether a data handle is valid.
auto layerFirst() const -> LayerHandle
First layer in draw and event processing order.
auto layerLast() const -> LayerHandle
Last layer in draw and event processing order.
auto layerPrevious(LayerHandle handle) const -> LayerHandle
Previous layer in draw and event processing order.
auto layerNext(LayerHandle handle) const -> LayerHandle
Next layer in draw and event processing order.
auto createLayer(LayerHandle before = LayerHandle::Null) -> LayerHandle
Create a layer.
auto setLayerInstance(Containers::Pointer<AbstractLayer>&& instance) -> AbstractLayer&
Set a layer instance.
template<class T>
auto setLayerInstance(Containers::Pointer<T>&& instance) -> T&
auto layer(LayerHandle handle) -> AbstractLayer&
Layer instance.
auto layer(LayerHandle handle) const -> const AbstractLayer&
template<class T>
auto layer(LayerHandle handle) -> T&
Layer instance in a concrete type.
template<class T>
auto layer(LayerHandle handle) const -> const T&
void removeLayer(LayerHandle handle)
Remove a layer.
void attachData(NodeHandle node, DataHandle data)
Attach data to a node.

Node layouter management

auto layouterCapacity() const -> std::size_t
Capacity of the layouter storage.
auto layouterUsedCount() const -> std::size_t
Count of used items in the layouter storage.
auto isHandleValid(LayouterHandle handle) const -> bool
Whether a layouter handle is valid.
auto isHandleValid(LayoutHandle handle) const -> bool
Whether a layout handle is valid.
auto layouterFirst() const -> LayouterHandle
First layouter in the layout calculation order.
auto layouterLast() const -> LayouterHandle
Last layouter in the layout calculation order.
auto layouterPrevious(LayouterHandle handle) const -> LayouterHandle
Previous layouter in the layout calculation order.
auto layouterNext(LayouterHandle handle) const -> LayouterHandle
Next layouter in the layout calculation order.
auto createLayouter(LayouterHandle before = LayouterHandle::Null) -> LayouterHandle
Create a layouter.
auto setLayouterInstance(Containers::Pointer<AbstractLayouter>&& instance) -> AbstractLayouter&
Set a layouter instance.
template<class T>
auto setLayouterInstance(Containers::Pointer<T>&& instance) -> T&
auto layouter(LayouterHandle handle) -> AbstractLayouter&
Layouter instance.
auto layouter(LayouterHandle handle) const -> const AbstractLayouter&
template<class T>
auto layouter(LayouterHandle handle) -> T&
Layouter instance in a concrete type.
template<class T>
auto layouter(LayouterHandle handle) const -> const T&
void removeLayouter(LayouterHandle handle)
Remove a layouter.

Animator management

auto animatorCapacity() const -> std::size_t
Capacity of the animator storage.
auto animatorUsedCount() const -> std::size_t
Count of used items in the animator storage.
auto isHandleValid(AnimatorHandle handle) const -> bool
Whether an animator handle is valid.
auto isHandleValid(AnimationHandle handle) const -> bool
Whether an animation handle is valid.
auto createAnimator() -> AnimatorHandle
Create an animator.
auto setGenericAnimatorInstance(Containers::Pointer<AbstractGenericAnimator>&& instance) -> AbstractGenericAnimator&
Set a generic animator instance.
template<class T>
auto setGenericAnimatorInstance(Containers::Pointer<T>&& instance) -> T&
auto setNodeAnimatorInstance(Containers::Pointer<AbstractNodeAnimator>&& instance) -> AbstractNodeAnimator&
Set a node animator instance.
template<class T>
auto setNodeAnimatorInstance(Containers::Pointer<T>&& instance) -> T&
auto setDataAnimatorInstance(Containers::Pointer<AbstractDataAnimator>&& instance) -> AbstractDataAnimator&
Set a data animator instance.
template<class T>
auto setDataAnimatorInstance(Containers::Pointer<T>&& instance) -> T&
auto setStyleAnimatorInstance(Containers::Pointer<AbstractStyleAnimator>&& instance) -> AbstractStyleAnimator&
Set a style animator instance.
template<class T>
auto setStyleAnimatorInstance(Containers::Pointer<T>&& instance) -> T&
auto animator(AnimatorHandle handle) -> AbstractAnimator&
Animator instance.
auto animator(AnimatorHandle handle) const -> const AbstractAnimator&
template<class T>
auto animator(AnimatorHandle handle) -> T&
Animator instance in a concrete type.
template<class T>
auto animator(AnimatorHandle handle) const -> const T&
void removeAnimator(AnimatorHandle handle)
Remove an animator.
void attachAnimation(NodeHandle node, AnimationHandle animation)
Attach an animation to a node.
void attachAnimation(DataHandle data, AnimationHandle animation)
Attach an animation to a data.

Node management

auto nodeCapacity() const -> std::size_t
Current capacity of the node storage.
auto nodeUsedCount() const -> std::size_t
Count of used items in the node storage.
auto isHandleValid(NodeHandle handle) const -> bool
Whether a node handle is valid.
auto createNode(NodeHandle parent, const Vector2& offset, const Vector2& size, NodeFlags flags = {}) -> NodeHandle
Create a node.
auto createNode(const Vector2& offset, const Vector2& size, NodeFlags flags = {}) -> NodeHandle
Create a root node.
auto nodeParent(NodeHandle handle) const -> NodeHandle
Node parent.
auto nodeOffset(NodeHandle handle) const -> Vector2
Node offset relative to its parent.
void setNodeOffset(NodeHandle handle, const Vector2& offset)
Set node offset relative to its parent.
auto nodeSize(NodeHandle handle) const -> Vector2
Node size.
void setNodeSize(NodeHandle handle, const Vector2& size)
Set node size.
auto nodeOpacity(NodeHandle handle) const -> Float
Node opacity.
void setNodeOpacity(NodeHandle handle, Float opacity)
Set node opacity.
auto nodeFlags(NodeHandle handle) const -> NodeFlags
Node flags.
void setNodeFlags(NodeHandle handle, NodeFlags flags)
Set node flags.
void addNodeFlags(NodeHandle handle, NodeFlags flags)
Add node flags.
void clearNodeFlags(NodeHandle handle, NodeFlags flags)
Clear node flags.
void removeNode(NodeHandle handle)
Remove a node.

Top-level node draw and event processing order management

auto nodeOrderCapacity() const -> std::size_t
Capacity of the top-level node order storage.
auto nodeOrderUsedCount() const -> std::size_t
Count of used items in the top-level node order storage.
auto nodeOrderFirst() const -> NodeHandle
First top-level node in draw and event processing order.
auto nodeOrderLast() const -> NodeHandle
Last top-level node in draw and event processing order.
auto isNodeTopLevel(NodeHandle handle) const -> bool
Whether a node is top-level for draw and event processing.
auto isNodeOrdered(NodeHandle handle) const -> bool
Whether a node is top-level and is included in a draw and event processing order.
auto nodeOrderPrevious(NodeHandle handle) const -> NodeHandle
Previous node in draw and event processing order.
auto nodeOrderNext(NodeHandle handle) const -> NodeHandle
Next node in draw and event processing order.
auto nodeOrderLastNested(NodeHandle handle) const -> NodeHandle
Last node in draw and event processing order nested under this node.
void setNodeOrder(NodeHandle handle, NodeHandle behind)
Order a top-level node for draw and event processing.
void clearNodeOrder(NodeHandle handle)
Clear a node from the draw and event processing order.
void flattenNodeOrder(NodeHandle handle)
Flatten a non-root top-level node back to the usual order defined by the node hierarchy.

Function documentation

Magnum::Ui::AbstractUserInterface::AbstractUserInterface(NoCreateT) explicit

Construct without creating the user interface with concrete parameters.

You're expected to call setSize() afterwards in order to define scaling of event coordinates, node positions and projection matrices for drawing.

Magnum::Ui::AbstractUserInterface::AbstractUserInterface(const Vector2& size, const Vector2& windowSize, const Vector2i& framebufferSize) explicit

Construct.

Parameters
size Size of the user interface to which everything is positioned
windowSize Size of the window to which all input events are related
framebufferSize Size of the window framebuffer. On some platforms with HiDPI screens may be different from window size.

Equivalent to constructing with AbstractUserInterface(NoCreateT) and then calling setSize(const Vector2&, const Vector2&, const Vector2i&). See its documentation for more information.

template<class Application, class = decltype(Implementation::ApplicationSizeConverter<Application>::set(std::declval<AbstractUserInterface&>(), std::declval<const Application&>()))>
Magnum::Ui::AbstractUserInterface::AbstractUserInterface(const Application& application) explicit

Construct with properties taken from an application instance.

Parameters
application Application instance to query properties from

Equivalent to constructing with AbstractUserInterface(NoCreateT) and then calling setSize(const ApplicationOrViewportEvent&). See its documentation for more information.

Magnum::Ui::AbstractUserInterface::AbstractUserInterface(const Vector2i& size) explicit

Construct with an unscaled size.

Delegates to AbstractUserInterface(const Vector2&, const Vector2&, const Vector2i&) with all sizes set to size. Doing so assumes that the coordinate system in which events are passed matches framebuffer size.

Magnum::Ui::AbstractUserInterface::AbstractUserInterface(AbstractUserInterface&&) noexcept

Move constructor.

Performs a destructive move, i.e. the original object isn't usable afterwards anymore.

Vector2 Magnum::Ui::AbstractUserInterface::size() const

User interface size.

Node positioning is in respect to this size. If setSize() or AbstractUserInterface(const Vector2&, const Vector2&, const Vector2i&) wasn't called yet, initial value is a zero vector.

Vector2 Magnum::Ui::AbstractUserInterface::windowSize() const

Window size.

Global event position in pointerPressEvent(), pointerReleaseEvent() and pointerMoveEvent() is in respect to this size. If setSize() or AbstractUserInterface(const Vector2&, const Vector2&, const Vector2i&) wasn't called yet, initial value is a zero vector.

Vector2i Magnum::Ui::AbstractUserInterface::framebufferSize() const

Framebuffer size.

Rendering performed by layers is in respect to this size. If setSize() or AbstractUserInterface(const Vector2&, const Vector2&, const Vector2i&) wasn't called yet, initial value is a zero vector.

AbstractUserInterface& Magnum::Ui::AbstractUserInterface::setSize(const Vector2& size, const Vector2& windowSize, const Vector2i& framebufferSize)

Set user interface size.

Parameters
size Size of the user interface to which everything is positioned
windowSize Size of the window to which all input events are related
framebufferSize Size of the window framebuffer. On some platforms with HiDPI screens may be different from window size.
Returns Reference to self (for method chaining)

All sizes are expected to be non-zero, origin is top left for all.

After calling this function, the pointerPressEvent(), pointerReleaseEvent() and pointerMoveEvent() functions take the global event position with respect to windowSize, which is then rescaled to match size when exposed through PointerEvent. The size and framebufferSize is passed through to AbstractLayer::setSize() to all layers with LayerFeature::Draw so they can set appropriate projection and other framebuffer-related properties, similarly the size is passed through to AbstractLayouter::setSize() to all layouters.

There's no default size and this function is expected to be called before the first update() happens, either directly or through the AbstractUserInterface(const Vector2&, const Vector2&, const Vector2i&) constructor. It's allowed to call this function for the first time even after node, layers or data were created.

Calling this function with new values will update the event position scaling accordingly. If size or framebufferSize changes, AbstractLayer::setSize() is called on all layers; if size changes, AbstractLayouter::setSize() is called on all layouters. If size changes and any nodes were already created, UserInterfaceState::NeedsNodeClipUpdate is set. If a renderer instance is set, AbstractRenderer::setupFramebuffers() is called to make the renderer populate or update its internal state. If a renderer instance isn't set yet when calling this function, the framebuffer setup is performed in the next setRendererInstance() call instead.

template<class ApplicationOrViewportEvent, class = decltype(Implementation::ApplicationSizeConverter<ApplicationOrViewportEvent>::set(std::declval<AbstractUserInterface&>(), std::declval<const ApplicationOrViewportEvent&>()))>
AbstractUserInterface& Magnum::Ui::AbstractUserInterface::setSize(const ApplicationOrViewportEvent& applicationOrViewportEvent)

Set user interface size from an application or a viewport event instance.

Parameters
applicationOrViewportEvent Application or a viewport event instance to query properties from

Delegates to setSize(const Vector2&, const Vector2&, const Vector2i&) using window size, framebuffer size and DPI scaling queried from the applicationOrViewportEvent instance. See its documentation, Integration with application libraries and DPI awareness for more information.

AbstractUserInterface& Magnum::Ui::AbstractUserInterface::setSize(const Vector2i& size)

Set unscaled user interface size.

Returns Reference to self (for method chaining)

Calls setSize(const Vector2&, const Vector2&, const Vector2i&) with all sizes set to size. Doing so assumes that the coordinate system in which events are passed matches framebuffer size.

UserInterfaceStates Magnum::Ui::AbstractUserInterface::state() const

User interface state.

See the UserInterfaceState enum for more information. By default no flags are set.

Nanoseconds Magnum::Ui::AbstractUserInterface::animationTime() const

Animation time.

Time value last passed to advanceAnimations(). Initial value is 0_nsec.

AbstractUserInterface& Magnum::Ui::AbstractUserInterface::clean()

Clean orphaned nodes, data and no longer valid data attachments.

Returns Reference to self (for method chaining)

Called implicitly from update() and subsequently also from draw() and all event processing functions. If state() contains neither UserInterfaceState::NeedsNodeClean nor UserInterfaceState::NeedsDataClean, this function is a no-op, otherwise it performs a subset of the following depending on the state:

After calling this function, state() doesn't contain UserInterfaceState::NeedsNodeClean anymore; nodeUsedCount() and AbstractLayer::usedCount() may get smaller.

AbstractUserInterface& Magnum::Ui::AbstractUserInterface::advanceAnimations(Nanoseconds time)

Advance active animations.

Returns Reference to self (for method chaining)

Implicitly calls clean(), should be called before any update() or draw() for given frame. Expects that time is greater or equal to animationTime(). If state() contains UserInterfaceState::NeedsAnimationAdvance, this function delegates to AbstractAnimator::update() and then a corresponding animator-specific advance function such as AbstractGenericAnimator::advance(), AbstractNodeAnimator::advance() or layer-specific AbstractLayer::advanceAnimations() on all animator instances that have AnimatorState::NeedsAdvance set.

Calling this function updates animationTime(). Afterwards, state() may still contain UserInterfaceState::NeedsAnimationAdvance, signalling that animation advance is still needed the next fram. It may also have other states added depending on what all the animators touched, and a subsequent call to draw() (or directly to clean() and update() before that) for given frame will take care of correctly updating the internal state.

AbstractUserInterface& Magnum::Ui::AbstractUserInterface::update()

Update node hierarchy, data order and data contents for drawing and event processing.

Returns Reference to self (for method chaining)

Expects that either setSize() was called or the AbstractUserInterface(const Vector2&, const Vector2&, const Vector2i&) constructor was used.

Implicitly calls clean(); called implicitly from draw() and all event processing functions. If state() contains none of UserInterfaceState::NeedsDataUpdate, UserInterfaceState::NeedsDataAttachmentUpdate, UserInterfaceState::NeedsNodeEnabledUpdate, UserInterfaceState::NeedsNodeClipUpdate, UserInterfaceState::NeedsLayoutUpdate, UserInterfaceState::NeedsLayoutAssignmentUpdate or UserInterfaceState::NeedsNodeUpdate, this function is a no-op, otherwise it performs a subset of the following depending on the state, in order:

After calling this function, state() is empty apart from UserInterfaceState::NeedsAnimationAdvance, which may be present if there are any animators for which advanceAnimations() should be called.

AbstractUserInterface& Magnum::Ui::AbstractUserInterface::draw()

Draw the user interface.

Returns Reference to self (for method chaining)

Implicitly calls update(), which in turn implicitly calls clean(). Performs the following:

bool Magnum::Ui::AbstractUserInterface::pointerPressEvent(const Vector2& globalPosition, PointerEvent& event)

Handle a pointer press event.

Implicitly calls update(), which in turn implicitly calls clean(). The globalPosition is assumed to be in respect to windowSize(), and is internally scaled to match size() before being set to PointerEvent.

If the event is not primary and a node was captured by a previous pointerPressEvent(), pointerMoveEvent() or a non-primary pointerReleaseEvent(), calls pointerPressEvent() on all data attached to that node even if the event happens outside of its area, with the event position made relative to the node.

Otherwise, if either the event is primary or no node is captured, propagates it to nodes under (scaled) globalPosition and calls AbstractLayer::pointerPressEvent() on all data attached to it belonging to layers that support LayerFeature::Event, in a front-to-back order as described in Node hierarchy event propagation. For each such node, the event is always called on all attached data, regardless of the accept status. For each call the PointerEvent::position() is made relative to the node to which given data is attached.

If the press happened with Pointer::MouseLeft, primary Pointer::Finger or Pointer::Pen, the currently focused node is affected as well. If the node the event was accepted on is different from currently focused node (if there's any), AbstractLayer::blurEvent() is called on all data attached to that node. Then, if the node has NodeFlag::Focusable set, AbstractLayer::focusEvent() is called on all data attached to it. If any data accept the focus event, the node is treated as focused and receives all following key events until a primary pointerPressEvent() outside of this node happens or until a different node is made focused using focusEvent(). If the node is not NodeFlag::Focusable or the focus event was not accepted by any data, the currently focused node is reset. If a primary press event happens on a NodeFlag::Focusable node that's already focused, AbstractLayer::focusEvent() gets called on it again, without any preceding AbstractLayer::blurEvent(). If that focus event is not accepted however, the node subsequently receives an AbstractLayer::blurEvent(). If the event isn't primary, the current focused node isn't affected in any way.

If the event is primary, the node that accepted the event is remembered as pressed. The node that accepted the primary event also implicitly captures all further pointer events until and including a primary pointerReleaseEvent() even if they happen outside of its area, unless PointerEvent::setCaptured() is called by the implementation to disable this behavior. The capture can be also removed from a later pointerMoveEvent() or a non-primary pointerReleaseEvent(). Any node that was already captured when calling this function with a primary event is ignored. If the event isn't primary, the node is not rememebered as pressed and neither it captures any further events.

If no node accepted the event or there wasn't any visible event handling node at given position, the previously remembered pressed and captured nodes are reset if and only if the event is primary.

After the above, if the event was accepted on any node or wasn't but is still captured on any node, it falls through upwards the parent hierarchy, calling AbstractLayer::pointerPressEvent() on all data attached to nodes that have NodeFlag::FallthroughPointerEvents set, with PointerEvent::isFallthrough() being true and the isNodePressed(), isNodeHovered(), isNodeFocused() and isCaptured() properties inherited as described in Pointer event fallthrough. If any data accept the event, AbstractLayer::pointerCancelEvent() is called on all data attached to any previously captured node, and if the event is primary, also any previously pressed, hovered or focused nodes. The captured state is then transferred to the new node, and for a primary event the pressed and hovered state is transferred as well. However, currently, no AbstractLayer::pointerEnterEvent() is subsequently sent to the new node. Focus is lost for a primary event unless the node that accepted the fallthrough event is itself focused, in which case it stays unchanged. This fallthrough then repeats until a root node is reached, and each time an event is accepted on a new node it's cancelled on the previous as appropriate.

Returns true if the press event was accepted by at least one data, false if it wasn't or there wasn't any visible event handling node at given position and thus the event should be propagated further. Accept status of the focus and blur events doesn't have any effect on the return value.

Expects that the event is not accepted yet.

template<class Event, class ... Args, class = decltype(Implementation::PointerEventConverter<Event>::press(std::declval<AbstractUserInterface&>(), std::declval<Event&>(), std::declval<Args>()...))>
bool Magnum::Ui::AbstractUserInterface::pointerPressEvent(Event& event, Args && ... args)

Handle an external pointer press event.

Converts the event to a PointerEvent and delegates to pointerPressEvent(const Vector2&, PointerEvent&), see its documentation and Integration with application libraries for more information. The args allow passing optional extra arguments to a particular event converter.

bool Magnum::Ui::AbstractUserInterface::pointerReleaseEvent(const Vector2& globalPosition, PointerEvent& event)

Handle a pointer release event.

Implicitly calls update(), which in turn implicitly calls clean(). The globalPosition is assumed to be in respect to windowSize(), and is internally scaled to match size() before being set to PointerEvent.

If a node was captured by a previous pointerPressEvent(), pointerMoveEvent() or a non-primary pointerReleaseEvent(), calls AbstractLayer::pointerReleaseEvent() on all data attached to that node even if the event happens outside of its area, with the event position made relative to the node.

Otherwise, if a node wasn't captured, propagates the event to nodes under (scaled) globalPosition and calls AbstractLayer::pointerReleaseEvent() on all data attached to it belonging to layers that support LayerFeature::Event, in a front-to-back order as described in Node hierarchy event propagation. For each such node, the event is always called on all attached data, regardless of the accept status. For each call the PointerEvent::position() is made relative to the node to which given data is attached.

If the event is primary and a node is captured, the capture is implicitly released after calling this function independently of whether PointerEvent::setCaptured() was set by the implementation. If the event isn't primary, the current captured node isn't affected by default, but the implementation can both release the existing capture or make the containing node capture future events by setting PointerEvent::setCaptured().

After the above, if the event was accepted on any node or wasn't but is still captured on any node, it falls through upwards the parent hierarchy, calling AbstractLayer::pointerReleaseEvent() on all data attached to nodes that have NodeFlag::FallthroughPointerEvents set, with PointerEvent::isFallthrough() being true and the isNodePressed(), isNodeHovered(), isNodeFocused() and isCaptured() properties inherited as described in Pointer event fallthrough. If any data accept the event, AbstractLayer::pointerCancelEvent() is called on all data attached to any previously captured node, and if the event is primary, also any previously pressed, hovered or focused nodes. The captured state is then transferred to the new node, and for a primary event the pressed and hovered state is transferred as well. However, currently, no AbstractLayer::pointerEnterEvent() is subsequently sent to the new node. Focus is lost for a primary event unless the node that accepted the fallthrough event is itself focused, in which case it stays unchanged. This fallthrough then repeats until a root node is reached, and each time an event is accepted on a new node it's cancelled on the previous as appropriate.

Returns true if the event was accepted by at least one data, false if it wasn't or there wasn't any visible event handling node at given position and thus the event should be propagated further.

Expects that the event is not accepted yet.

template<class Event, class ... Args, class = decltype(Implementation::PointerEventConverter<Event>::release(std::declval<AbstractUserInterface&>(), std::declval<Event&>(), std::declval<Args>()...))>
bool Magnum::Ui::AbstractUserInterface::pointerReleaseEvent(Event& event, Args && ... args)

Handle an external pointer release event.

Converts the event to a PointerEvent and delegates to pointerReleaseEvent(const Vector2&, PointerEvent&), see its documentation and Integration with application libraries for more information. The args allow passing optional extra arguments to a particular event converter.

bool Magnum::Ui::AbstractUserInterface::pointerMoveEvent(const Vector2& globalPosition, PointerMoveEvent& event)

Handle a pointer move event.

Implicitly calls update(), which in turn implicitly calls clean(). The globalPosition is assumed to be in respect to windowSize(), and is internally scaled to match size() before being set to PointerEvent.

If a node was captured by a previous pointerPressEvent() or pointerMoveEvent(), pointerReleaseEvent() wasn't called yet and the node wasn't removed since, calls AbstractLayer::pointerMoveEvent() on all data attached to that node even if it happens outside of its area, with the event position made relative to the node. If the move event is primary, happened inside the node area and is accepted by at least one data, the node is treated as hovered, otherwise as not hovered. An AbstractLayer::pointerEnterEvent() or pointerLeaveEvent() is then called for all data attached to the captured node if the node hover status changed with the same event except for PointerMoveEvent::relativePosition() which is reset to a zero vector. No corresponding leave / enter event is called for any other node in this case. If the move event isn't primary, the current hovered node isn't affected in any way. If the move, enter or leave event implementations called PointerMoveEvent::setCaptured() resulting in it being false, the capture is released after this function, otherwise it stays unchanged. For primary events the capture reset is performed regardless of the event accept status, for non-primary events only if they're accepted.

Otherwise, if a node wasn't captured, propagates the event to nodes under (scaled) globalPosition and calls AbstractLayer::pointerMoveEvent() on all data attached to it belonging to layers that support LayerFeature::Event, in a front-to-back order as described in Node hierarchy event propagation. For each such node, the event is always called on all attached data, regardless of the accept status. If the move event is primary, the node on which the data accepted the event is then treated as hovered; if no data accepted the event, there's no hovered node. For each call the PointerMoveEvent::position() is made relative to the node to which given data is attached. If the currently hovered node changed, an AbstractLayer::pointerLeaveEvent() is then called for all data attached to a previously hovered node if it exists, and then a corresponding AbstractLayer::pointerEnterEvent() is called for all data attached to the currently hovered node if it exists, in both cases with the same event except for PointerMoveEvent::position() which is made relative to the particular node it's called on and PointerMoveEvent::relativePosition() which is reset to a zero vector. If the move event isn't primary, the current hovered node isn't affected in any way. If any accepted move event or any enter event called PointerMoveEvent::setCaptured() resulting in it being true, the containing node implicitly captures all further pointer events until and including a pointerReleaseEvent() even if they happen outside of its area, or until the capture is released in a pointerMoveEvent() again. Calling PointerMoveEvent::setCaptured() in the leave event has no effect in this case.

After the above, if the event was accepted on any node or wasn't but is still captured on any node, it falls through upwards the parent hierarchy, calling AbstractLayer::pointerMoveEvent() on all data attached to nodes that have NodeFlag::FallthroughPointerEvents set, with PointerMoveEvent::isFallthrough() being true and the isNodePressed(), isNodeHovered(), isNodeFocused() and isCaptured() properties inherited as described in Pointer event fallthrough. If any data accept the event, AbstractLayer::pointerCancelEvent() is called on all data attached to any previously captured node, and if the event is primary, also any previously pressed, hovered or focused nodes. The captured state is then transferred to the new node, and for a primary event the pressed and hovered state is transferred as well. However, currently, no AbstractLayer::pointerEnterEvent() is subsequently sent to the new node. Focus is lost for a primary event unless the node that accepted the fallthrough event is itself focused, in which case it stays unchanged. This fallthrough then repeats until a root node is reached, and each time an event is accepted on a new node it's cancelled on the previous as appropriate.

Returns true if the event was accepted by at least one data, false if it wasn't or there wasn't any visible event handling node at given position and thus the event should be propagated further; accept status of the enter and leave events is ignored.

Expects that the event is not accepted yet.

template<class Event, class ... Args, class = decltype(Implementation::PointerMoveEventConverter<Event>::move(std::declval<AbstractUserInterface&>(), std::declval<Event&>(), std::declval<Args>()...))>
bool Magnum::Ui::AbstractUserInterface::pointerMoveEvent(Event& event, Args && ... args)

Handle an external pointer move event.

Converts the event to a PointerMoveEvent and delegates to pointerMoveEvent(const Vector2&, PointerMoveEvent&), see its documentation and Integration with application libraries for more information. The args allow passing optional extra arguments to a particular event converter.

bool Magnum::Ui::AbstractUserInterface::focusEvent(NodeHandle node, FocusEvent& event)

Handle a focus event.

Implicitly calls update(), which in turn implicitly calls clean(). The node is expected to be either NodeHandle::Null or valid with NodeFlag::Focusable set.

If node is non-null and is or any of its parents are not visible either due to NodeFlag::Hidden set or due to the node hierarchy not being in the top-level node order, or have NodeFlag::Disabled or NodeFlag::NoEvents set, the function is a no-op and returns false. Note that this does not apply to nodes that are clipped or otherwise out of view.

If the node is visible and can be focused, calls AbstractLayer::focusEvent() on all attached data, regardless of the accept status, and even if the current focused node is the same as node. Then, if any of them accept the event, the node is set as currently focused, and AbstractLayer::blurEvent() is called on the previously focused node if different from node. If none of them accept the event and the previously focused node is different from node, the previously focused node stays. If none of them accept the event and the previously focused node is the same as node, AbstractLayer::blurEvent() is called on node and the current focused node is set to NodeHandle::Null.

If node is NodeHandle::Null and current focused node is non-null, calls AbstractLayer::blurEvent() on it and sets the current focused node to NodeHandle::Null.

Returns true if the AbstractLayer::focusEvent() was called at all and accepted by at least one data, false otherwise.

Compared to pointerPressEvent(), where pressing on a non-focusable node blurs the previously focused node, this function preserves the previously focused node if node is cannot be focused for any of the above reasons. Given this function is meant to be called by the application itself, this gives it more control — for example to try to focus the next active control, if there's any, and stay on the previous one if not. To achieve the same behavior as pointerPressEvent(), call the function with NodeHandle::Null if a call with a non-null handle fails:

if(!ui.focusEvent(node, event))
    ui.focusEvent(Ui::NodeHandle::Null, event);

bool Magnum::Ui::AbstractUserInterface::keyPressEvent(KeyEvent& event)

Handle a key press event.

Implicitly calls update(), which in turn implicitly calls clean().

If currentFocusedNode() is not NodeHandle::Null, calls AbstractLayer::keyPressEvent() on all data attached to it belonging to layers that support LayerFeature::Event.

Otherwise, if currentGlobalPointerPosition() is not Containers::NullOpt, propagates the event to nodes under it and calls AbstractLayer::keyPressEvent() on all data attached to it belonging to layers that support LayerFeature::Event, in a front-to-back order as described in Node hierarchy event propagation. For each such node, the event is always called on all attached data, regardless of the accept status. For each call the event contains the currentGlobalPointerPosition() made relative to the node to which given data is attached.

Returns true if the event was accepted by at least one data; false if it wasn't, if currentFocusedNode() is NodeHandle::Null and there either wasn't any visible event handling node at given position or currentGlobalPointerPosition() is Containers::NullOpt, and thus the event should be propagated further.

Expects that the event is not accepted yet.

template<class Event, class ... Args, class = decltype(Implementation::KeyEventConverter<Event>::press(std::declval<AbstractUserInterface&>(), std::declval<Event&>(), std::declval<Args>()...))>
bool Magnum::Ui::AbstractUserInterface::keyPressEvent(Event& event, Args && ... args)

Handle an external key press event.

Converts the event to a KeyEvent and delegates to keyPressEvent(KeyEvent&), see its documentation and Integration with application libraries for more information. The args allow passing optional extra arguments to a particular event converter.

bool Magnum::Ui::AbstractUserInterface::keyReleaseEvent(KeyEvent& event)

Handle a key release event.

Implicitly calls update(), which in turn implicitly calls clean().

If currentFocusedNode() is not NodeHandle::Null, calls AbstractLayer::keyReleaseEvent() on all data attached to it belonging to layers that support LayerFeature::Event.

Otherwise, if currentGlobalPointerPosition() is not Containers::NullOpt, finds the front-most node under it and calls AbstractLayer::keyReleaseEvent() on all data attached to it belonging to layers that support LayerFeature::Event. If no data accept the event, continues to other nodes under the position in a front-to-back order and then to parent nodes. For each such node, the event is always called on all attached data, regardless of the accept status. For each call the event contains the currentGlobalPointerPosition() made relative to the node to which given data is attached.

Returns true if the event was accepted by at least one data; false if it wasn't, if currentFocusedNode() is NodeHandle::Null and there either wasn't any visible event handling node at given position or currentGlobalPointerPosition() is Containers::NullOpt, and thus the event should be propagated further.

Expects that the event is not accepted yet.

template<class Event, class ... Args, class = decltype(Implementation::KeyEventConverter<Event>::release(std::declval<AbstractUserInterface&>(), std::declval<Event&>(), std::declval<Args>()...))>
bool Magnum::Ui::AbstractUserInterface::keyReleaseEvent(Event& event, Args && ... args)

Handle an external key release event.

Converts the event to a KeyEvent and delegates to keyReleaseEvent(KeyEvent&), see its documentation and Integration with application libraries for more information. The args allow passing optional extra arguments to a particular event converter.

bool Magnum::Ui::AbstractUserInterface::textInputEvent(TextInputEvent& event)

Handle a text input event.

Implicitly calls update(), which in turn implicitly calls clean().

If currentFocusedNode() is not NodeHandle::Null, calls AbstractLayer::textInputEvent() on all data attached to it belonging to layers that support LayerFeature::Event.

Returns true if the event was accepted by at least one data; false if it wasn't or if currentFocusedNode() is NodeHandle::Null, and thus the event should be propagated further.

Expects that the event is not accepted yet.

template<class Event, class ... Args, class = decltype(Implementation::TextInputEventConverter<Event>::trigger(std::declval<AbstractUserInterface&>(), std::declval<Event&>(), std::declval<Args>()...))>
bool Magnum::Ui::AbstractUserInterface::textInputEvent(Event& event, Args && ... args)

Handle an external text input event.

Converts the event to a TextInputEvent and delegates to textInputEvent(TextInputEvent&), see its documentation and Integration with application libraries for more information. The args allow passing optional extra arguments to a particular event converter.

NodeHandle Magnum::Ui::AbstractUserInterface::currentPressedNode() const

Node pressed by last pointer event.

Returns handle of a node that was under the pointer for the last pointerPressEvent(), the pointer wasn't released since and the pointer is either captured on that node or didn't leave its area since.

If no pointer press event was called yet, if the event wasn't accepted by any data, if pointerReleaseEvent() was called since or the pointer was uncaptured and left the node area, returns NodeHandle::Null. It also becomes NodeHandle::Null if the node or any of its parents were removed, hidden or have NodeFlag::NoEvents or NodeFlag::Disabled set and update() was called since.

The returned handle may be invalid if the node or any of its parents were removed and clean() wasn't called since.

NodeHandle Magnum::Ui::AbstractUserInterface::currentCapturedNode() const

Node captured by last pointer event.

Returns handle of a node that captured the last pointerPressEvent() or pointerMoveEvent(). All data attached to the captured node then receive all following pointer events until and including a pointerReleaseEvent() even if they happen outside of its area, or until the capture is released in a pointerMoveEvent() again.

If no pointer press event was called yet, if the event wasn't accepted by any data, if the capture was disabled or subsequently released with PointerEvent::setCaptured() or if a pointerReleaseEvent() was called since, returns NodeHandle::Null. It also becomes NodeHandle::Null if the node or any of its parents were removed, hidden or have NodeFlag::NoEvents or NodeFlag::Disabled set and update() was called since.

The returned handle may be invalid if the node or any of its parents were removed and clean() wasn't called since.

NodeHandle Magnum::Ui::AbstractUserInterface::currentHoveredNode() const

Node hovered by last pointer event.

Returns handle of a node that was under the pointer for the last pointerMoveEvent(). All data attached to such node already received a AbstractLayer::pointerEnterEvent(). Once a pointerMoveEvent() leaves its area, all data attached to that node will receive a AbstractLayer::pointerLeaveEvent(), and this either becomes NodeHandle::Null, or another node becomes hovered, receiving an enter event. It's also NodeHandle::Null if no pointer move event was called yet or if the node or any of its parents were removed, hidden or have NodeFlag::NoEvents or NodeFlag::Disabled set and update() was called since.

The returned handle may be invalid if the node or any of its parents were removed and clean() wasn't called since.

NodeHandle Magnum::Ui::AbstractUserInterface::currentFocusedNode() const

Node focused by last pointer or focus event.

Returns handle of a NodeFlag::Focusable node that was under the pointer for the last pointerPressEvent() or for which focusEvent() was called and at least one data attached to such node accepted the AbstractLayer::focusEvent(). Once the node becomes hidden (either due to NodeFlag::Hidden set or due to the node hierarchy not being in the top-level node order), NodeFlag::NoEvents or NodeFlag::Disabled is set on it or it loses NodeFlag::Focusable, the data receive AbstractLayer::blurEvent() and this becomes NodeHandle::Null. It's also NodeHandle::Null if no node was focused yet or if the node or any of its parents were removed or hidden and update() was called since.

The returned handle may be invalid if the node or any of its parents were removed and clean() wasn't called since.

Containers::Optional<Vector2> Magnum::Ui::AbstractUserInterface::currentGlobalPointerPosition() const

Position of last pointer event.

Returns a position passed to the last primary pointerPressEvent(), pointerReleaseEvent() or pointerMoveEvent(), scaled to match size() instead of windowSize(). If no primary pointer event happened yet, returns Containers::NullOpt.

AbstractRenderer& Magnum::Ui::AbstractUserInterface::setRendererInstance(Containers::Pointer<AbstractRenderer>&& instance)

Set renderer instance.

Expects that the instance hasn't been set yet. A renderer instance has to be set in order to draw anything, it's the user responsibility to ensure that the GPU API used by the renderer matches the GPU API used by all layer instances, such as RendererGL being used for BaseLayerGL and TextLayerGL. The instance is subsequently available through renderer().

If framebuffer size was set with setSize() already, calling this function causes AbstractRenderer::setupFramebuffers() to be called. Otherwise the setup gets performed in the next setSize() call.

template<class T>
T& Magnum::Ui::AbstractUserInterface::setRendererInstance(Containers::Pointer<T>&& instance)

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

bool Magnum::Ui::AbstractUserInterface::hasRenderer() const

Whether a renderer instance has been set.

AbstractRenderer& Magnum::Ui::AbstractUserInterface::renderer()

Renderer instance.

Expects that setRendererInstance() was called.

const AbstractRenderer& Magnum::Ui::AbstractUserInterface::renderer() const

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

template<class T>
T& Magnum::Ui::AbstractUserInterface::renderer()

Renderer instance in a concrete type.

Expected that setRendererInstance() was called. It's the user responsibility to ensure that T matches the actual instance type.

template<class T>
const T& Magnum::Ui::AbstractUserInterface::renderer() const

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

std::size_t Magnum::Ui::AbstractUserInterface::layerCapacity() const

Capacity of the layer storage.

Can be at most 256. If createLayer() is called and there's no free slots left, the internal storage gets grown.

std::size_t Magnum::Ui::AbstractUserInterface::layerUsedCount() const

Count of used items in the layer storage.

Always at most layerCapacity(). Expired handles are counted among used as well. The operation is done with a $ \mathcal{O}(n) $ complexity where $ n $ is layerCapacity().

bool Magnum::Ui::AbstractUserInterface::isHandleValid(LayerHandle handle) const

Whether a layer handle is valid.

A handle is valid if it has been returned from createLayer() before and removeLayer() wasn't called on it yet. Note that a handle is valid even if the layer instance wasn't set with setLayerInstance() yet. For LayerHandle::Null always returns false.

bool Magnum::Ui::AbstractUserInterface::isHandleValid(DataHandle handle) const

Whether a data handle is valid.

A shorthand for extracting a LayerHandle from handle using dataHandleLayer(), calling isHandleValid(LayerHandle) const on it, if it's valid and set then retrieving the particular layer instance using layer() and then calling AbstractLayer::isHandleValid(LayerDataHandle) const with a LayerDataHandle extracted from handle using dataHandleData(). See these functions for more information. For DataHandle::Null, LayerHandle::Null or LayerDataHandle::Null always returns false.

LayerHandle Magnum::Ui::AbstractUserInterface::layerFirst() const

First layer in draw and event processing order.

The first layer gets drawn first (thus is at the back) and reacts to events after all others. Returns LayerHandle::Null if there's no layers yet. The returned handle is always either valid or null.

LayerHandle Magnum::Ui::AbstractUserInterface::layerLast() const

Last layer in draw and event processing order.

The last layer gets drawn last (thus is at the front) and reacts to event before all others. Returns LayerHandle::Null if there's no layers yet. The returned handle is always either valid or null.

LayerHandle Magnum::Ui::AbstractUserInterface::layerPrevious(LayerHandle handle) const

Previous layer in draw and event processing order.

The previous layer gets drawn earlier (thus is behind) and reacts to events later. Expects that handle is valid. Returns LayerHandle::Null if the layer is first. The returned handle is always either valid or null.

LayerHandle Magnum::Ui::AbstractUserInterface::layerNext(LayerHandle handle) const

Next layer in draw and event processing order.

The next layer gets drawn later (thus is in front) and reacts to events earlier. Expects that handle is valid. Returns LayerHandle::Null if the layer is last. The returned handle is always either valid or null.

LayerHandle Magnum::Ui::AbstractUserInterface::createLayer(LayerHandle before = LayerHandle::Null)

Create a layer.

Parameters
before A layer to order before for draw and event processing or LayerHandle::Null if ordered as last (i.e., at the front, being drawn last and receiving events first). Expected to be valid if not null.
Returns New layer handle

Allocates a new handle in a free slot in the internal storage or grows the storage if there's no free slots left. Expects that there's at most 256 layers. The returned handle is meant to be used to construct an AbstractLayer subclass and the instance then passed to setLayerInstance(). A layer can be removed again with removeLayer().

AbstractLayer& Magnum::Ui::AbstractUserInterface::setLayerInstance(Containers::Pointer<AbstractLayer>&& instance)

Set a layer instance.

Returns Reference to instance

Expects that instance was created with a LayerHandle returned from createLayer() earlier, the handle is valid and setLayerInstance() wasn't called for the same handle yet.

Calls AbstractLayer::setSize() on the layer, unless neither setSize() nor AbstractUserInterface(const Vector2&, const Vector2&, const Vector2i&) was called yet.

template<class T>
T& Magnum::Ui::AbstractUserInterface::setLayerInstance(Containers::Pointer<T>&& instance)

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

AbstractLayer& Magnum::Ui::AbstractUserInterface::layer(LayerHandle handle)

Layer instance.

Expects that handle is valid and that setLayerInstance() was called for it.

const AbstractLayer& Magnum::Ui::AbstractUserInterface::layer(LayerHandle handle) const

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

template<class T>
T& Magnum::Ui::AbstractUserInterface::layer(LayerHandle handle)

Layer instance in a concrete type.

Expects that handle is valid and that setLayerInstance() was called for it. It's the user responsibility to ensure that T matches the actual instance type.

template<class T>
const T& Magnum::Ui::AbstractUserInterface::layer(LayerHandle handle) const

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

void Magnum::Ui::AbstractUserInterface::removeLayer(LayerHandle handle)

Remove a layer.

Expects that handle is valid. After this call, isHandleValid(LayerHandle) const returns false for handle and isHandleValid(DataHandle) const returns false for all data associated with handle.

Animators with AnimatorFeature::DataAttachment that were associated with this layers are kept, but are excluded from any further processing in clean() or advanceAnimations(). Calling removeAnimator() on these is left to the user.

Calling this function causes UserInterfaceState::NeedsDataAttachmentUpdate to be set.

void Magnum::Ui::AbstractUserInterface::attachData(NodeHandle node, DataHandle data)

Attach data to a node.

A shorthand for extracting a LayerHandle from data using dataHandleLayer(), retrieving the particular layer instance using layer() and then calling AbstractLayer::attach(LayerDataHandle, NodeHandle) with a LayerDataHandle extracted with dataHandleData(). See these functions for more information. In addition to AbstractLayer::attach(LayerDataHandle, NodeHandle), this function checks that node is either valid or NodeHandle::Null.

Calling this function transitively causes UserInterfaceState::NeedsDataAttachmentUpdate to be set, which is a consequence of LayerState::NeedsAttachmentUpdate being set by AbstractLayer::attach().

std::size_t Magnum::Ui::AbstractUserInterface::layouterCapacity() const

Capacity of the layouter storage.

Can be at most 256. If createLayouter() is called and there's no free slots left, the internal storage gets grown.

std::size_t Magnum::Ui::AbstractUserInterface::layouterUsedCount() const

Count of used items in the layouter storage.

Always at most layouterCapacity(). Expired handles are counted among used as well. The operation is done with a $ \mathcal{O}(n) $ complexity where $ n $ is layouterCapacity().

bool Magnum::Ui::AbstractUserInterface::isHandleValid(LayouterHandle handle) const

Whether a layouter handle is valid.

A handle is valid if it has been returned from createLayouter() before and removeLayouter() wasn't called on it yet. Note that a handle is valid even if the layouter instance wasn't set with setLayouterInstance() yet. For LayouterHandle::Null always returns false.

bool Magnum::Ui::AbstractUserInterface::isHandleValid(LayoutHandle handle) const

Whether a layout handle is valid.

A shorthand for extracting a LayouterHandle from handle using layoutHandleLayouter(), calling isHandleValid(LayouterHandle) const on it, if it's valid and set then retrieving the particular layouter instance using layouter() and then calling AbstractLayouter::isHandleValid(LayouterDataHandle) const with a LayouterDataHandle extracted from handle using layoutHandleData(). See these functions for more information. For LayoutHandle::Null, LayouterHandle::Null or LayouterDataHandle::Null always returns false.

LayouterHandle Magnum::Ui::AbstractUserInterface::layouterFirst() const

First layouter in the layout calculation order.

This layouter gets executed before all others. Returns LayouterHandle::Null if there's no layouters. The returned handle is always either valid or null.

LayouterHandle Magnum::Ui::AbstractUserInterface::layouterLast() const

Last layouter in the layout calculation order.

This layouter gets executed after all others. Returns LayouterHandle::Null if there's no layouters. The returned handle is always either valid or null.

LayouterHandle Magnum::Ui::AbstractUserInterface::layouterPrevious(LayouterHandle handle) const

Previous layouter in the layout calculation order.

The previous layouter gets executed earlier. Expects that handle is valid. Returns LayouterHandle::Null if the layouter is first. The returned handle is always either valid or null.

LayouterHandle Magnum::Ui::AbstractUserInterface::layouterNext(LayouterHandle handle) const

Next layouter in the layout calculation order.

The next layouter gets executed later. Expects that handle is valid. Returns LayouterHandle::Null if the layouter is last. The returned handle is always either valid or null.

LayouterHandle Magnum::Ui::AbstractUserInterface::createLayouter(LayouterHandle before = LayouterHandle::Null)

Create a layouter.

Parameters
before A layouter to order before for layout calculation or LayouterHandle::Null if ordered as last. Expected to be valid if not null.
Returns New layouter handle

Allocates a new handle in a free slot in the internal storage or grows the storage if there's no free slots left. Expects that there's at most 256 layouters. The returned handle is meant to be used to construct an AbstractLayouter subclass and the instance then passed to setLayouterInstance(). A layouter can be removed again with removeLayer().

AbstractLayouter& Magnum::Ui::AbstractUserInterface::setLayouterInstance(Containers::Pointer<AbstractLayouter>&& instance)

Set a layouter instance.

Returns Reference to instance

Expects that instance was created with a LayouterHandle returned from createLayouter() earlier, the handle is valid and setLayouterInstance() wasn't called for the same handle yet.

Calls AbstractLayouter::setSize() on the layouter, unless neither setSize() nor AbstractUserInterface(const Vector2&, const Vector2&, const Vector2i&) was called yet.

template<class T>
T& Magnum::Ui::AbstractUserInterface::setLayouterInstance(Containers::Pointer<T>&& instance)

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

AbstractLayouter& Magnum::Ui::AbstractUserInterface::layouter(LayouterHandle handle)

Layouter instance.

Expects that handle is valid and that setLayouterInstance() was called for it.

const AbstractLayouter& Magnum::Ui::AbstractUserInterface::layouter(LayouterHandle handle) const

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

template<class T>
T& Magnum::Ui::AbstractUserInterface::layouter(LayouterHandle handle)

Layouter instance in a concrete type.

Expects that handle is valid and that setLayouterInstance() was called for it. It's the user responsibility to ensure that T matches the actual instance type.

template<class T>
const T& Magnum::Ui::AbstractUserInterface::layouter(LayouterHandle handle) const

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

void Magnum::Ui::AbstractUserInterface::removeLayouter(LayouterHandle handle)

Remove a layouter.

Expects that handle is valid. After this call, isHandleValid(LayouterHandle) const returns false for handle and isHandleValid(LayoutHandle) const returns false for all layouts associated with handle.

Calling this function causes UserInterfaceState::NeedsLayoutAssignmentUpdate to be set.

std::size_t Magnum::Ui::AbstractUserInterface::animatorCapacity() const

Capacity of the animator storage.

Can be at most 256. If createAnimator() is called and there's no free slots left, the internal storage gets grown.

std::size_t Magnum::Ui::AbstractUserInterface::animatorUsedCount() const

Count of used items in the animator storage.

Always at most animatorCapacity(). Expired handles are counted among used as well. The operation is done with a $ \mathcal{O}(n) $ complexity where $ n $ is animatorCapacity().

bool Magnum::Ui::AbstractUserInterface::isHandleValid(AnimatorHandle handle) const

Whether an animator handle is valid.

A handle is valid if it has been returned from createAnimator() before and removeAnimator() wasn't called on it yet. Note that a handle is valid even if the animator instance wasn't set with setGenericAnimatorInstance(), setNodeAnimatorInstance(), setDataAnimatorInstance() or setStyleAnimatorInstance() yet. For AnimatorHandle::Null always returns false.

bool Magnum::Ui::AbstractUserInterface::isHandleValid(AnimationHandle handle) const

Whether an animation handle is valid.

A shorthand for extracting an AnimatorHandle from handle using animationHandleAnimator(), calling isHandleValid(AnimatorHandle) const on it, if it's valid and set then retrieving the particular animator instance using animator() and then calling AbstractAnimator::isHandleValid(AnimatorDataHandle) const with an AnimatorDataHandle extracted from handle using animationHandleData(). See these functions for more information. For AnimationHandle::Null, AnimatorHandle::Null or AnimatorDataHandle::Null always returns false.

AnimatorHandle Magnum::Ui::AbstractUserInterface::createAnimator()

Create an animator.

Returns New animator handle

Allocates a new handle in a free slot in the internal storage or grows the storage if there's no free slots left. Expects that there's at most 256 animators. The returned handle is meant to be used to construct an AbstractAnimator subclass and the instance then passed to setGenericAnimatorInstance(), setNodeAnimatorInstance(), setDataAnimatorInstance() or setStyleAnimatorInstance(). An animator can be removed again with removeAnimator().

AbstractGenericAnimator& Magnum::Ui::AbstractUserInterface::setGenericAnimatorInstance(Containers::Pointer<AbstractGenericAnimator>&& instance)

Set a generic animator instance.

Returns Reference to instance

Expects that instance was created with an AnimatorHandle returned from createAnimator() earlier, the handle is valid and none of setGenericAnimatorInstance(), setNodeAnimatorInstance(), setDataAnimatorInstance() or setStyleAnimatorInstance() was called for the same handle yet. Additionally, if AnimatorFeature::DataAttachment is supported by instance, expects that AbstractGenericAnimator::setLayer() has already been called on it.

Internally, the instance is inserted into a list partitioned by animator type, which is done with a $ \mathcal{O}(n) $ complexity where $ n $ is animatorCapacity().

template<class T>
T& Magnum::Ui::AbstractUserInterface::setGenericAnimatorInstance(Containers::Pointer<T>&& instance)

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

AbstractNodeAnimator& Magnum::Ui::AbstractUserInterface::setNodeAnimatorInstance(Containers::Pointer<AbstractNodeAnimator>&& instance)

Set a node animator instance.

Returns Reference to instance

Expects that instance was created with an AnimatorHandle returned from createAnimator() earlier, the handle is valid and none of setGenericAnimatorInstance(), setNodeAnimatorInstance(), setDataAnimatorInstance() or setStyleAnimatorInstance() was called for the same handle yet. The AbstractNodeAnimator is expected to advertise AnimatorFeature::NodeAttachment.

Internally, the instance is inserted into a list partitioned by animator type, which is done with a $ \mathcal{O}(n) $ complexity where $ n $ is animatorCapacity().

template<class T>
T& Magnum::Ui::AbstractUserInterface::setNodeAnimatorInstance(Containers::Pointer<T>&& instance)

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

AbstractDataAnimator& Magnum::Ui::AbstractUserInterface::setDataAnimatorInstance(Containers::Pointer<AbstractDataAnimator>&& instance)

Set a data animator instance.

Returns Reference to instance

Expects that instance was created with an AnimatorHandle returned from createAnimator() earlier, the handle is valid and none of setGenericAnimatorInstance(), setNodeAnimatorInstance(), setDataAnimatorInstance() or setStyleAnimatorInstance() was called for the same handle yet. The AbstractDataAnimator is expected to advertise AnimatorFeature::DataAttachment and it's expected that AbstractLayer::assignAnimator(AbstractDataAnimator&) const has already been called for it.

Internally, the instance is inserted into a list partitioned by animator type, which is done with a $ \mathcal{O}(n) $ complexity where $ n $ is animatorCapacity().

template<class T>
T& Magnum::Ui::AbstractUserInterface::setDataAnimatorInstance(Containers::Pointer<T>&& instance)

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

AbstractStyleAnimator& Magnum::Ui::AbstractUserInterface::setStyleAnimatorInstance(Containers::Pointer<AbstractStyleAnimator>&& instance)

Set a style animator instance.

Returns Reference to instance

Expects that instance was created with an AnimatorHandle returned from createAnimator() earlier, the handle is valid and none of setGenericAnimatorInstance(), setNodeAnimatorInstance(), setDataAnimatorInstance() or setStyleAnimatorInstance() was called for the same handle yet. The AbstractStyleAnimator is expected to advertise AnimatorFeature::DataAttachment and it's expected that AbstractLayer::assignAnimator(AbstractStyleAnimator&) const has already been called for it.

Internally, the instance is inserted into a list partitioned by animator type, which is done with a $ \mathcal{O}(n) $ complexity where $ n $ is animatorCapacity().

template<class T>
T& Magnum::Ui::AbstractUserInterface::setStyleAnimatorInstance(Containers::Pointer<T>&& instance)

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

AbstractAnimator& Magnum::Ui::AbstractUserInterface::animator(AnimatorHandle handle)

Animator instance.

Expects that handle is valid and that one of setGenericAnimatorInstance(), setNodeAnimatorInstance(), setDataAnimatorInstance() or setStyleAnimatorInstance() was called for it.

const AbstractAnimator& Magnum::Ui::AbstractUserInterface::animator(AnimatorHandle handle) const

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

template<class T>
T& Magnum::Ui::AbstractUserInterface::animator(AnimatorHandle handle)

Animator instance in a concrete type.

Expects that handle is valid and that one of setGenericAnimatorInstance(), setNodeAnimatorInstance(), setDataAnimatorInstance() or setStyleAnimatorInstance() was called for it. It's the user responsibility to ensure that T matches the actual instance type.

template<class T>
const T& Magnum::Ui::AbstractUserInterface::animator(AnimatorHandle handle) const

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

void Magnum::Ui::AbstractUserInterface::removeAnimator(AnimatorHandle handle)

Remove an animator.

Expects that handle is valid. After this call, isHandleValid(AnimatorHandle) const returns false for handle and isHandleValid(AnimationHandle) const returns false for all animations associated with handle.

Internally, if the removed animator had an instance set, the instance is removed from a list partitioned by animator type, which is done with a $ \mathcal{O}(n) $ complexity where $ n $ is animatorCapacity().

void Magnum::Ui::AbstractUserInterface::attachAnimation(NodeHandle node, AnimationHandle animation)

Attach an animation to a node.

A shorthand for extracting a AnimatorHandle from animation using animationHandleAnimator(), retrieving the particular animator instance using animator() and then calling AbstractAnimator::attach(AnimatorDataHandle, NodeHandle) with a AnimatorDataHandle extracted with animationHandleData(). See these functions for more information. In addition to AbstractAnimator::attach(AnimatorDataHandle, NodeHandle), this function checks that node is either valid or NodeHandle::Null.

void Magnum::Ui::AbstractUserInterface::attachAnimation(DataHandle data, AnimationHandle animation)

Attach an animation to a data.

A shorthand for extracting a AnimatorHandle from animation using animationHandleAnimator(), retrieving the particular animator instance using animator() and then calling AbstractAnimator::attach(AnimatorDataHandle, DataHandle) with a AnimatorDataHandle extracted with animationHandleData(). See these functions for more information. In addition to AbstractAnimator::attach(AnimatorDataHandle, DataHandle), this function checks that data is either valid with the layer portion matching AbstractAnimator::layer() of given animator or DataHandle::Null.

Note that unlike AbstractAnimator::attach(AnimationHandle, LayerDataHandle), here's no convenience function that would take a LayerDataHandle as it wouldn't be able to provide any extra checks over calling the AbstractAnimator API directly.

std::size_t Magnum::Ui::AbstractUserInterface::nodeCapacity() const

Current capacity of the node storage.

Can be at most 1048576. If createNode() is called and there's no free slots left, the internal storage gets grown.

std::size_t Magnum::Ui::AbstractUserInterface::nodeUsedCount() const

Count of used items in the node storage.

Always at most nodeCapacity(). Expired handles are counted among used as well. The operation is done with a $ \mathcal{O}(n) $ complexity where $ n $ is nodeCapacity().

bool Magnum::Ui::AbstractUserInterface::isHandleValid(NodeHandle handle) const

Whether a node handle is valid.

A handle is valid if it has been returned from createNode() before, removeNode() wasn't called on it yet and it wasn't removed inside update() due to a parent node being removed earlier. For NodeHandle::Null always returns false.

NodeHandle Magnum::Ui::AbstractUserInterface::createNode(NodeHandle parent, const Vector2& offset, const Vector2& size, NodeFlags flags = {})

Create a node.

Parameters
parent Parent node to attach to or NodeHandle::Null for a new root node. Expected to be valid if not null.
offset Offset relative to the parent node
size Size of the node contents. Used for layouting, clipping and event handling.
flags Initial node flags
Returns New node handle

Allocates a new handle in a free slot in the internal storage or grows the storage if there's no free slots left. Expects that there's at most 1048576 nodes. The returned handle can be then removed again with removeNode().

If parent is NodeHandle::Null, the node is added at the back of the draw and event processing list, i.e. drawn last (thus at the front) and reacting to events before all others. Use setNodeOrder() and clearNodeOrder() to adjust the draw and event processing order or remove it from the list of visible top-level nodes.

Calling this function causes UserInterfaceState::NeedsNodeUpdate to be set.

NodeHandle Magnum::Ui::AbstractUserInterface::createNode(const Vector2& offset, const Vector2& size, NodeFlags flags = {})

Create a root node.

Equivalent to calling createNode(NodeHandle, const Vector2&, const Vector2&, NodeFlags) with NodeHandle::Null as the parent.

NodeHandle Magnum::Ui::AbstractUserInterface::nodeParent(NodeHandle handle) const

Node parent.

Expects that handle is valid. Returns NodeHandle::Null if it's a root node. Note that the returned handle may be invalid if removeNode() was called on one of the parent nodes and update() hasn't been called since.

Unlike other node properties, the parent cannot be changed after creation.

Vector2 Magnum::Ui::AbstractUserInterface::nodeOffset(NodeHandle handle) const

Node offset relative to its parent.

The returned value is before any layout calculation is done. Absolute offset after all layout calculation is passed to AbstractLayer::doUpdate() and AbstractLayer::doDraw().

Expects that handle is valid.

void Magnum::Ui::AbstractUserInterface::setNodeOffset(NodeHandle handle, const Vector2& offset)

Set node offset relative to its parent.

If the node has a layout assigned, the value is subsequently used for layout calculation and it's up to the particular layouter implementation how the initial offset value is used. If the node doesn't have a layout assigned, the offset is used as-is. Expects that handle is valid. Initially, a node has the offset that was passed to createNode().

Calling this function causes UserInterfaceState::NeedsLayoutUpdate to be set.

Vector2 Magnum::Ui::AbstractUserInterface::nodeSize(NodeHandle handle) const

Node size.

The returned value is before any layout calculation is done. Size after all layout calculations is available through PointerEvent::nodeSize(), PointerMoveEvent::nodeSize(), KeyEvent::nodeSize() and is passed to AbstractLayer::doUpdate() and AbstractLayer::doDraw().

Expects that handle is valid.

void Magnum::Ui::AbstractUserInterface::setNodeSize(NodeHandle handle, const Vector2& size)

Set node size.

If the node has a layout assigned, the value is subsequently used for layout calculation and it's up to the particular layouter implementation how the initial size value is used. If the node doesn't have a layout assigned, the size is used as-is. Expects that handle is valid. Initially, a node has the size that was passed to createNode().

Calling this function causes UserInterfaceState::NeedsLayoutUpdate to be set.

Float Magnum::Ui::AbstractUserInterface::nodeOpacity(NodeHandle handle) const

Node opacity.

The returned value is only the opacity set on the node itself, without considering opacity inherited from parents. Expects that handle is valid.

void Magnum::Ui::AbstractUserInterface::setNodeOpacity(NodeHandle handle, Float opacity)

Set node opacity.

The value is subsequently multiplied with opacity of all parents and passed to layers to affect rendering. Opacity of 1.0f makes a node opaque and 0.0f fully transparent, although values outside of this range are allowed as well. Note that it's up to the particular layer implementation how the opacity value is actually used, and if at all — the layer may for example have additional options that affect the opacity either way. Expects that handle is valid. Initially, a node has the opacity set to 1.0f.

Calling this function causes UserInterfaceState::NeedsNodeOpacityUpdate to be set.

NodeFlags Magnum::Ui::AbstractUserInterface::nodeFlags(NodeHandle handle) const

Node flags.

The returned value is only flags set directly on the node itself, without considering flags such as NodeFlag::Disabled that would be inherited from parents. Expects that handle is valid.

void Magnum::Ui::AbstractUserInterface::setNodeFlags(NodeHandle handle, NodeFlags flags)

Set node flags.

Expects that handle is valid. Initially, a node has the flags that were passed to createNode(), which are by default none.

If NodeFlag::Hidden was added or cleared by calling this function, it causes UserInterfaceState::NeedsNodeUpdate to be set. If NodeFlag::Clip was added or cleared by calling this function, it causes UserInterfaceState::NeedsNodeClipUpdate to be set. If NodeFlag::NoEvents, NodeFlag::Disabled or NodeFlag::Focusable was added or cleared by calling this function, it causes UserInterfaceState::NeedsNodeEnabledUpdate to be set.

void Magnum::Ui::AbstractUserInterface::addNodeFlags(NodeHandle handle, NodeFlags flags)

Add node flags.

Calls setNodeFlags() with the existing flags ORed with flags. Useful for preserving previously set flags.

void Magnum::Ui::AbstractUserInterface::clearNodeFlags(NodeHandle handle, NodeFlags flags)

Clear node flags.

Calls setNodeFlags() with the existing flags ANDed with the inverse of flags. Useful for removing a subset of previously set flags.

void Magnum::Ui::AbstractUserInterface::removeNode(NodeHandle handle)

Remove a node.

Expects that handle is valid. Nested nodes and data attached to any of the nodes are then removed during the next call to update(). After this call, isHandleValid(NodeHandle) const returns false for handle.

If handle is a top-level node, the operation is done with an $ \mathcal{O}(n) $ complexity, where $ n $ is the count of nested top-level hierarchies.

Calling this function causes UserInterfaceState::NeedsNodeClean to be set.

std::size_t Magnum::Ui::AbstractUserInterface::nodeOrderCapacity() const

Capacity of the top-level node order storage.

nodeOrderUsedCount()

std::size_t Magnum::Ui::AbstractUserInterface::nodeOrderUsedCount() const

Count of used items in the top-level node order storage.

Always at most nodeOrderCapacity(). The operation is done with a $ \mathcal{O}(n) $ complexity where $ n $ is nodeOrderCapacity(). When a root node is created or setNodeOrder() is called for the first time on a non-root node, a slot in the node order storage is used for it, and gets recycled only when the node is removed again or when flattenNodeOrder() is called on a non-root node.

NodeHandle Magnum::Ui::AbstractUserInterface::nodeOrderFirst() const

First top-level node in draw and event processing order.

The first node gets drawn first (thus is at the back) and reacts to events after all others. Returns NodeHandle::Null if there's no nodes included in the draw and event processing order. The returned handle is always either valid or null.

NodeHandle Magnum::Ui::AbstractUserInterface::nodeOrderLast() const

Last top-level node in draw and event processing order.

The last node gets drawn last (thus is at the front) and reacts to events before all others. Returns NodeHandle::Null if there's no nodes included in the draw and event processing order. The returned handle is always either valid or null.

bool Magnum::Ui::AbstractUserInterface::isNodeTopLevel(NodeHandle handle) const

Whether a node is top-level for draw and event processing.

If not top-level, it's in the usual order defined by the node hierarchy. If top-level, it's ordered respective to other top-level nodes. Expects that handle is valid.

Always returns true for root nodes. If isNodeOrdered() returns true, it implies the node is top-level. If this function returns true, it doesn't necessarily mean the node is visible — the node or any of its parents could be excluded from the order or it could be hidden.

bool Magnum::Ui::AbstractUserInterface::isNodeOrdered(NodeHandle handle) const

Whether a node is top-level and is included in a draw and event processing order.

If not included, the node and all its children are not drawn and don't react to events. Expects that handle is valid.

If this function returns true, isNodeTopLevel() is true as well. For non-root nodes the function returning true means the node is included in the order respective to its parent top-level node. It doesn't necessarily mean the node is visible — the parents can themselves be excluded from the order or they could be hidden.

NodeHandle Magnum::Ui::AbstractUserInterface::nodeOrderPrevious(NodeHandle handle) const

Previous node in draw and event processing order.

The previous node gets drawn earlier (thus is behind) and reacts to events later. Expects that handle is valid. Returns NodeHandle::Null if the node is first in the draw and processing order, is a root node that's not included in the draw and event processing order, or is a non-root node that's drawn in a regular hierarchy-defined order. The returned handle is always either valid or null.

NodeHandle Magnum::Ui::AbstractUserInterface::nodeOrderNext(NodeHandle handle) const

Next node in draw and event processing order.

The next node gets drawn later (thus is in front) and reacts to events earlier. Expects that handle is valid. Returns NodeHandle::Null if the node is last in the draw and processing order, is a root node that's not included in the draw and event processing order, or is a non-root node that's drawn in a regular hierarchy-defined order. The returned handle is always either valid or null.

NodeHandle Magnum::Ui::AbstractUserInterface::nodeOrderLastNested(NodeHandle handle) const

Last node in draw and event processing order nested under this node.

Expects that handle is valid. If any children are top-level nodes as well, points to the last of them, including any nested top-level hierarchies. If there are no child top-level hierarchies, returns handle itself.

The order of child top-level hierarchies relative to the handle gets preserved when modifying the top-level order of handle using setNodeOrder() or clearNodeOrder().

void Magnum::Ui::AbstractUserInterface::setNodeOrder(NodeHandle handle, NodeHandle behind)

Order a top-level node for draw and event processing.

The handle gets ordered to be drawn earlier (thus behind) and react to event later than behind. Expects that handle is valid and behind is either NodeHandle::Null or a valid node that's included in the draw and event processing order.

If handle is a root node, expects that behind is also a root node, if not null. If null, the handle is ordered as drawn last. The operation is done with an $ \mathcal{O}(1) $ complexity in this case.

If handle is not a root node, expects that both handle and behind have a common parent that's the closest top-level or root node for both, if behind is not null. If null, the handle is ordered after all other top-level nodes under its closest top-level or root parent. NodeFlag::Hidden set on any parent node affects the top-level node, NodeFlag::Clip, NoEvents or Disabled doesn't. The operation is done with an $ \mathcal{O}(n) $ complexity in this case, where $ n $ is the depth at which handle is in the node hierarchy.

If the node was previously in a different position in the draw and event processing order, it's moved, if it wasn't previously a top-level node or if it wasn't included in the draw and event processing order, it's inserted. Use clearNodeOrder() to exclude the node from the order afterwards and flattenNodeOrder() to integrate a non-root node back into the usual order defined by the node hierarchy.

Calling this function causes UserInterfaceState::NeedsNodeUpdate to be set.

void Magnum::Ui::AbstractUserInterface::clearNodeOrder(NodeHandle handle)

Clear a node from the draw and event processing order.

Expects that handle is valid. If the node isn't top-level or wasn't previously in the draw and processing order, the function is a no-op. After calling this function, isNodeOrdered() returns false for handle.

If handle is a root node, the operation is done with an $ \mathcal{O}(1) $ complexity. If handle is not a root node, the operation is done with an $ \mathcal{O}(n) $ complexity, where $ n $ is the depth at which handle is in the node hierarchy.

If the node contains any child top-level hierarchies, their order relative to the handle gets preserved, i.e. they get inserted back alongside it next time setNodeOrder() is called. Use flattenNodeOrder() to integrate a non-root node back into the usual order defined by the node hierarchy.

If not a no-op, calling this function causes UserInterfaceState::NeedsNodeUpdate to be set.

void Magnum::Ui::AbstractUserInterface::flattenNodeOrder(NodeHandle handle)

Flatten a non-root top-level node back to the usual order defined by the node hierarchy.

Expects that handle is valid and isn't a root node. Undoes the operation done by calling setNodeOrder() on a non-root node, i.e. the node is again drawn alongside its neighbors. If the node isn't top-level, the function is a no-op. After calling this function, isNodeTopLevel() returns false for handle.

The operation is done with an $ \mathcal{O}(n) $ complexity, where $ n $ is the depth at which handle is in the node hierarchy.

If the node contains nested top-level nodes, they stay top-level. Use clearNodeOrder() if you want to exclude a top-level node including all its children from the draw and event processing order.

If not a no-op, calling this function causes UserInterfaceState::NeedsNodeUpdate to be set.