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

Base for data layers.

Attaches data to particular nodes in the UI hierarchy, providing rendering and event handling functionality. See the AbstractUserInterface class documentation for introduction and overview of builtin layers. The following sections describe behavior common to all layers and provide a guide for implementing custom layers from scratch.

Layer data creation and removal

Layer data get created using create() with an optional NodeHandle to attach the data to, returning a DataHandle. The create() function is protected on the AbstractLayer, as concrete implementations require additional parameters. Such as TextLayer::create() taking also the actual text to render, or for example EventLayer having several differently named functions like onTapOrClick() or onDrag() for reacting to different events. As a special case the DebugLayer then doesn't expose any data creation functionality at all, as it manages its data implicitly internally based on what sources it tracks.

The DataHandle is a combination of a LayerHandle, identifying a particular layer the data is coming from, and a LayerDataHandle identifying data within given layer, extractible using dataHandleLayer() and dataHandleData(), respectively. All builtin layer APIs taking a DataHandle have overloads taking the smaller LayerDataHandle type as well, which is useful to save space in case you're storing the handles and know which layer they come from.

/* Query the node given data from an arbitrary layer is attached to */
Ui::DataHandle data = ;
Ui::NodeHandle node = ui.layer(dataHandleLayer(data)).node(data);

/* Remembering a data handle from a known layer in order to update it later */
Ui::LayerDataHandle bg = dataHandleData(ui.baseLayer().create());

ui.baseLayer().setColor(bg, 0x3bd267_rgbf);

Data lifetime is implicitly tied to a NodeHandle they're attached to, if any, so if the node or any of its parents get removed, all data attached to it from all layers get removed. It's also possible to remove the data directly using remove(), after which the DataHandle (or LayerDataHandle) becomes invalid. The remove() function is again protected as concrete layers may want to extend its behavior, such as in case of TextLayer::remove() or EventLayer::remove().

Node data attachments

Besides attaching directly in create() or its derivatives in subclasses, node attachment can be modified using attach(). A data can be also detached from a node by passing NodeHandle::Null, after which it's excluded from all updates and rendering until it's attached to a node again. This is useful for example to transfer some persistent state from one node to another, or when it's containing a heavy resource and it makes more sense to reattach an existing instance rather than remove and recreate it for a different node.

Ui::DataHandle label = textLayer.create(, node);

/* Stop showing the label but keep it instead of removing */
textLayer.attach(label, Ui::NodeHandle::Null);



/* Show it again */
textLayer.attach(label, node);

Creating a custom drawing layer

If none of the builtin layers provide desired functionality, it's possible to implement a custom layer that then gets added among others. At the very least, the doFeatures() function needs to be implemented. Based on which LayerFeature values it returns, other interfaces need to be implemented as well.

As an example, let's assume we want to implement a simple layer that draws colored quads with OpenGL. In other words, a very small subset of what BaseLayer provides. The initial setup could look like this, with doFeatures() returning LayerFeature::Draw and create() along with remove() made public, specifying quad color at creation time:

class QuadLayer: public Ui::AbstractLayer {
    public:
        explicit QuadLayer(Ui::LayerHandle handle);

        Ui::DataHandle create(const Color3& color,
                              Ui::NodeHandle node = Ui::NodeHandle::Null);
        void remove(Ui::DataHandle handle);
        void remove(Ui::LayerDataHandle handle);

    private:
        Ui::LayerFeatures doFeatures() const override {
            return Ui::LayerFeature::Draw;
        }
        

        struct Vertex {
            Vector2 position;
            Color3 color;
        };
        GL::Buffer _indices, _vertices;
        GL::Mesh _mesh;
        Shaders::FlatGL2D _shader{Shaders::FlatGL2D::Configuration{}
            .setFlags(Shaders::FlatGL2D::Flag::VertexColor)};

        Containers::Array<Color3> _colors;
};

Internally the layer contains a struct definition describing vertex layout, GL::Buffer for storing indices and vertices, a GL::Mesh, and a Shaders::FlatGL shader to draw the mesh with. The shader is set up with vertex colors enabled, and the mesh configured with a matching vertex layout.

QuadLayer::QuadLayer(Ui::LayerHandle handle): Ui::AbstractLayer{handle} {
    _mesh.addVertexBuffer(_vertices, 0,
            Shaders::FlatGL2D::Position{},
            Shaders::FlatGL2D::Color3{})
         .setIndexBuffer(_indices, 0, GL::MeshIndexType::UnsignedInt);
}

A common pattern for storing data associated with UI handles, as shown in Handles and resource ownership, is to have a contiguous array indexed by the handle ID, which is the case with the _colors member above. Creating a data delegates to the base create(), extracts the handle ID using dataHandleId(), enlarges the array to fit it, and saves the color there:

Ui::DataHandle QuadLayer::create(const Color3& color, Ui::NodeHandle node) {
    Ui::DataHandle handle = Ui::AbstractLayer::create(node);
    UnsignedInt dataId = dataHandleId(handle);
    if(dataId >= _colors.size())
        arrayResize(_colors, dataId + 1);

    _colors[dataId] = color;
    return handle;
}

As the layer stores just plain data, there's nothing to be done when data get removed — the color just stays unused in the array until it's overwritten by a different handle that reuses the same ID. Data removal thus simply delegates to the base remove(), providing both a DataHandle and a LayerDataHandle overload for convenience. Dealing with resources that need explicit destruction is described later.

void QuadLayer::remove(Ui::DataHandle handle) {
    Ui::AbstractLayer::remove(handle);
}

void QuadLayer::remove(Ui::LayerDataHandle handle) {
    Ui::AbstractLayer::remove(handle);
}

Implementing an update function

Each time AbstractUserInterface::draw() is called and something in the UI changed since last draw, it delegates to AbstractUserInterface::update() which then results in doUpdate() being called with appropriate inputs on all layers that need updating. The doUpdate() implementation prepares data for drawing; afterwards, depending on how many top-level node hierarchies the layer is used in, follow one or more doDraw() calls where the layer draws a slice of data prepared in doUpdate().

The doUpdate() function receives a broad set of inputs, initially we'll be interested in just the essential parameters to generate the quad mesh with, shown in the snippet below. The dataIds view is a list of data handle IDs attached to currently visible nodes, ordered back-to-front, so when they're drawn in this order, they overlap correctly. Each time doUpdate() is called, the dataIds may have different size and contain different entries in different order, based on what's currently visible.

void QuadLayer::doUpdate(Ui::LayerStates,
    const Containers::StridedArrayView1D<const UnsignedInt>& dataIds,
    const Containers::StridedArrayView1D<const UnsignedInt>&,
    const Containers::StridedArrayView1D<const UnsignedInt>&,
    const Containers::StridedArrayView1D<const Vector2>& nodeOffsets,
    const Containers::StridedArrayView1D<const Vector2>& nodeSizes,
    const Containers::StridedArrayView1D<const Float>&, Containers::BitArrayView,
    const Containers::StridedArrayView1D<const Vector2>&,
    const Containers::StridedArrayView1D<const Vector2>&,
    const Containers::StridedArrayView1D<const Vector2>&,
    const Containers::StridedArrayView1D<const Vector2>&)

Next there are nodeOffsets and nodeSizes, containing final node offsets and sizes with node hierarchy and all layouts applied. The views contain offsets and sizes for all nodes in the UI and are indexed by node ID. In other words, they're not matching the order in dataIds — instead, to get a NodeHandle attachment for a particular data ID, the view returned from nodes() is used, with nodeHandleId() extracting the ID out of the handle. Nodes that are not visible have offsets and sizes left in an unspecified state, but as dataIds only contain IDs attached to currently visible nodes, it's guaranteed that the nodes[dataId] is never NodeHandle::Null and both nodeOffsets[nodeId] and nodeSizes[nodeId] have a meaningful value.

With the above inputs, the vertexData (containing 4 vertices for each quad), and indexData (with 6 indices corresponding to the two triangles) are filled, and then those are uploaded to the GPU mesh for drawing:

{
    Containers::Array<Vertex> vertexData{NoInit, dataIds.size()*4};
    Containers::Array<UnsignedInt> indexData{NoInit, dataIds.size()*6};

    Containers::StridedArrayView1D<const Ui::NodeHandle> nodes = this->nodes();
    for(UnsignedInt i = 0; i != dataIds.size(); ++i) {
        UnsignedInt dataId = dataIds[i];
        UnsignedInt nodeId = nodeHandleId(nodes[dataId]);
        Range2D rect = Range2D::fromSize(nodeOffsets[nodeId], nodeSizes[nodeId]);

        /*           0--1          0-2 3
           vertices: |  | indices: |/ /|
                     2--3          1 4-5 */
        for(UnsignedInt j = 0; j != 4; ++j) {
            vertexData[i*4 + j].position = Math::lerp(rect.min(), rect.max(), j);
            vertexData[i*4 + j].color = _colors[dataId];
        }
        Utility::copy({i*4 + 0, i*4 + 2, i*4 + 1,
                       i*4 + 1, i*4 + 2, i*4 + 3},
                      indexData.sliceSize(i*6, 6));
    }

    _vertices.setData(vertexData);
    _indices.setData(indexData);
    _mesh.setCount(indexData.size());
}

Drawing the data

In the doUpdate() implementation above, we filled the mesh with vertex positions in UI units. To apply a correct projection in the shader, we need to know how large the UI is. The UI size is passed to the doSetSize() interface, which is called at least once before the first draw, and then each time the UI size changes. We'll use the size to form a projection matrix passed to Shaders::FlatGL::setTransformationProjectionMatrix(), the second argument is framebuffer size in pixels that we don't need at the moment. The Matrix3::projection() scales the size to the $ [-1, +1] $ unit square, additionally we convert from the UI library coordinate system with Y down and origin top left to OpenGL's Y up with origin in the center:

void QuadLayer::doSetSize(const Vector2& size, const Vector2i&) {
    _shader.setTransformationProjectionMatrix(
        Matrix3::scaling(Vector2::yScale(-1.0f))*
        Matrix3::translation({-1.0f, -1.0f})*
        Matrix3::projection(size));
}

Finally, the doDraw() interface is called with almost the same inputs as doUpdate(), but additionally an offset and count is supplied, describing the range of dataIds that is meant to be drawn. Since all setup and upload was done in doUpdate() already, we don't need to use any other arguments and just use the appropriate index range, i.e. multiplying the offset and count by 6 to get the quad index range corresponding to the data:

void QuadLayer::doDraw(
    const Containers::StridedArrayView1D<const UnsignedInt>&,
    std::size_t offset, std::size_t count,
    const Containers::StridedArrayView1D<const UnsignedInt>&,
    const Containers::StridedArrayView1D<const UnsignedInt>&,
    std::size_t, std::size_t,
    const Containers::StridedArrayView1D<const Vector2>&,
    const Containers::StridedArrayView1D<const Vector2>&,
    const Containers::StridedArrayView1D<const Float>&, Containers::BitArrayView,
    const Containers::StridedArrayView1D<const Vector2>&,
    const Containers::StridedArrayView1D<const Vector2>&)
{
    _mesh
        .setIndexOffset(offset*6)
        .setCount(count*6);
    _shader.draw(_mesh);
}

Drawing with alpha blending

The above showed the simplest possible case of drawing a fully opaque mesh. In many cases you'll however want to have some sort of transparency, even if just for smooth edges. For that, the layer can advertise Ui::LayerFeature::DrawUsesBlending in doFeatures(), which makes the user itnerface renderer instance enable the corresponding state (such as GL::Renderer::Feature::Blending in case of OpenGL) when needed. Together with using Color4 and the matching Shaders::FlatGL2D::Color4 attribute the QuadLayer would look like this instead:

class QuadLayer: public Ui::AbstractLayer {
    public:
        explicit QuadLayer(Ui::LayerHandle handle): Ui::AbstractLayer{handle} {
            _mesh.addVertexBuffer(_vertices, 0,
                Shaders::FlatGL2D::Position{},
                Shaders::FlatGL2D::Color4{});
            
        }

        Ui::DataHandle create(const Color4& color,
                              Ui::NodeHandle node = Ui::NodeHandle::Null);
        

    private:
        Ui::LayerFeatures doFeatures() const override {
            return Ui::LayerFeature::Draw|
                   Ui::LayerFeature::DrawUsesBlending;
        }
        

        struct Vertex {
            Vector2 position;
            Color4 color;
        };
        

        Containers::Array<Color4> _colors;
};

Note that the rest of the Ui library uses a premultiplied alpha workflow and the custom layer should match that. Thus for example making a color with 50% transparency is rgba*0.5f rather than {rgb, 0.5f}. You can also use the Color4::premultiplied() helper to convert non-premultiplied RGBA colors to premultiplied.

Dealing with node opacity and disabled state

Among the other inputs passed to doUpdate() are nodeOpacities and nodesEnabled. They reflect presence of NodeFlag::Disabled and values passed to AbstractUserInterface::setNodeOpacity(), together with propagation to child nodes. If we'd draw quads in disabled nodes grayscale and slightly darker, and apply the opacity in a premultiplied fashion, the relevant parts of the function would look like this:

void QuadLayer::doUpdate(Ui::LayerStates,
    const Containers::StridedArrayView1D<const UnsignedInt>& dataIds,
    const Containers::StridedArrayView1D<const UnsignedInt>&,
    const Containers::StridedArrayView1D<const UnsignedInt>&,
    const Containers::StridedArrayView1D<const Vector2>& nodeOffsets,
    const Containers::StridedArrayView1D<const Vector2>& nodeSizes,
    const Containers::StridedArrayView1D<const Float>& nodeOpacities,
    Containers::BitArrayView nodesEnabled,
    const Containers::StridedArrayView1D<const Vector2>&,
    const Containers::StridedArrayView1D<const Vector2>&,
    const Containers::StridedArrayView1D<const Vector2>&,
    const Containers::StridedArrayView1D<const Vector2>&)
{
    
    for(UnsignedInt i = 0; i != dataIds.size(); ++i) {
        

        Color4 color = _colors[dataId];
        if(!nodesEnabled[nodeId])
            color.rgb() = Color3{color.value()*0.75f};
        color *= nodeOpacities[nodeId];
        for(UnsignedInt j = 0; j != 4; ++j)
            vertexData[i*4 + j].color = color;
    }

    
}

You're free to do anything else with these inputs — for example have an entirely different "disabled look", or even ignore them altogether if given layer is not expected to be used on such nodes.

Taking clip rectangles into account

If the layer may get used within nodes that have NodeFlag::Clip enabled, such as various scroll areas, it should respect clip rectangles when drawing. The clipRectOffsets and clipRectSizes views passed to doUpdate() describe clip rectangle placement, clipRectIds and clipRectDataCounts then specify which of the rectangles is used for which subrange of dataIds. There's always at least one clip rectangle present and the sum of clipRectDataCounts is equal to size of dataIds.

As with node opacity and disabled state above, the actual implementation is entirely up to the layer itself. One option is to apply the clip rectangles directly to the vertex data, in this case performing an intersection of the quad with the clip rectangle using Math::intersect():

void QuadLayer::doUpdate(Ui::LayerStates,
    const Containers::StridedArrayView1D<const UnsignedInt>& dataIds,
    const Containers::StridedArrayView1D<const UnsignedInt>& clipRectIds,
    const Containers::StridedArrayView1D<const UnsignedInt>& clipRectDataCounts,
    const Containers::StridedArrayView1D<const Vector2>& nodeOffsets,
    const Containers::StridedArrayView1D<const Vector2>& nodeSizes,
    const Containers::StridedArrayView1D<const Float>&, Containers::BitArrayView,
    const Containers::StridedArrayView1D<const Vector2>& clipRectOffsets,
    const Containers::StridedArrayView1D<const Vector2>& clipRectSizes,
    const Containers::StridedArrayView1D<const Vector2>&,
    const Containers::StridedArrayView1D<const Vector2>&)
{
    

    UnsignedInt clipRect = 0;
    UnsignedInt clipRectDataCount = 0;
    for(UnsignedInt i = 0; i != dataIds.size(); ++i) {
        

        /* If the clip rectangle is empty, no clipping is active */
        Range2D rect = Range2D::fromSize(nodeOffsets[nodeId],
                                         nodeSizes[nodeId]);
        Range2D clip = Range2D::fromSize(clipRectOffsets[clipRectIds[clipRect]],
                                         clipRectSizes[clipRectIds[clipRect]]);
        if(!clip.size().isZero())
            rect = Math::intersect(rect, clip);
        for(UnsignedInt j = 0; j != 4; ++j)
            vertexData[i*4 + j].position = Math::lerp(rect.min(), rect.max(), j);

        /* The clip rect got applied to all data it affects, move to the next */
        if(++clipRectDataCount == clipRectDataCounts[clipRect]) {
            ++clipRect;
            clipRectDataCount = 0;
        }
    }
}

Clipping using GPU scissor rectangles

It's not always possible or efficient to clip the vertex data directly. In such cases it's possible to make use of scissor rectangles instead. Similarly as with blending, the layer advertises Ui::LayerFeature::DrawUsesScissor in doFeatures() to make the AbstractRenderer enable scissor state when drawing the layer. Clip rectangles are specified in framebuffer coordinates, thus doSetSize() now needs to remember both the UI and the framebuffer size:

Ui::LayerFeatures QuadLayer::doFeatures() const {
    return |Ui::LayerFeature::DrawUsesScissor;
}

void QuadLayer::doSetSize(const Vector2& size, const Vector2i& framebufferSize) {
    
    _size = size;
    _framebufferSize = framebufferSize;
}

And then, instead of clipping inside doUpdate(), the doDraw() function performs not just a single draw, but one for each clip rectangle. To match OpenGL framebuffer coordinates that have origin bottom left and Y up, the clip rectangles get scaled, Y-flipped and converted to integers.

void QuadLayer::doDraw(
    const Containers::StridedArrayView1D<const UnsignedInt>&,
    std::size_t offset, std::size_t,
    const Containers::StridedArrayView1D<const UnsignedInt>& clipRectIds,
    const Containers::StridedArrayView1D<const UnsignedInt>& clipRectDataCounts,
    std::size_t clipRectOffset, std::size_t clipRectCount,
    const Containers::StridedArrayView1D<const Vector2>&,
    const Containers::StridedArrayView1D<const Vector2>&,
    const Containers::StridedArrayView1D<const Float>&, Containers::BitArrayView,
    const Containers::StridedArrayView1D<const Vector2>& clipRectOffsets,
    const Containers::StridedArrayView1D<const Vector2>& clipRectSizes)
{
    std::size_t clipDataOffset = offset;
    for(std::size_t i = 0; i != clipRectCount; ++i) {
        UnsignedInt clipRectId = clipRectIds[clipRectOffset + i];
        UnsignedInt clipRectDataCount = clipRectDataCounts[clipRectOffset + i];
        Vector2i clipOffset = clipRectOffsets[clipRectId]*_framebufferSize/_size;
        Vector2i clipSize = clipRectSizes[clipRectId]*_framebufferSize/_size;

        /* If the clip rectangle is empty, not clipping anything, reset the
           scissor back to the whole framebuffer */
        GL::Renderer::setScissor(clipSize.isZero() ?
            Range2Di::fromSize({}, _framebufferSize) :
            Range2Di::fromSize(
                {clipOffset.x(), _framebufferSize.y() - clipOffset.y() - clipSize.y()},
                clipSize));

        _mesh
            .setIndexOffset(clipDataOffset*6)
            .setCount(clipRectDataCount*6);
        _shader.draw(_mesh);

        clipDataOffset += clipRectDataCount;
    }
}

You can assume that the list of clip rectangles is made in a way that minimizes the amount of extra draw calls this approach needs compared to culling CPU-side.

Setters and triggering data updates

So far, all updates and drawing happened only in a response to the node hierarchy changing in some way — nodes changing place, visibility, or data being attached / detached. Combined with the user interface only redrawing when needed it means that any updates to the layer data, such as changing the color of a particular quad, need to notify the UI that an update and redraw is needed. This is done with setNeedsUpdate(). A color setter would thus look like this:

void QuadLayer::setColor(Ui::DataHandle handle, const Color3& color) {
    CORRADE_ASSERT(isHandleValid(handle),
        "QuadLayer::setColor(): invalid handle" << handle, );
    _colors[Ui::dataHandleId(handle)] = color;
    setNeedsUpdate(Ui::LayerState::NeedsDataUpdate);
}

As the snippet shows, it's a good practice to check for handle validity, to ensure stale handles don't accidentally change unrelated data. All builtin APIs have those checks, but in this case we're directly accessing our own data array and thus nothing else can check the validity for us. Additionally it makes sense to provide also a LayerDataHandle overload so the setter can be called with the layer-specific handle type as well:

void QuadLayer::setColor(Ui::LayerDataHandle handle, const Color3& color) {
    CORRADE_ASSERT(isHandleValid(handle),
        "QuadLayer::setColor(): invalid handle" << handle, );
    _colors[Ui::layerDataHandleId(handle)] = color;
    setNeedsUpdate(Ui::LayerState::NeedsDataUpdate);
}

Populating the vertex data in data order instead of draw order

So far, the doUpdate() function populated the mesh with vertex data in order as drawn in that particular frame. While that makes the mesh always contain only exactly what's needed, it means reuploading also data that didn't change, such as the quad colors in our QuadLayer, just in a slightly different order.

Putting aside node opacity and disabled state for now, an alternative approach could be to fill the vertex colors directly in create() and setColor() instead of managing a CPU-side _colors array and then copying from it. The doUpdate() would then map the vertex buffer, update just the positions in it, and only the index buffer gets fully regenerated every time. We can use capacity() to size the buffer mapping, as it's an upper bound for all data IDs:

void QuadLayer::doUpdate() {
    /* vertices[i*4] to vertices[i*4 + 4] is a quad for data i */
    Containers::ArrayView<Vertex> vertices = Containers::arrayCast<Vertex>(
        _vertices.map(0, capacity()*sizeof(Vertex)*4, GL::Buffer::MapFlag::Write));
    Containers::StridedArrayView1D<const Ui::NodeHandle> nodes = this->nodes();
    for(UnsignedInt i = 0; i != dataIds.size(); ++i) {
        UnsignedInt dataId = dataIds[i];
        UnsignedInt nodeId = nodeHandleId(nodes[dataId]);
        Range2D rect = Range2D::fromSize(nodeOffsets[nodeId], nodeSizes[nodeId]);
        for(UnsignedInt j = 0; j != 4; ++j)
            vertices[dataId*4 + j].position = Math::lerp(rect.min(), rect.max(), j);
    }
    _vertices.unmap();

    /* indexData[i*6] to indexData[i*6 + 6] draws a quad for dataIds[i] */
    Containers::Array<UnsignedInt> indexData{NoInit, dataIds.size()*6};
    for(UnsignedInt i = 0; i != dataIds.size(); ++i) {
        UnsignedInt dataId = dataIds[i];
        Utility::copy({dataId*4 + 0, dataId*4 + 2, dataId*4 + 1,
                       dataId*4 + 1, dataId*4 + 2, dataId*4 + 3},
                      indexData.sliceSize(i*6, 6));
    }
    _indices.setData(indexData);

    _mesh.setCount(indexData.size());
}

Partial updates

The first argument to doUpdate() is a set of LayerState bits, which enumerates the reasons why an update needs to done. For example, in given frame only LayerState::NeedsNodeOrderUpdate could be set due to a popup being brought to the front, but node positions and everything else would stay unchanged. Or it could be just LayerState::NeedsDataUpdate being triggered through setNeedsUpdate() from a setter, but the node hierarchy stays the same.

The doUpdate() implementation can make use of these states to perform just partial updates. This makes sense especially in the above case where the draw data are stored in a way that's independent from the actual draw order, because otherwise even changes in node visibility would require rebuilding all data in the new order. Note that there are various interactions between the states, see particular LayerState values for more information.

void QuadLayer::doUpdate(Ui::LayerStates state, ) {
    if(state & Ui::LayerState::NeedsNodeOffsetSizeUpdate) {
        /* Perform updates to vertex positions */
    }

    if(state & (Ui::LayerState::NeedsNodeEnabledUpdate|
                Ui::LayerState::NeedsNodeOpacityUpdate|
                Ui::LayerState::NeedsDataUpdate)) {
        /* Perform updates to vertex colors */
    }

    if(state & Ui::LayerState::NeedsNodeOrderUpdate) {
        /* Perform updates to the index buffer */
    }
}

Explicitly and implicitly triggered updates

In addition to LayerState::NeedsDataUpdate, the setNeedsUpdate() function can be called also with NeedsCommonDataUpdate and NeedsSharedDataUpdate. These two states are never triggered by the UI library but are meant to be used by the layer itself, to denote a need for updates that aren't tied to any concrete data ID.

For example, in the original case of data being stored in draw order the index buffer is always the same and only needs to be updated if it isn't large enough. For that, the create() implementation would call setNeedsUpdate() with LayerState::NeedsCommonDataUpdate if the buffer needs to be enlarged, which then gets done in doUpdate().

Ui::DataHandle QuadLayer::create(const Color3& color, Ui::NodeHandle node) {
    UnsignedInt capacityBefore = capacity();
    Ui::DataHandle handle = Ui::AbstractLayer::create(node);
    UnsignedInt dataId = dataHandleId(handle);
    if(dataId >= capacityBefore)
        setNeedsUpdate(Ui::LayerState::NeedsCommonDataUpdate);

    
    return handle;
}

void QuadLayer::doUpdate(Ui::LayerStates state, ) {
    if(state & Ui::LayerState::NeedsCommonDataUpdate) {
        Containers::Array<UnsignedInt> indexData{NoInit, capacity()*6};
        for(UnsignedInt i = 0; i != capacity(); ++i) {
            Utility::copy({i*4 + 0, i*4 + 2, i*4 + 1,
                           i*4 + 1, i*4 + 2, i*4 + 3},
                          indexData.sliceSize(i*6, 6));
        }
        _indices.setData(indexData);
    }

    
}

It's of course possible to perform the index buffer upload directly in create() as well, but when creating a lot of data the buffer could get reuploaded several times over. Deferring the update like this makes it updated at most once per frame.

The LayerState::NeedsSharedDataUpdate is then for differentiating updates of data that may be shared among multiple layers. This is what for example BaseLayer uses to trigger updates of style data, which are stored in BaseLayer::Shared, and for which it's enough to be updated just once for all layers that use the same shared instance.

Finally, it might not always be possible to have setNeedsUpdate() called in order to trigger an update. One such case is when the layer polls data from an external source, and there's no way for the source to explicitly notify the layer about updates. To solve this, the layer can implement the doState() interface and return LayerState::NeedsDataUpdate or similar in case an update was detected. A common pattern is for the external source to have some sort of a last update timestamp, or even just a counter that gets incremented on every update. The layer then compares its copy against it in doState(), and refreshes the saved timestamp in doUpdate():

Ui::LayerStates QuadLayer::doState() const {
    if(_lastUpdate != _externalColors.lastUpdate())
        return Ui::LayerState::NeedsDataUpdate;
    return {};
}

void QuadLayer::doUpdate(Ui::LayerStates state,  ) {
    if(state & Ui::LayerState::NeedsDataUpdate) {
        _lastUpdate = _externalColors.lastUpdate();
        
    }

    
}

Resource cleanup on data removal

Besides plain data, it's possible for layers to store heavier resources. As an example, let's assume the quads are textured, with each such quad using a dedicated GL::Texture2D. Putting aside the obvious downsides like each quad needing a separate draw call, we should ensure that the textures don't just stay in GPU memory after the data are removed. We hande that explicitly in the remove() overrides:

class QuadLayer: public Ui::AbstractLayer {
    

    private:
        Containers::Array<Containers::Optional<GL::Texture2D>> _textures;
};

void QuadLayer::remove(Ui::DataHandle handle) {
    Ui::AbstractLayer::remove(handle);
    _textures[dataHandleId(handle)] = Containers::NullOpt;
}

More commonly however, instead of users explicitly calling remove(), the data get removed as a consequence of node hierarchy removal. For that there's the doClean() interface, which gets called as part of the regular update if any cascaded removes happened since last time. It gets a bitmask marking which data IDs got removed, which we use to perform a cleanup:

void QuadLayer::doClean(Containers::BitArrayView dataIdsToRemove) {
    for(UnsignedInt i = 0; i != dataIdsToRemove.size(); ++i) {
        if(i)
            _textures[i] = Containers::NullOpt;
    }
}

The doClean() interface is designed like this instead of something like calling remove() in a loop in order to allow implementations to batch the operations. For example, if the textures would be instead in some sort of an atlas that needs repacking afterwards, doing it just once for all removed textures would be more efficient than repacking after each removal.

Custom event handling layers

While the builtin EventLayer allows attaching callbacks to various high-level events like taps, clicks or pinch gestures, you may need specialized behavior that can only be implemented by directly accessing the low-level event interfaces in a custom layer. Another use case is implementing event handling in addition to drawing, for example to implicitly handle hover and pressed state. While it could be done externally with EventLayer::onEnter(), onLeave() and such, implementing it directly on the custom drawing layer is often simpler and makes the layer more self-contained.

A layer that wants to handle events advertises LayerFeature::Event in doFeatures() and implements one or more of the do*Event() interfaces, overview of which is below. As with drawing, an event handling layer doesn't need to implement doUpdate() or doClean() — if it has nothing to do on a per-data basis, it can contain only event handlers alone.

Reacting to hover

For a simple introductory example, let's make the QuadLayer react to hover by making given quad brighter. The doPointerMoveEvent() is called in a response to a pointer moving over area of a node that given dataId is attached to. With PointerMoveEvent::setAccepted() we let the UI know that it's handling the event and the event shouldn't get propagated to further nodes. Here we want to handle a hover on all quads, so we call it regardless of the data ID the event happens on, but the layer can implement any behavior it wants. We however restrict it to just primary events, i.e. we don't want secondary fingers in a multi-touch event to highlight the node:

Ui::LayerFeatures QuadLayer::doFeatures() const {
    return |Ui::LayerFeature::Event;
}

void QuadLayer::doPointerMoveEvent(UnsignedInt, Ui::PointerMoveEvent& event) {
    if(!event.isPrimary())
        return;

    event.setAccepted();
}

If the move event gets accepted and the node wasn't hovered already, doPointerEnterEvent() gets called next. Once the pointer moves outside of the node area, doPointerLeaveEvent() gets called. We'll simply brighten the color in one and darken again in the other, and call setNeedsUpdate() to let the UI know that we need an update and a redraw. Compared to doPointerMoveEvent(), calling setAccepted() isn't needed, as the enter and leave events don't propagate anywhere if not handled.

void QuadLayer::doPointerEnterEvent(UnsignedInt dataId, Ui::PointerMoveEvent&) {
    _colors[dataId] *= 1.25f;

    setNeedsUpdate(Ui::LayerState::NeedsDataUpdate);
}

void QuadLayer::doPointerLeaveEvent(UnsignedInt dataId, Ui::PointerMoveEvent&) {
    _colors[dataId] /= 1.25f;

    setNeedsUpdate(Ui::LayerState::NeedsDataUpdate);
}

The pair of enter / leave events deals with the common case of a pointer moving across the user interface, but it can also happen that the currently hovered nodes stops being visible or for example gets disabled, and at that point it should no longer show a hover state. We'll get notified about that and other cases in doVisibilityLostEvent(), VisibilityLostEvent::isNodeHovered() tells us if the node was hovered before and we should thus darken again:

void QuadLayer::doVisibilityLostEvent(UnsignedInt dataId, Ui::VisibilityLostEvent& event) {
    if(event.isNodeHovered()) {
        _colors[dataId] /= 1.25f;

        setNeedsUpdate(Ui::LayerState::NeedsDataUpdate);
    }
}

Pointer press and release, pointer capture

The doPointerPressEvent() and doPointerReleaseEvent() interfaces are called when a pointer is pressed and released on a node. Compared to the EventLayer tap or click handler, there isn't any high-level tap or click event on the AbstractLayer itself, that's up to the layer to implement if needed. The PointerEvent exposes various state for this, allowing you to decide what a click should actually be, such as taking into distance between a press and a release, time between the events, pointers being pressed etc.

Additionally, pointer capture is implicitly enabled, meaning that all events following a press get sent to the originating node even if the pointer leaves its area. Furthermore, the event handlers can toggle the capture at any time, which can be used to implement advanced functionality.

For example, we can use a press and a drag of a (primary mouse, pen or touch) pointer to change the color brightness, but dragging more than a certain distance will cancel the whole action, reverting back to the original color:

void QuadLayer::doPointerPressEvent(UnsignedInt dataId, Ui::PointerEvent& event) {
    /* If a primary pointer is pressed, remember the current color. No need for
       setting NeedsDataUpdate as nothing visually changed. */
    if(event.isPrimary() &&
       (event.pointer() & (Ui::Pointer::MouseLeft|
                           Ui::Pointer::Pen|
                           Ui::Pointer::Finger))) {
        _originalColor = _colors[dataId];
        event.setAccepted();
    }
}

void QuadLayer::doPointerMoveEvent(UnsignedInt dataId, Ui::PointerMoveEvent& event) {
    /* If a primary pointer is among the ones currently pressed and it's
       captured, i..e. it originated from a press on this node */
    if(event.isPrimary() &&
       (event.pointers() & (Ui::Pointer::MouseLeft|
                            Ui::Pointer::Pen|
                            Ui::Pointer::Finger)) &&
       event.isCaptured())
    {
        /* Calculate distance from node center */
        Vector2 distance = event.position() - event.nodeSize()*0.5f;

        /* If further than 100 pixels, cancel pointer capture and reset the
           color back */
        if(distance.dot() >= Math::pow<2>(100.0f)) {
            event.setCaptured(false);
            _colors[dataId] = _originalColor;

        /* Otherwise update the color based on the Y distance, brightening up
           and darkening down */
        } else {
            _colors[dataId] = distance.y() > 0.0f ?
                _originalColor/(1.0f + distance.y()/100.0f) :
                _originalColor*(1.0f + distance.y()/100.0f);
        }

        event.setAccepted();
        setNeedsUpdate(Ui::LayerState::NeedsDataUpdate);
    }
}

Finally, doScrollEvent() handles scroll wheel and trackpad input. It's affected by pointer capture as well but there isn't anything specific to wheel events that would need a dedicated example.

Key events, node focus and text input

The doKeyPressEvent() and doKeyReleaseEvent() interfaces are called in response to keyboard input. By default, if no node is focused, they're delivered the same way as pointer events, i.e. to a node under pointer or to the currently captured node. As an example, we could react to R being pressed to reset a color to some default. Important to note is that we check for KeyEvent::modifiers() to be empty to not also react to Ctrl R and such:

void QuadLayer::doKeyPressEvent(UnsignedInt dataId, Ui::KeyEvent& event) {
    if(event.key() == Ui::Key::R && event.modifiers() == Ui::Modifiers{}) {
        _colors[dataId] = ;
        event.setAccepted();
        setNeedsUpdate(Ui::LayerState::NeedsDataUpdate);
    }
}

Nodes that have NodeFlag::Focusable enabled can be focused, after which they receive all key input as well as text input in doTextInputEvent() regardless of pointer location or capture. Focusing is usually done in a response to an accepted pointer press, i.e. doPointerPressEvent() has to accept a press first, and then a doFocusEvent() allows the node to decide about the focus. When the node loses focus again, doBlurEvent() is called. Besides a pointer press, AbstractUserInterface::focusEvent() can be used to programmatically focus a node, which results in doFocusEvent() / doBlurEvent() being called directly.

For a practical example, let's say we want to be able to type out a hexadecimal color to change a color of a focused quad — assuming it was attached to a NodeFlag::Focusable node. For simplicity, as soon as all six letters are typed, the color is changed and input starts over:

void QuadLayer::doPointerPressEvent(UnsignedInt, Ui::PointerEvent& event) {
    /* Accept press to get a focus */
    event.setAccepted();
}

void QuadLayer::doFocusEvent(UnsignedInt, Ui::FocusEvent& event) {
    /* Accept focus to get a text input */
    event.setAccepted();
}

void QuadLayer::doKeyPressEvent(UnsignedInt, Ui::KeyEvent& event) {
    /* If the node is focused (and thus we're editing the color), remove last
       char on backspace */
    if(event.isNodeFocused() &&
       event.key() == Ui::Key::Backspace &&
       event.modifiers() == Ui::Modifiers{})
    {
        _editedColor >>= 4;
        if(_charCount)
            --_charCount;
        event.setAccepted();
    }
}

void QuadLayer::doTextInputEvent(UnsignedInt dataId, Ui::TextInputEvent& event) {
    for(char c: event.text()) {
        char value;
        if(c >= '0' && c <= '9')
            value = c - 0;
        else if((c >= 'a' && c <= 'f') ||
                (c >= 'A' && c <= 'F'))
            value = 10 + (c & ~0x20) - 'a';
        /* Skip unknown chars for simplicity */
        else continue;

        _editedColor = (_editedColor << 4)|value;
        ++_charCount;

        /* Got exactly six chars, update the color and reset */
        if(_charCount == 6) {
            _colors[dataId] = Color3::fromLinearRgbInt(_editedColor);
            _charCount = _editedColor = {};
            setNeedsUpdate(Ui::LayerState::NeedsDataUpdate);
        }
    }

    event.setAccepted();
}

Note that TextInputEvent::text() is allowed to contain more than one byte. If text input is active, regular key events still get sent as well, to allow the layer to perform other operations with them if needed, such as a backspace here. The TextLayer implements more advanced text editing capabilities on top of these events, see the TextEdit enum and TextLayer::editText() for an overview of common editing behavior mapped to keyboard shortcuts.

Derived classes

class AbstractVisualLayer new in Git master
Base for visual data layers.
class DebugLayer new in Git master
Debug layer.
class EventLayer new in Git master
Event handling layer.

Constructors, destructors, conversion operators

AbstractLayer(LayerHandle handle) explicit
Constructor.
AbstractLayer(const AbstractLayer&) deleted
Copying is not allowed.
AbstractLayer(AbstractLayer&&) noexcept
Move constructor.

Public functions

auto operator=(const AbstractLayer&) -> AbstractLayer& deleted
Copying is not allowed.
auto operator=(AbstractLayer&&) -> AbstractLayer& noexcept
Move assignment.
auto handle() const -> LayerHandle
Layer handle.
auto features() const -> LayerFeatures
Features exposed by a layer.
auto state() const -> LayerStates
Layer state.
void setNeedsUpdate(LayerStates state)
Mark the layer as needing an update.
auto capacity() const -> std::size_t
Current capacity of the data storage.
auto usedCount() const -> std::size_t
Count of used items in the data storage.
auto isHandleValid(LayerDataHandle handle) const -> bool
Whether a data handle is valid.
auto isHandleValid(DataHandle handle) const -> bool
Whether a data handle is valid.
void attach(DataHandle data, NodeHandle node)
Attach data to a node.
void attach(LayerDataHandle data, NodeHandle node)
Attach data to a node assuming it belongs to this layer.
auto node(DataHandle data) const -> NodeHandle
Node attachment for given data.
auto node(LayerDataHandle data) const -> NodeHandle
Node attachment for given data assuming it belongs to this layer.
auto nodes() const -> Containers::StridedArrayView1D<const NodeHandle>
Node attachments for all data.
auto generations() const -> Containers::StridedArrayView1D<const UnsignedShort>
Generation counters for all data.
void setSize(const Vector2& size, const Vector2i& framebufferSize)
Set user interface size.
void cleanNodes(const Containers::StridedArrayView1D<const UnsignedShort>& nodeHandleGenerations)
Clean data attached to no longer valid nodes.
void cleanData(const Containers::Iterable<AbstractAnimator>& animators)
Clean animations attached to no longer valid data.
void advanceAnimations(Nanoseconds time, Containers::MutableBitArrayView activeStorage, Containers::MutableBitArrayView startedStorage, Containers::MutableBitArrayView stoppedStorage, const Containers::StridedArrayView1D<Float>& factorStorage, Containers::MutableBitArrayView removeStorage, const Containers::Iterable<AbstractDataAnimator>& animators)
Advance data animations in animators assigned to this layer.
void advanceAnimations(Nanoseconds time, Containers::MutableBitArrayView activeStorage, Containers::MutableBitArrayView startedStorage, Containers::MutableBitArrayView stoppedStorage, const Containers::StridedArrayView1D<Float>& factorStorage, Containers::MutableBitArrayView removeStorage, const Containers::Iterable<AbstractStyleAnimator>& animators)
Advance style animations in animators assigned to this layer.
void preUpdate(LayerStates state)
Pre-update common and shared layer data.
void update(LayerStates state, const Containers::StridedArrayView1D<const UnsignedInt>& dataIds, const Containers::StridedArrayView1D<const UnsignedInt>& clipRectIds, const Containers::StridedArrayView1D<const UnsignedInt>& clipRectDataCounts, const Containers::StridedArrayView1D<const Vector2>& nodeOffsets, const Containers::StridedArrayView1D<const Vector2>& nodeSizes, const Containers::StridedArrayView1D<const Float>& nodeOpacities, Containers::BitArrayView nodesEnabled, const Containers::StridedArrayView1D<const Vector2>& clipRectOffsets, const Containers::StridedArrayView1D<const Vector2>& clipRectSizes, const Containers::StridedArrayView1D<const Vector2>& compositeRectOffsets, const Containers::StridedArrayView1D<const Vector2>& compositeRectSizes)
Update visible layer data to given offsets and positions.
void composite(AbstractRenderer& renderer, const Containers::StridedArrayView1D<const Vector2>& rectOffsets, const Containers::StridedArrayView1D<const Vector2>& rectSizes, std::size_t offset, std::size_t count)
Composite previously rendered contents.
void draw(const Containers::StridedArrayView1D<const UnsignedInt>& dataIds, std::size_t offset, std::size_t count, const Containers::StridedArrayView1D<const UnsignedInt>& clipRectIds, const Containers::StridedArrayView1D<const UnsignedInt>& clipRectDataCounts, std::size_t clipRectOffset, std::size_t clipRectCount, const Containers::StridedArrayView1D<const Vector2>& nodeOffsets, const Containers::StridedArrayView1D<const Vector2>& nodeSizes, const Containers::StridedArrayView1D<const Float>& nodeOpacities, Containers::BitArrayView nodesEnabled, const Containers::StridedArrayView1D<const Vector2>& clipRectOffsets, const Containers::StridedArrayView1D<const Vector2>& clipRectSizes)
Draw a sub-range of visible layer data.
void pointerPressEvent(UnsignedInt dataId, PointerEvent& event)
Handle a pointer press event.
void pointerReleaseEvent(UnsignedInt dataId, PointerEvent& event)
Handle a pointer release event.
void pointerMoveEvent(UnsignedInt dataId, PointerMoveEvent& event)
Handle a pointer move event.
void pointerEnterEvent(UnsignedInt dataId, PointerMoveEvent& event)
Handle a pointer enter event.
void pointerLeaveEvent(UnsignedInt dataId, PointerMoveEvent& event)
Handle a pointer leave event.
void pointerCancelEvent(UnsignedInt dataId, PointerCancelEvent& event)
Handle a pointer cancel event.
void scrollEvent(UnsignedInt dataId, ScrollEvent& event)
Handle a scroll event.
void focusEvent(UnsignedInt dataId, FocusEvent& event)
Handle a focus event.
void blurEvent(UnsignedInt dataId, FocusEvent& event)
Handle a blur event.
void keyPressEvent(UnsignedInt dataId, KeyEvent& event)
Handle a key press event.
void keyReleaseEvent(UnsignedInt dataId, KeyEvent& event)
Handle a key release event.
void textInputEvent(UnsignedInt dataId, TextInputEvent& event)
Handle a text input event.
void visibilityLostEvent(UnsignedInt dataId, VisibilityLostEvent& event)
Handle a visibility lost event.

Protected functions

auto hasUi() const -> bool
Whether the layer is a part of an user interface instance.
auto ui() -> AbstractUserInterface&
User interface instance the layer is part of.
auto ui() const -> const AbstractUserInterface&
auto create(NodeHandle node = NodeHandle::Null) -> DataHandle
Create a data.
void remove(DataHandle handle)
Remove a data.
void remove(LayerDataHandle handle)
Remove a data assuming it belongs to this layer.
void assignAnimator(AbstractDataAnimator& animator) const
Assign a data animator to this layer.
void assignAnimator(AbstractStyleAnimator& animator) const
Assign a style animator to this layer.

Private functions

auto doFeatures() const -> LayerFeatures pure virtual
Implementation for features()
auto doState() const -> LayerStates virtual
Query layer state.
void doSetSize(const Vector2& size, const Vector2i& framebufferSize) virtual
Set user interface size.
void doClean(Containers::BitArrayView dataIdsToRemove) virtual
Clean no longer valid layer data.
void doAdvanceAnimations(Nanoseconds time, Containers::MutableBitArrayView activeStorage, Containers::MutableBitArrayView startedStorage, Containers::MutableBitArrayView stoppedStorage, const Containers::StridedArrayView1D<Float>& factorStorage, Containers::MutableBitArrayView removeStorage, const Containers::Iterable<AbstractDataAnimator>& animators) virtual
Advance data animations in animators assigned to this layer.
void doAdvanceAnimations(Nanoseconds time, Containers::MutableBitArrayView activeStorage, Containers::MutableBitArrayView startedStorage, Containers::MutableBitArrayView stoppedStorage, const Containers::StridedArrayView1D<Float>& factorStorage, Containers::MutableBitArrayView removeStorage, const Containers::Iterable<AbstractStyleAnimator>& animators) virtual
Advance style animations in animators assigned to this layer.
void doPreUpdate(LayerStates state) virtual
Pre-update common and shared layer data.
void doUpdate(LayerStates state, const Containers::StridedArrayView1D<const UnsignedInt>& dataIds, const Containers::StridedArrayView1D<const UnsignedInt>& clipRectIds, const Containers::StridedArrayView1D<const UnsignedInt>& clipRectDataCounts, const Containers::StridedArrayView1D<const Vector2>& nodeOffsets, const Containers::StridedArrayView1D<const Vector2>& nodeSizes, const Containers::StridedArrayView1D<const Float>& nodeOpacities, Containers::BitArrayView nodesEnabled, const Containers::StridedArrayView1D<const Vector2>& clipRectOffsets, const Containers::StridedArrayView1D<const Vector2>& clipRectSizes, const Containers::StridedArrayView1D<const Vector2>& compositeRectOffsets, const Containers::StridedArrayView1D<const Vector2>& compositeRectSizes) virtual
Update visible layer data to given offsets and positions.
void doComposite(AbstractRenderer& renderer, const Containers::StridedArrayView1D<const Vector2>& compositeRectOffsets, const Containers::StridedArrayView1D<const Vector2>& compositeRectSizes, std::size_t offset, std::size_t count) virtual
Composite previously rendered contents.
void doDraw(const Containers::StridedArrayView1D<const UnsignedInt>& dataIds, std::size_t offset, std::size_t count, const Containers::StridedArrayView1D<const UnsignedInt>& clipRectIds, const Containers::StridedArrayView1D<const UnsignedInt>& clipRectDataCounts, std::size_t clipRectOffset, std::size_t clipRectCount, const Containers::StridedArrayView1D<const Vector2>& nodeOffsets, const Containers::StridedArrayView1D<const Vector2>& nodeSizes, const Containers::StridedArrayView1D<const Float>& nodeOpacities, Containers::BitArrayView nodesEnabled, const Containers::StridedArrayView1D<const Vector2>& clipRectOffsets, const Containers::StridedArrayView1D<const Vector2>& clipRectSizes) virtual
Draw a sub-range of visible layer data.
void doPointerPressEvent(UnsignedInt dataId, PointerEvent& event) virtual
Handle a pointer press event.
void doPointerReleaseEvent(UnsignedInt dataId, PointerEvent& event) virtual
Handle a pointer release event.
void doPointerMoveEvent(UnsignedInt dataId, PointerMoveEvent& event) virtual
Handle a pointer move event.
void doPointerEnterEvent(UnsignedInt dataId, PointerMoveEvent& event) virtual
Handle a pointer enter event.
void doPointerLeaveEvent(UnsignedInt dataId, PointerMoveEvent& event) virtual
Handle a pointer leave event.
void doPointerCancelEvent(UnsignedInt dataId, PointerCancelEvent& event) virtual
Handle a pointer cancel event.
void doScrollEvent(UnsignedInt dataId, ScrollEvent& event) virtual
Handle a scroll event.
void doFocusEvent(UnsignedInt dataId, FocusEvent& event) virtual
Handle a focus event.
void doBlurEvent(UnsignedInt dataId, FocusEvent& event) virtual
Handle a blur event.
void doKeyPressEvent(UnsignedInt dataId, KeyEvent& event) virtual
Handle a key press event.
void doKeyReleaseEvent(UnsignedInt dataId, KeyEvent& event) virtual
Handle a pointer release event.
void doTextInputEvent(UnsignedInt dataId, TextInputEvent& event) virtual
Handle a text input event.
void doVisibilityLostEvent(UnsignedInt dataId, VisibilityLostEvent& event) virtual
Handle a visibility lost event.

Function documentation

Magnum::Ui::AbstractLayer::AbstractLayer(LayerHandle handle) explicit

Constructor.

Parameters
handle Handle returned by AbstractUserInterface::createLayer()

Magnum::Ui::AbstractLayer::AbstractLayer(AbstractLayer&&) noexcept

Move constructor.

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

LayerHandle Magnum::Ui::AbstractLayer::handle() const

Layer handle.

Returns the handle passed to the constructor.

LayerStates Magnum::Ui::AbstractLayer::state() const

Layer state.

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

void Magnum::Ui::AbstractLayer::setNeedsUpdate(LayerStates state)

Mark the layer as needing an update.

Meant to be called by layer implementations when the data get modified. Expects that state is a non-empty subset of LayerState::NeedsDataUpdate, NeedsCommonDataUpdate, NeedsSharedDataUpdate, and if the layer advertises LayerFeature::Composite, also LayerState::NeedsCompositeOffsetSizeUpdate. See the flags for more information.

std::size_t Magnum::Ui::AbstractLayer::capacity() const

Current capacity of the data storage.

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

std::size_t Magnum::Ui::AbstractLayer::usedCount() const

Count of used items in the data storage.

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

bool Magnum::Ui::AbstractLayer::isHandleValid(LayerDataHandle handle) const

Whether a data handle is valid.

A handle is valid if it has been returned from create() before and remove() wasn't called on it yet. For LayerDataHandle::Null always returns false.

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

Whether a data handle is valid.

A shorthand for extracting a LayerHandle from handle using dataHandleLayer(), comparing it to handle() and if it's the same, calling 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.

void Magnum::Ui::AbstractLayer::attach(DataHandle data, NodeHandle node)

Attach data to a node.

Makes the data handle tied to a particular node, meaning it gets included in draw or event processing depending on node position and visibility. Also, AbstractUserInterface::removeNode() called for node or any parent node will then mean that the data gets scheduled for removal during the next cleanNodes() call.

Expects that data is valid. The node can be anything including NodeHandle::Null, but if it's non-null and not valid the data will be scheduled for deletion during the next cleanNodes() call. If the data is already attached to some node, this will overwrite the previous attachment — i.e., it's not possible to have the same data attached to multiple nodes. The inverse, attaching multiple different data handles to a single node, is supported however.

If data wasn't attached to node before, calling this function causes LayerState::NeedsAttachmentUpdate to be set. Additionally, if node isn't NodeHandle::Null, LayerState::NeedsNodeOffsetSizeUpdate is set as well.

void Magnum::Ui::AbstractLayer::attach(LayerDataHandle data, NodeHandle node)

Attach data to a node assuming it belongs to this layer.

Like attach(DataHandle, NodeHandle) but without checking that data indeed belongs to this layer. See its documentation for more information.

NodeHandle Magnum::Ui::AbstractLayer::node(DataHandle data) const

Node attachment for given data.

Expects that data is valid. If given data isn't attached to any node, returns NodeHandle::Null. See also node(LayerDataHandle) const which is a simpler operation if the data is already known to belong to this layer.

The returned handle may be invalid if either the data got attached to an invalid node in the first place or the node or any of its parents were removed and AbstractUserInterface::clean() wasn't called since.

NodeHandle Magnum::Ui::AbstractLayer::node(LayerDataHandle data) const

Node attachment for given data assuming it belongs to this layer.

Like node(DataHandle) const but without checking that data indeed belongs to this layer. See its documentation for more information.

Containers::StridedArrayView1D<const NodeHandle> Magnum::Ui::AbstractLayer::nodes() const

Node attachments for all data.

Used internally from AbstractUserInterface::update(), meant to be also used by doUpdate() implementations to map data IDs to node handles. Size of the returned view is the same as capacity(). Items that are NodeHandle::Null are either data with no node attachments or corresponding to data that are freed.

Containers::StridedArrayView1D<const UnsignedShort> Magnum::Ui::AbstractLayer::generations() const

Generation counters for all data.

Meant to be used by code that only gets data IDs or masks but needs the full handle, or for various diagnostic purposes such as tracking handle recycling. Size of the returned view is the same as capacity(), individual items correspond to generations of particular data IDs. All values fit into the DataHandle / LayerDataHandle generation bits, 0 denotes an expired generation counter.

Passing an ID along with the corresponding generation to layerDataHandle() produces a LayerDataHandle, passing that along with handle() to dataHandle() produces a DataHandle. Use isHandleValid(LayerDataHandle) const / isHandleValid(DataHandle) const to determine whether given slot is actually used.

void Magnum::Ui::AbstractLayer::setSize(const Vector2& size, const Vector2i& framebufferSize)

Set user interface size.

Used internally from AbstractUserInterface::setSize() and AbstractUserInterface::setLayerInstance(). Exposed just for testing purposes, there should be no need to call this function directly. Expects that the layer supports LayerFeature::Draw and that both sizes are non-zero. Delegates to doSetSize(), see its documentation for more information about the arguments.

void Magnum::Ui::AbstractLayer::cleanNodes(const Containers::StridedArrayView1D<const UnsignedShort>& nodeHandleGenerations)

Clean data attached to no longer valid nodes.

Used internally from AbstractUserInterface::clean(). Exposed just for testing purposes, there should be no need to call this function directly and doing so may cause internal AbstractUserInterface state update to misbehave. Assumes that nodeHandleGenerations contains handle generation counters for all nodes, where the index is implicitly the handle ID. They're used to decide about node attachment validity, data with invalid node attachments are then removed. Delegates to doClean(), see its documentation for more information about the arguments.

void Magnum::Ui::AbstractLayer::cleanData(const Containers::Iterable<AbstractAnimator>& animators)

Clean animations attached to no longer valid data.

Used internally from AbstractUserInterface::clean(). Exposed just for testing purposes, there should be no need to call this function directly and doing so may cause internal AbstractUserInterface state update to misbehave. Expects that all animators expose AnimatorFeature::DataAttachment and their AbstractAnimator::layer() matches handle(), and assumes that all such animators are passed together in a single call. Delegates to AbstractAnimator::cleanData() for every animator, see its documentation for more information.

Calling this function resets LayerState::NeedsDataClean, however note that behavior of this function is independent of state() — it performs the clean always regardless of what flags are set.

void Magnum::Ui::AbstractLayer::advanceAnimations(Nanoseconds time, Containers::MutableBitArrayView activeStorage, Containers::MutableBitArrayView startedStorage, Containers::MutableBitArrayView stoppedStorage, const Containers::StridedArrayView1D<Float>& factorStorage, Containers::MutableBitArrayView removeStorage, const Containers::Iterable<AbstractDataAnimator>& animators)

Advance data animations in animators assigned to this layer.

Used internally from AbstractUserInterface::advanceAnimations(). Exposed just for testing purposes, there should be no need to call this function directly and doing so may cause internal AbstractUserInterface state update to misbehave.

Expects that activeStorage, startedStorage, stoppedStorage, factorStorage and removeStorage have the same size, which is at least as large as the largest capacity of all animators, and that all animators expose AnimatorFeature::DataAttachment and their AbstractAnimator::layer() matches handle(), in other words that they were passed to assignAnimator(AbstractDataAnimator&) const earlier. Delegates to doAdvanceAnimations(Nanoseconds, Containers::MutableBitArrayView, Containers::MutableBitArrayView, Containers::MutableBitArrayView, const Containers::StridedArrayView1D<Float>&, Containers::MutableBitArrayView, const Containers::Iterable<AbstractDataAnimator>&), see its documentation for more information.

void Magnum::Ui::AbstractLayer::advanceAnimations(Nanoseconds time, Containers::MutableBitArrayView activeStorage, Containers::MutableBitArrayView startedStorage, Containers::MutableBitArrayView stoppedStorage, const Containers::StridedArrayView1D<Float>& factorStorage, Containers::MutableBitArrayView removeStorage, const Containers::Iterable<AbstractStyleAnimator>& animators)

Advance style animations in animators assigned to this layer.

Used internally from AbstractUserInterface::advanceAnimations(). Exposed just for testing purposes, there should be no need to call this function directly and doing so may cause internal AbstractUserInterface state update to misbehave.

Expects that activeStorage, startedStorage, stoppedStorage, factorStorage and removeStorage have the same size, which is at least as large as the largest capacity of all animators, and that all animators expose AnimatorFeature::DataAttachment and their AbstractAnimator::layer() matches handle(), in other words that they were passed to assignAnimator(AbstractStyleAnimator&) const earlier. Delegates to doAdvanceAnimations(Nanoseconds, Containers::MutableBitArrayView, Containers::MutableBitArrayView, Containers::MutableBitArrayView, const Containers::StridedArrayView1D<Float>&, Containers::MutableBitArrayView, const Containers::Iterable<AbstractStyleAnimator>&), see its documentation for more information.

void Magnum::Ui::AbstractLayer::preUpdate(LayerStates state)

Pre-update common and shared layer data.

Used internally from AbstractUserInterface::update(). Exposed just for testing purposes, there should be no need to call this function directly and doing so may cause internal AbstractUserInterface state update to misbehave.

Expects that states isn't empty and is a subset of LayerState::NeedsCommonDataUpdate and NeedsSharedDataUpdate.

Note that, unlike update(), calling this function does not reset LayerStates present in state, that's only done once update() is subsequently called.

void Magnum::Ui::AbstractLayer::update(LayerStates state, const Containers::StridedArrayView1D<const UnsignedInt>& dataIds, const Containers::StridedArrayView1D<const UnsignedInt>& clipRectIds, const Containers::StridedArrayView1D<const UnsignedInt>& clipRectDataCounts, const Containers::StridedArrayView1D<const Vector2>& nodeOffsets, const Containers::StridedArrayView1D<const Vector2>& nodeSizes, const Containers::StridedArrayView1D<const Float>& nodeOpacities, Containers::BitArrayView nodesEnabled, const Containers::StridedArrayView1D<const Vector2>& clipRectOffsets, const Containers::StridedArrayView1D<const Vector2>& clipRectSizes, const Containers::StridedArrayView1D<const Vector2>& compositeRectOffsets, const Containers::StridedArrayView1D<const Vector2>& compositeRectSizes)

Update visible layer data to given offsets and positions.

Used internally from AbstractUserInterface::update(). Exposed just for testing purposes, there should be no need to call this function directly and doing so may cause internal AbstractUserInterface state update to misbehave.

Expects that states isn't empty and is a subset of LayerState::NeedsNodeOffsetSizeUpdate, NeedsNodeOrderUpdate, NeedsNodeEnabledUpdate, NeedsNodeOpacityUpdate, NeedsDataUpdate, NeedsCommonDataUpdate, NeedsSharedDataUpdate and NeedsAttachmentUpdate, and NeedsCompositeOffsetSizeUpdate if the layer advertises LayerFeature::Composite, that the clipRectIds and clipRectDataCounts views have the same size, nodeOffsets, nodeSizes, nodeOpacities and nodesEnabled have the same size, clipRectOffsets and clipRectOffset have the same size and compositeRectOffsets and compositeRectSizes have the same size. If LayerFeature::Composite isn't supported, compositeRectOffsets and compositeRectSizes are expected to be empty. The nodeOffsets, nodeSizes, nodeOpacities and nodesEnabled views should be large enough to contain any valid node ID. If the layer advertises LayerFeature::Draw, expects that setSize() was called at least once before this function. Delegates to doUpdate(), see its documentation for more information about the arguments.

Calling this function resets LayerStates present in state, however note that behavior of this function is independent of state() — it performs the update only based on what's passed in state.

void Magnum::Ui::AbstractLayer::composite(AbstractRenderer& renderer, const Containers::StridedArrayView1D<const Vector2>& rectOffsets, const Containers::StridedArrayView1D<const Vector2>& rectSizes, std::size_t offset, std::size_t count)

Composite previously rendered contents.

Used internally from AbstractUserInterface::draw(). Exposed just for testing purposes, there should be no need to call this function directly. Expects that the layer supports LayerFeature::Composite, that the rectOffsets and rectSizes views have the same size and that offset and count fits into their size. Delegates to doComposite(), see its documentation for more information about the arguments.

void Magnum::Ui::AbstractLayer::draw(const Containers::StridedArrayView1D<const UnsignedInt>& dataIds, std::size_t offset, std::size_t count, const Containers::StridedArrayView1D<const UnsignedInt>& clipRectIds, const Containers::StridedArrayView1D<const UnsignedInt>& clipRectDataCounts, std::size_t clipRectOffset, std::size_t clipRectCount, const Containers::StridedArrayView1D<const Vector2>& nodeOffsets, const Containers::StridedArrayView1D<const Vector2>& nodeSizes, const Containers::StridedArrayView1D<const Float>& nodeOpacities, Containers::BitArrayView nodesEnabled, const Containers::StridedArrayView1D<const Vector2>& clipRectOffsets, const Containers::StridedArrayView1D<const Vector2>& clipRectSizes)

Draw a sub-range of visible layer data.

Used internally from AbstractUserInterface::draw(). Exposed just for testing purposes, there should be no need to call this function directly. Expects that the layer supports LayerFeature::Draw, offset and count fits into dataIds size, that the clipRectIds and clipRectDataCounts views have the same size, nodeOffsets, nodeSizes, nodeOpacities and nodesEnabled have the same size and clipRectOffsets and clipRectOffset have the same size. The nodeOffsets, nodeSizes, nodeOpacities and nodesEnabled views should be large enough to contain any valid node ID. Delegates to doDraw(), see its documentation for more information about the arguments.

void Magnum::Ui::AbstractLayer::pointerPressEvent(UnsignedInt dataId, PointerEvent& event)

Handle a pointer press event.

Used internally from AbstractUserInterface::pointerPressEvent(). Exposed just for testing purposes, there should be no need to call this function directly. Expects that the layer supports LayerFeature::Event and dataId is less than capacity(), with the assumption that the ID points to a valid data and PointerEvent::position() is relative to the node to which the data is attached. The event is expected to not be accepted yet. Delegates to doPointerPressEvent(), see its documentation for more information.

void Magnum::Ui::AbstractLayer::pointerReleaseEvent(UnsignedInt dataId, PointerEvent& event)

Handle a pointer release event.

Used internally from AbstractUserInterface::pointerReleaseEvent(). Exposed just for testing purposes, there should be no need to call this function directly. Expects that the layer supports LayerFeature::Event and dataId is less than capacity(), with the assumption that the ID points to a valid data and PointerEvent::position() is relative to the node to which the data is attached. The event is expected to not be accepted yet. Delegates to doPointerReleaseEvent(), see its documentation for more information.

void Magnum::Ui::AbstractLayer::pointerMoveEvent(UnsignedInt dataId, PointerMoveEvent& event)

Handle a pointer move event.

Used internally from AbstractUserInterface::pointerMoveEvent(). Exposed just for testing purposes, there should be no need to call this function directly. Expects that the layer supports LayerFeature::Event and dataId is less than capacity(), with the assumption that the ID points to a valid data and PointerMoveEvent::position() is relative to the node to which the data is attached. The event is expected to not be accepted yet. Delegates to doPointerMoveEvent(), see its documentation for more information.

void Magnum::Ui::AbstractLayer::pointerEnterEvent(UnsignedInt dataId, PointerMoveEvent& event)

Handle a pointer enter event.

Used internally from AbstractUserInterface::pointerMoveEvent(). Exposed just for testing purposes, there should be no need to call this function directly. Expects that the layer supports LayerFeature::Event and dataId is less than capacity(), with the assumption that the ID points to a valid data and PointerMoveEvent::position() is relative to the node to which the data is attached, and PointerMoveEvent::relativePosition() is a zero vector, given that the event is meant to happen right after another event. The event is expected to be primary and to not be accepted yet. Delegates to doPointerEnterEvent(), see its documentation for more information.

void Magnum::Ui::AbstractLayer::pointerLeaveEvent(UnsignedInt dataId, PointerMoveEvent& event)

Handle a pointer leave event.

Used internally from AbstractUserInterface::pointerMoveEvent(). Exposed just for testing purposes, there should be no need to call this function directly. Expects that the layer supports LayerFeature::Event and dataId is less than capacity(), with the assumption that the ID points to a valid data and PointerMoveEvent::position() is relative to the node to which the data is attached, and PointerMoveEvent::relativePosition() is a zero vector, given that the event is meant to happen right after another event. The event is expected to be primary and to not be accepted yet. Delegates to doPointerLeaveEvent(), see its documentation for more information.

void Magnum::Ui::AbstractLayer::pointerCancelEvent(UnsignedInt dataId, PointerCancelEvent& event)

Handle a pointer cancel event.

Used internally from AbstractUserInterface::pointerPressEvent(), pointerReleaseEvent() and pointerMoveEvent(). Exposed just for testing purposes, there should be no need to call this function directly. Expects that the layer supports LayerFeature::Event and dataId is less than capacity(), with the assumption that the ID points to a valid data. Delegates to doPointerCancelEvent(), see its documentation for more information.

void Magnum::Ui::AbstractLayer::scrollEvent(UnsignedInt dataId, ScrollEvent& event)

Handle a scroll event.

Used internally from AbstractUserInterface::scrollEvent(). Exposed just for testing purposes, there should be no need to call this function directly. Expects that the layer supports LayerFeature::Event and dataId is less than capacity(), with the assumption that the ID points to a valid data and ScrollEvent::position() is relative to the node to which the data is attached. The event is expected to not be accepted yet. Delegates to doScrollEvent(), see its documentation for more information.

void Magnum::Ui::AbstractLayer::focusEvent(UnsignedInt dataId, FocusEvent& event)

Handle a focus event.

Used internally from AbstractUserInterface::focusEvent() and AbstractUserInterface::pointerPressEvent(). Exposed just for testing purposes, there should be no need to call this function directly. Expects that the layer supports LayerFeature::Event and dataId is less than capacity(), with the assumption that the ID points to a valid data. The event is expected to not be accepted yet. Delegates to doFocusEvent(), see its documentation for more information.

void Magnum::Ui::AbstractLayer::blurEvent(UnsignedInt dataId, FocusEvent& event)

Handle a blur event.

Used internally from AbstractUserInterface::focusEvent(), AbstractUserInterface::pointerPressEvent() and AbstractUserInterface::update(). Exposed just for testing purposes, there should be no need to call this function directly. Expects that the layer supports LayerFeature::Event and dataId is less than capacity(), with the assumption that the ID points to a valid data. The event is expected to not be accepted yet. Delegates to doBlurEvent(), see its documentation for more information.

void Magnum::Ui::AbstractLayer::keyPressEvent(UnsignedInt dataId, KeyEvent& event)

Handle a key press event.

Used internally from AbstractUserInterface::keyPressEvent(). Exposed just for testing purposes, there should be no need to call this function directly. Expects that the layer supports LayerFeature::Event and dataId is less than capacity(), with the assumption that the ID points to a valid data. The event is expected to not be accepted yet. Delegates to doKeyPressEvent(), see its documentation for more information.

void Magnum::Ui::AbstractLayer::keyReleaseEvent(UnsignedInt dataId, KeyEvent& event)

Handle a key release event.

Used internally from AbstractUserInterface::keyReleaseEvent(). Exposed just for testing purposes, there should be no need to call this function directly. Expects that the layer supports LayerFeature::Event and dataId is less than capacity(), with the assumption that the ID points to a valid data. The event is expected to not be accepted yet. Delegates to doKeyReleaseEvent(), see its documentation for more information.

void Magnum::Ui::AbstractLayer::textInputEvent(UnsignedInt dataId, TextInputEvent& event)

Handle a text input event.

Used internally from AbstractUserInterface::textInputEvent(). Exposed just for testing purposes, there should be no need to call this function directly. Expects that the layer supports LayerFeature::Event and dataId is less than capacity(), with the assumption that the ID points to a valid data. The event is expected to not be accepted yet. Delegates to doTextInputEvent(), see its documentation for more information.

void Magnum::Ui::AbstractLayer::visibilityLostEvent(UnsignedInt dataId, VisibilityLostEvent& event)

Handle a visibility lost event.

Used internally from AbstractUserInterface::update(). Exposed just for testing purposes, there should be no need to call this function directly. Expects that the layer supports LayerFeature::Event and dataId is less than capacity(), with the assumption that the ID points to a valid data. Delegates to doVisibilityLostEvent(), see its documentation for more information.

bool Magnum::Ui::AbstractLayer::hasUi() const protected

Whether the layer is a part of an user interface instance.

Returns true if the layer has been already passed to AbstractUserInterface::setLayerInstance(), false otherwise. The function isn't public as it's intended to be used only by the layer implementation itself, not user code.

AbstractUserInterface& Magnum::Ui::AbstractLayer::ui() protected

User interface instance the layer is part of.

Expects that the layer has been already passed to AbstractUserInterface::setLayerInstance(). The function isn't public as it's intended to be used only by the layer implementation itself, not user code.

const AbstractUserInterface& Magnum::Ui::AbstractLayer::ui() const protected

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

DataHandle Magnum::Ui::AbstractLayer::create(NodeHandle node = NodeHandle::Null) protected

Create a data.

Parameters
node Node to attach to
Returns New data 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 data. The returned handle can be removed again with remove(). If node is not NodeHandle::Null, directly attaches the created data to given node, equivalent to calling attach().

Calling this function causes LayerState::NeedsDataUpdate to be set. If node is not NodeHandle::Null, causes also LayerState::NeedsAttachmentUpdate and NeedsNodeOffsetSizeUpdate to be set. The subclass is meant to wrap this function in a public API and perform appropriate additional initialization work there.

void Magnum::Ui::AbstractLayer::remove(DataHandle handle) protected

Remove a data.

Expects that handle is valid. After this call, isHandleValid(DataHandle) const returns false for handle. See also remove(LayerDataHandle) which is a simpler operation if the data is already known to belong to this layer.

Calling this function causes LayerState::NeedsDataClean to be set. If handle is attached to a node, calling this function also causes LayerState::NeedsAttachmentUpdate to be set. Other than that, no flag is set to trigger a subsequent cleanNodes() or update() — instead the subclass is meant to wrap this function in a public API and perform appropriate cleanup work directly there.

void Magnum::Ui::AbstractLayer::remove(LayerDataHandle handle) protected

Remove a data assuming it belongs to this layer.

Expects that handle is valid. After this call, isHandleValid(LayerDataHandle) const returns false for handle. See also remove(DataHandle) which additionally checks that the data belongs to this layer.

Calling this function causes LayerState::NeedsDataClean to be set. If handle is attached to a node, calling this function also causes LayerState::NeedsAttachmentUpdate to be set. Other than that, no flag is set to trigger a subsequent cleanNodes() or update() — instead the subclass is meant to wrap this function in a public API and perform appropriate cleanup work directly there.

void Magnum::Ui::AbstractLayer::assignAnimator(AbstractDataAnimator& animator) const protected

Assign a data animator to this layer.

Expects that the layer supports LayerFeature::AnimateData, the animator supports AnimatorFeature::DataAttachment and that given animator wasn't passed to assignAnimator(AbstractDataAnimator&) const on any layer yet. On the other hand, it's possible to assign multiple different animators to the same layer. Saves handle() into AbstractAnimator::layer(), making it possible to call AbstractAnimator::create(Nanoseconds, Nanoseconds, DataHandle, UnsignedInt, AnimationFlags), AbstractAnimator::create(Nanoseconds, Nanoseconds, LayerDataHandle, UnsignedInt, AnimationFlags), AbstractAnimator::attach(AnimationHandle, DataHandle), AbstractAnimator::attach(AnimationHandle, LayerDataHandle), AbstractAnimator::attach(AnimatorDataHandle, DataHandle) and AbstractAnimator::attach(AnimatorDataHandle, LayerDataHandle) and pass the animator to advanceAnimations(Nanoseconds, Containers::MutableBitArrayView, Containers::MutableBitArrayView, Containers::MutableBitArrayView, const Containers::StridedArrayView1D<Float>&, Containers::MutableBitArrayView, const Containers::Iterable<AbstractDataAnimator>&).

A concrete layer implementation is meant to wrap this function in a public API, restricting to a more concrete animator type, in order to be able to safely cast back to that type in doAdvanceAnimations(Nanoseconds, Containers::MutableBitArrayView, Containers::MutableBitArrayView, Containers::MutableBitArrayView, const Containers::StridedArrayView1D<Float>&, Containers::MutableBitArrayView, const Containers::Iterable<AbstractDataAnimator>&).

See assignAnimator(AbstractStyleAnimator&) const for style animators, a corresponding API for an AbstractGenericAnimator is AbstractGenericAnimator::setLayer(), where the animator has the control over a concrete layer type instead.

void Magnum::Ui::AbstractLayer::assignAnimator(AbstractStyleAnimator& animator) const protected

Assign a style animator to this layer.

Expects that the layer supports LayerFeature::AnimateStyles, the animator supports AnimatorFeature::DataAttachment and that given animator wasn't passed to assignAnimator(AbstractStyleAnimator&) const on any layer yet. On the other hand, it's possible to assign multiple different animators to the same layer. Saves handle() into AbstractAnimator::layer(), making it possible to call AbstractAnimator::create(Nanoseconds, Nanoseconds, DataHandle, UnsignedInt, AnimationFlags), AbstractAnimator::create(Nanoseconds, Nanoseconds, LayerDataHandle, UnsignedInt, AnimationFlags), AbstractAnimator::attach(AnimationHandle, DataHandle), AbstractAnimator::attach(AnimationHandle, LayerDataHandle), AbstractAnimator::attach(AnimatorDataHandle, DataHandle) and AbstractAnimator::attach(AnimatorDataHandle, LayerDataHandle) and pass the animator to advanceAnimations(Nanoseconds, Containers::MutableBitArrayView, Containers::MutableBitArrayView, Containers::MutableBitArrayView, const Containers::StridedArrayView1D<Float>&, Containers::MutableBitArrayView, const Containers::Iterable<AbstractStyleAnimator>&).

A concrete layer implementation is meant to wrap this function in a public API, restricting to a more concrete animator type, in order to be able to safely cast back to that type in doAdvanceAnimations(Nanoseconds, Containers::MutableBitArrayView, Containers::MutableBitArrayView, Containers::MutableBitArrayView, const Containers::StridedArrayView1D<Float>&, Containers::MutableBitArrayView, const Containers::Iterable<AbstractStyleAnimator>&).

See assignAnimator(AbstractDataAnimator&) const for data animators, a corresponding API for an AbstractGenericAnimator is AbstractGenericAnimator::setLayer(), where the animator has the control over a concrete layer type instead.

LayerFeatures Magnum::Ui::AbstractLayer::doFeatures() const pure virtual private

Implementation for features()

Note that the value returned by this function is assumed to stay constant during the whole layer lifetime.

LayerStates Magnum::Ui::AbstractLayer::doState() const virtual private

Query layer state.

Called by state() to retrieve additional state bits that might have changed without layer's direct involvement, such as data shared between multiple layers getting modified by another layer. The implementation is expected to return a subset of LayerState::NeedsDataUpdate, NeedsCommonDataUpdate and NeedsSharedDataUpdate, and if the layer advertises LayerFeature::Composite, also LayerState::NeedsCompositeOffsetSizeUpdate.

Default implementation returns an empty set.

void Magnum::Ui::AbstractLayer::doSetSize(const Vector2& size, const Vector2i& framebufferSize) virtual private

Set user interface size.

Parameters
size Size of the user interface to which everything including events is positioned
framebufferSize Size of the window framebuffer

Implementation for setSize(), which is called from AbstractUserInterface::setSize() whenever the UI size or the framebuffer size changes, and from AbstractUserInterface::setLayerInstance() if the UI size is already set at the time the function is called. Called only if LayerFeature::Draw is supported. The implementation is expected to update its internal rendering state such as projection matrices and other framebuffer-related state. If any nodes are already created, the implementation can expect a followup doUpdate() call with up-to-date node positions and sizes.

Default implementation does nothing.

void Magnum::Ui::AbstractLayer::doClean(Containers::BitArrayView dataIdsToRemove) virtual private

Clean no longer valid layer data.

Parameters
dataIdsToRemove Data IDs to remove

Implementation for cleanNodes(), which is called from AbstractUserInterface::clean() (and transitively from AbstractUserInterface::update()) whenever UserInterfaceState::NeedsNodeClean or any of the states that imply it are present in AbstractUserInterface::state().

The dataIdsToRemove view has the same size as capacity() and is guaranteed to have bits set only for valid data IDs, i.e. data IDs that are already removed are not set.

This function may get also called with dataIdsToRemove having all bits zero.

Default implementation does nothing.

void Magnum::Ui::AbstractLayer::doAdvanceAnimations(Nanoseconds time, Containers::MutableBitArrayView activeStorage, Containers::MutableBitArrayView startedStorage, Containers::MutableBitArrayView stoppedStorage, const Containers::StridedArrayView1D<Float>& factorStorage, Containers::MutableBitArrayView removeStorage, const Containers::Iterable<AbstractDataAnimator>& animators) virtual private

Advance data animations in animators assigned to this layer.

Parameters
time in Time to which to advance
activeStorage in/out Storage for animators to put a mask of active animations into
startedStorage in/out Storage for animators to put a mask of started animations into
stoppedStorage in/out Storage for animators to put a mask of stopped animations into
factorStorage in/out Storage for animators to put animation interpolation factors into
removeStorage in/out Storage for animators to put a mask of animations to remove into
animators in Animators to advance

Implementation for advanceAnimations(), which is called from AbstractUserInterface::advanceAnimations() whenever UserInterfaceState::NeedsAnimationAdvance is present in AbstractUserInterface::state() and there are animators assigned to given layer.

The activeStorage, startedStorage, stoppedStorage, factorStorage and removeStorage views are guaranteed to be at least as large as the largest capacity of all animators. The animators are all guaranteed to support AnimatorFeature::DataAttachment, with their AbstractAnimator::layer() matching handle(), in other words that they were passed to assignAnimator(AbstractDataAnimator&) const earlier.

For each animator in animators the implementation is expected to call AbstractAnimator::update() to fill a correctly-sized slice of activeStorage, factorStorage and removeStorage; then, if the returned value says advance is needed, pass the slices of activeStorage, factorStorage and removeStorage to the animator-specific advance function; and then, if the returned value says clean is needed, pass the slice of removeStorage to AbstractAnimator::clean().

Assuming the layer implementation publicizes assignAnimator(AbstractDataAnimator&) const with a restricted type, the animators can then be safely cast back to that type in order to call a concrete layer-specific advance function.

void Magnum::Ui::AbstractLayer::doAdvanceAnimations(Nanoseconds time, Containers::MutableBitArrayView activeStorage, Containers::MutableBitArrayView startedStorage, Containers::MutableBitArrayView stoppedStorage, const Containers::StridedArrayView1D<Float>& factorStorage, Containers::MutableBitArrayView removeStorage, const Containers::Iterable<AbstractStyleAnimator>& animators) virtual private

Advance style animations in animators assigned to this layer.

Parameters
time in Time to which to advance
activeStorage in/out Storage for animators to put a mask of active animations into
startedStorage in/out Storage for animators to put a mask of started animations into
stoppedStorage in/out Storage for animators to put a mask of stopped animations into
factorStorage in/out Storage for animators to put animation interpolation factors into
removeStorage in/out Storage for animators to put a mask of animations to remove into
animators in Animators to advance

Implementation for advanceAnimations(), which is called from AbstractUserInterface::advanceAnimations() whenever UserInterfaceState::NeedsAnimationAdvance is present in AbstractUserInterface::state() and there are animators assigned to given layer.

The activeStorage, startedStorage, stoppedStorage, factorStorage and removeStorage views are guaranteed to be at least as large as the largest capacity of all animators. The animators are all guaranteed to support AnimatorFeature::DataAttachment, with their AbstractAnimator::layer() matching handle(), in other words that they were passed to assignAnimator(AbstractStyleAnimator&) const earlier.

For each animator in animators the implementation is expected to call AbstractAnimator::update() to fill a correctly-sized slice of activeStorage, factorStorage and removeStorage; then, if the returned value says advance is needed, pass the slices of activeStorage, factorStorage and removeStorage to the animator-specific advance function; and then, if the returned value says clean is needed, pass the slice of removeStorage to AbstractAnimator::clean().

Assuming the layer implementation publicizes assignAnimator(AbstractStyleAnimator&) const with a restricted type, the animators can then be safely cast back to that type in order to call a concrete layer-specific advance function.

void Magnum::Ui::AbstractLayer::doPreUpdate(LayerStates state) virtual private

Pre-update common and shared layer data.

Parameters
state State that's needed to be updated

Implementation for preUpdate(), which is called from AbstractUserInterface whenever UserInterfaceState::NeedsDataUpdate or any of the global or layer-specific states that imply it are present in AbstractUserInterface::state(). Is always called after doClean() and before doUpdate(), doComposite() and doDraw(), with at least one doSetSize() call happening at some point before. In case the function performs operations that trigger UserInterfaceState::NeedsDataClean, another doClean() is executed right after calling this function. This function is always called before any internal operations that calculate the set of visible nodes and data, meaning this function can be used not just for updating common and shared layer data, but also for example for data addition or removal.

The state is guaranteed to be a subset of LayerState::NeedsCommonDataUpdate and NeedsSharedDataUpdate and the implementation can make use of this information to skip some of its internal update logic. It can however also update everything always. Note that the doUpdate() function gets called every time this function is called, with a superset of state, so the layer can choose to not implement this function at all if relevant updates can be done during the regular doUpdate() as well.

Default implementation does nothing.

void Magnum::Ui::AbstractLayer::doUpdate(LayerStates state, const Containers::StridedArrayView1D<const UnsignedInt>& dataIds, const Containers::StridedArrayView1D<const UnsignedInt>& clipRectIds, const Containers::StridedArrayView1D<const UnsignedInt>& clipRectDataCounts, const Containers::StridedArrayView1D<const Vector2>& nodeOffsets, const Containers::StridedArrayView1D<const Vector2>& nodeSizes, const Containers::StridedArrayView1D<const Float>& nodeOpacities, Containers::BitArrayView nodesEnabled, const Containers::StridedArrayView1D<const Vector2>& clipRectOffsets, const Containers::StridedArrayView1D<const Vector2>& clipRectSizes, const Containers::StridedArrayView1D<const Vector2>& compositeRectOffsets, const Containers::StridedArrayView1D<const Vector2>& compositeRectSizes) virtual private

Update visible layer data to given offsets and positions.

Parameters
state State that's needed to be updated
dataIds Data IDs to update, in order that matches the draw order
clipRectIds IDs of clip rects to use for dataIds
clipRectDataCounts Counts of dataIds to use for each clip rect from clipRectIds
nodeOffsets Absolute node offsets indexed by node ID
nodeSizes Node sizes indexed by node ID
nodeOpacities Absolute node opacities (i.e., inheriting parent node opacities as well) indexed by node ID
nodesEnabled Which visible nodes are enabled, i.e. which don't have NodeFlag::Disabled set on themselves or any parent node
clipRectOffsets Absolute clip rect offsets referenced by clipRectIds
clipRectSizes Clip rect sizes referenced by clipRectIds
compositeRectOffsets Offsets of framebuffer rectangles to composite
compositeRectSizes Sizes of framebuffer rectangles to composite

Implementation for update(), which is called from AbstractUserInterface::update() whenever UserInterfaceState::NeedsDataUpdate or any of the global or layer-specific states that imply it are present in AbstractUserInterface::state(). Is always called after doPreUpdate() and doClean(), and before doComposite() and doDraw(), with at least one doSetSize() call happening at some point before.

The state is guaranteed to be a subset of LayerState::NeedsNodeOffsetSizeUpdate, NeedsNodeOrderUpdate, NeedsNodeEnabledUpdate, NeedsDataUpdate, NeedsCommonDataUpdate and NeedsSharedDataUpdate and the implementation can make use of this information to skip some of its internal update logic. It can however also update everything always. The LayerState::NeedsAttachmentUpdate flag isn't passed through from update() as it's only meant to be used by the layer to signalize a state update to AbstractUserInterface, not the other way around.

Node handles corresponding to dataIds are available in nodes(), node IDs can be then extracted from the handles using nodeHandleId(). The node IDs then index into the nodeOffsets, nodeSizes, nodeOpacities and nodesEnabled views. The nodeOffsets, nodeSizes, nodeOpacities and nodesEnabled have the same size and are guaranteed to be large enough to contain any valid node ID.

All nodes() at indices corresponding to dataIds are guaranteed to not be NodeHandle::Null at the time this function is called. The nodeOffsets, nodeSizes, nodeOpacities and nodesDisabled arrays may contain random or uninitialized values for nodes different than those referenced from dataIds, such as for nodes that are not currently visible or freed node handles.

The node data are meant to be clipped by rects defined in clipRectOffsets and clipRectSizes. The clipRectIds and clipRectDataCounts have the same size and specify which of these rects is used for which data. For example, a sequence of {3, 2}, {0, 4}, {1, 7} means clipping the first two data with clip rect 3, then the next four data with clip rect 0 and then the next seven data with clip rect 1. The sum of all clipRectDataCounts is equal to the size of the dataIds array. The clipRectOffsets and clipRectSizes have the same size and are guaranteed to be large enough to contain any ID from clipRectIds. They're in the same coordinate system as nodeOffsets and nodeSizes, a zero offset and a zero size denotes that no clipping is needed. Tt's up to the implementation whether it clips the actual data directly or whether it performs clipping at draw time.

The compositeRectOffsets and compositeRectOffsets have the same size and define rectangles to be used by compositing operations, i.e. intersections of node rectangles with corresponding clip rectangles. If the layer doesn't advertise LayerFeature::Composite, the views are empty.

This function may get also called with dataIds being empty, for example when setNeedsUpdate() was called but the layer doesn't have any data currently visible.

Default implementation does nothing. Data passed to this function are subsequently passed to doComposite() / doDraw() calls as well, the only difference is that doUpdate() gets called just once with all data to update, while doComposite() / doDraw() is called several times with different sub-ranges of the data based on desired draw order.

void Magnum::Ui::AbstractLayer::doComposite(AbstractRenderer& renderer, const Containers::StridedArrayView1D<const Vector2>& compositeRectOffsets, const Containers::StridedArrayView1D<const Vector2>& compositeRectSizes, std::size_t offset, std::size_t count) virtual private

Composite previously rendered contents.

Parameters
renderer Renderer instance containing the previously rendered contents
compositeRectOffsets Offsets of framebuffer rectangles to composite. Same as the view passed to doUpdate() earlier.
compositeRectSizes Sizes of framebuffer rectangles to composite. Same as the view passed to doUpdate() earlier.
offset Offset into compositeRectOffsets and compositeRectSizes
count Count of compositeRectOffsets and compositeRectSizes to composite

Implementation for composite(), which is called from AbstractUserInterface::draw(). Called only if LayerFeature::Composite is supported, it's guaranteed that doUpdate() was called at some point before this function with the exact same views passed to compositeRectOffsets and compositeRectSizes, see its documentation for their relations and constraints. This function is called after drawing contents of all layers earlier in the top-level node and layer draw order. It's guaranteed that doDraw() will get called after this function.

This function usually gets called several times with the same views but different offset and count. The range of compositeRectOffsets and compositeRectSizes views defined by offset and count contains rectangles that correspond to the layer data and can be used to restrict the compositing operation to only the area that's actually subsequently drawn.)

void Magnum::Ui::AbstractLayer::doDraw(const Containers::StridedArrayView1D<const UnsignedInt>& dataIds, std::size_t offset, std::size_t count, const Containers::StridedArrayView1D<const UnsignedInt>& clipRectIds, const Containers::StridedArrayView1D<const UnsignedInt>& clipRectDataCounts, std::size_t clipRectOffset, std::size_t clipRectCount, const Containers::StridedArrayView1D<const Vector2>& nodeOffsets, const Containers::StridedArrayView1D<const Vector2>& nodeSizes, const Containers::StridedArrayView1D<const Float>& nodeOpacities, Containers::BitArrayView nodesEnabled, const Containers::StridedArrayView1D<const Vector2>& clipRectOffsets, const Containers::StridedArrayView1D<const Vector2>& clipRectSizes) virtual private

Draw a sub-range of visible layer data.

Parameters
dataIds Data IDs to update, in order that matches the draw order. Same as the view passed to doUpdate() earlier.
offset Offset into dataIds
count Count of dataIds to draw
clipRectIds IDs of clip rects to use for dataIds. Same as the view passed to doUpdate() earlier.
clipRectDataCounts Counts of dataIds to use for each clip rect from clipRectIds. Same as the view passed to doUpdate() earlier.
clipRectOffset Offset into clipRectIds and clipRectDataCounts
clipRectCount Count of clipRectIds and clipRectDataCounts to use
nodeOffsets Absolute node offsets. Same as the view passed to doUpdate() earlier.
nodeSizes Node sizes. Same as the view passed to doUpdate() earlier.
nodeOpacities Absolute node opacities. Same as the view passed to doUpdate() earlier.
nodesEnabled Which visible nodes are enabled, i.e. which don't have NodeFlag::Disabled set on themselves or any parent node. Same as the view passed to doUpdate() earlier.
clipRectOffsets Absolute clip rect offsets. Same as the view passed to doUpdate() earlier.
clipRectSizes Clip rect sizes. Same as the view passed to doUpdate() earlier.

Implementation for draw(), which is called from AbstractUserInterface::draw(). Called only if LayerFeature::Draw is supported, it's guaranteed that doUpdate() was called at some point before this function with the exact same views passed to dataIds, clipRectIds, clipRectDataCounts, nodeOffsets, nodeSizes, nodeOpacities, nodesEnabled, clipRectOffsets and clipRectSizes, see its documentation for their relations and constraints. If LayerFeature::Composite is supported as well, it's guaranteed that doComposite() was called before this function and at some point after doUpdate(), and after drawing contents of all layers earlier in the top-level node and layer draw order.

Like with doUpdate(), the clipRectOffsets and clipRectSizes are in the same coordinate system as nodeOffsets and nodeSizes. If performing the clipping at draw time (instead of clipping the actual data directly in doDraw()), the implementation may need to scale these to match actual framebuffer pixels, i.e. by multiplying them with Vector2{framebufferSize}/size inside doSetSize().

This function usually gets called several times with the same views but different offset, count, clipRectOffset and clipRectCount values in order to interleave the draws for a correct back-to-front order. In each call, the sum of all clipRectDataCounts in the range given by clipRectOffset and clipRectCount is equal to count. Unlike doUpdate() or doClean(), this function is never called with an empty count.

void Magnum::Ui::AbstractLayer::doPointerPressEvent(UnsignedInt dataId, PointerEvent& event) virtual private

Handle a pointer press event.

Parameters
dataId Data ID the event happens on. Guaranteed to be less than capacity() and point to a valid data.
event Event data, with PointerEvent::position() relative to the node to which the data is attached. If pointer event capture is active and the event is not primary, the position can be outside of the area of the node.

Implementation for pointerPressEvent(), which is called from AbstractUserInterface::pointerPressEvent(). See its documentation for more information about pointer event behavior, especially event capture. It's guaranteed that doUpdate() was called before this function with up-to-date data for dataId.

If the implementation handles the event, it's expected to call PointerEvent::setAccepted() on it to prevent it from being propagated further. If the event is primary, pointer capture is implicitly performed on a node that accepts this event. Call PointerEvent::setCaptured() to disable it or to adjust the behavior in case of a non-primary event.

Default implementation does nothing, i.e. the event gets implicitly propagated further.

void Magnum::Ui::AbstractLayer::doPointerReleaseEvent(UnsignedInt dataId, PointerEvent& event) virtual private

Handle a pointer release event.

Parameters
dataId Data ID the event happens on. Guaranteed to be less than capacity() and point to a valid data.
event Event data, with PointerEvent::position() relative to the node to which the data is attached. If pointer event capture is active, the position can be outside of the area of the node.

Implementation for pointerReleaseEvent(), which is called from AbstractUserInterface::pointerReleaseEvent(). See its documentation for more information about pointer event behavior, especially event capture. It's guaranteed that doUpdate() was called before this function with up-to-date data for dataId.

If the implementation handles the event, it's expected to call PointerEvent::setAccepted() on it to prevent it from being propagated further. If the event is primary, pointer capture is implicitly released after, thus calling PointerEvent::setCaptured() only has an effect for non-primary events.

Default implementation does nothing, i.e. the event gets implicitly propagated further.

void Magnum::Ui::AbstractLayer::doPointerMoveEvent(UnsignedInt dataId, PointerMoveEvent& event) virtual private

Handle a pointer move event.

Parameters
dataId Data ID the event happens on. Guaranteed to be less than capacity() and point to a valid data.
event Event data, with PointerMoveEvent::position() relative to the node to which the data is attached. If pointer event capture is active, the position can be outside of the area of the node.

Implementation for pointerMoveEvent(), which is called from AbstractUserInterface::pointerMoveEvent(). See its documentation for more information about pointer event behavior, especially event capture, hover and relation to doPointerEnterEvent() and doPointerLeaveEvent(). It's guaranteed that doUpdate() was called before this function with up-to-date data for dataId.

If the implementation handles the event, it's expected to call PointerEvent::setAccepted() on it to prevent it from being propagated further. Pointer capture behavior for remaining pointer events can be changed using PointerMoveEvent::setCaptured().

Default implementation does nothing, i.e. the event gets implicitly propagated further. That also implies the node is never marked as hovered for primary events and enter / leave events are not emitted for it.

void Magnum::Ui::AbstractLayer::doPointerEnterEvent(UnsignedInt dataId, PointerMoveEvent& event) virtual private

Handle a pointer enter event.

Parameters
dataId Data ID the event happens on. Guaranteed to be less than capacity() and point to a valid data.
event Event data, with PointerMoveEvent::position() relative to the node to which the data is attached. If pointer event capture is active, the position can be outside of the area of the node.

Implementation for pointerEnterEvent(), which is called from AbstractUserInterface::pointerMoveEvent() if the currently hovered node changed to one containing dataId. See its documentation for more information about relation of pointer enter/leave events to doPointerMoveEvent(). It's guaranteed that doUpdate() was called before this function with up-to-date data for dataId, the event is guaranteed to be always primary.

Unlike doPointerMoveEvent(), the accept status is ignored for enter and leave events, as the event isn't propagated anywhere if it's not handled. Thus calling PointerEvent::setAccepted() has no effect here. On the other hand, pointer capture behavior for remaining pointer events can be changed using PointerMoveEvent::setCaptured() here as well.

Default implementation does nothing.

void Magnum::Ui::AbstractLayer::doPointerLeaveEvent(UnsignedInt dataId, PointerMoveEvent& event) virtual private

Handle a pointer leave event.

Parameters
dataId Data ID the event happens on. Guaranteed to be less than capacity() and point to a valid data.
event Event data, with PointerMoveEvent::position() relative to the node to which the data is attached. If pointer event capture is active, the position can be outside of the area of the node.

Implementation for pointerEnterEvent(), which is called from AbstractUserInterface::pointerMoveEvent() if the currently hovered node changed away from one containing dataId. See its documentation for more information about relation of pointer enter/leave events to doPointerMoveEvent(). It's guaranteed that doUpdate() was called before this function with up-to-date data for dataId, the event is guaranteed to be always primary.

Unlike doPointerMoveEvent(), the accept status is ignored for enter and leave events, as the event isn't propagated anywhere if it's not handled. Thus calling PointerEvent::setAccepted() has no effect here. On the other hand, pointer capture behavior for remaining pointer events can be changed using PointerMoveEvent::setCaptured() here as well.

Default implementation does nothing.

void Magnum::Ui::AbstractLayer::doPointerCancelEvent(UnsignedInt dataId, PointerCancelEvent& event) virtual private

Handle a pointer cancel event.

Parameters
dataId Data ID the event happens on. Guaranteed to be less than capacity() and point to a valid data.
event Event data

Implementation for pointerCancelEvent(), which is called from AbstractUserInterface::pointerPressEvent(), pointerReleaseEvent() or pointerMoveEvent() if the event falls through and gets accepted by a node different than the one it originally happened on. It's guaranteed that doUpdate() was called before this function with up-to-date data for dataId.

Default implementation does nothing.

void Magnum::Ui::AbstractLayer::doScrollEvent(UnsignedInt dataId, ScrollEvent& event) virtual private

Handle a scroll event.

Parameters
dataId Data ID the event happens on. Guaranteed to be less than capacity() and point to a valid data.
event Event data, with ScrollEvent::position() relative to the node to which the data is attached. If pointer event capture is active, the position can be outside of the area of the node.

Implementation for scrollEvent(), which is called from AbstractUserInterface::scrollEvent(). See its documentation for more information about pointer event behavior, especially event capture. It's guaranteed that doUpdate() was called before this function with up-to-date data for dataId.

If the implementation handles the event, it's expected to call ScrollEvent::setAccepted() on it to prevent it from being propagated further.

Default implementation does nothing, i.e. the event gets implicitly propagated further.

void Magnum::Ui::AbstractLayer::doFocusEvent(UnsignedInt dataId, FocusEvent& event) virtual private

Handle a focus event.

Parameters
dataId Data ID the event happens on. Guaranteed to be less than capacity() and point to a valid data.
event Event data

Implementation for focusEvent(), which is called from AbstractUserInterface::focusEvent() and AbstractUserInterface::pointerPressEvent(). See their documentation for more information about focus and blur event behavior. It's guaranteed that doUpdate() was called before this function with up-to-date data for dataId.

If the implementation handles the event, it's expected to call FocusEvent::setAccepted() on it to cause the node to be marked as focused. If it doesn't, the node doesn't get marked as focused and depending on what AbstractUserInterface API the event originated in, the previously focused node stays or gets blurred.

Default implementation does nothing.

void Magnum::Ui::AbstractLayer::doBlurEvent(UnsignedInt dataId, FocusEvent& event) virtual private

Handle a blur event.

Parameters
dataId Data ID the event happens on. Guaranteed to be less than capacity() and point to a valid data.
event Event data

Implementation for blurEvent(), which is called from AbstractUserInterface::focusEvent() and AbstractUserInterface::pointerPressEvent(). See their documentation for more information about focus and blur event behavior. It's guaranteed that doUpdate() was called before this function with up-to-date data for dataId.

Unlike doFocusEvent(), the accept status is ignored for blur events, as the node is still unmarked as focused if the event is not handled. Thus calling FocusEvent::setAccepted() has no effect here.

Default implementation does nothing.

void Magnum::Ui::AbstractLayer::doKeyPressEvent(UnsignedInt dataId, KeyEvent& event) virtual private

Handle a key press event.

Parameters
dataId Data ID the event happens on. Guaranteed to be less than capacity() and point to a valid data.
event Event data

Implementation for keyPressEvent(), which is called from AbstractUserInterface::keyPressEvent(). See its documentation for more information about key event behavior. It's guaranteed that doUpdate() was called before this function with up-to-date data for dataId.

If the implementation handles the event, it's expected to call KeyEvent::setAccepted() on it to prevent it from being propagated further.

Default implementation does nothing, i.e. the event gets implicitly propagated further.

void Magnum::Ui::AbstractLayer::doKeyReleaseEvent(UnsignedInt dataId, KeyEvent& event) virtual private

Handle a pointer release event.

Parameters
dataId Data ID the event happens on. Guaranteed to be less than capacity() and point to a valid data.
event Event data

Implementation for keyReleaseEvent(), which is called from AbstractUserInterface::keyReleaseEvent(). See its documentation for more information. It's guaranteed that doUpdate() was called before this function with up-to-date data for dataId.

If the implementation handles the event, it's expected to call KeyEvent::setAccepted() on it to prevent it from being propagated further.

Default implementation does nothing, i.e. the event gets implicitly propagated further.

void Magnum::Ui::AbstractLayer::doTextInputEvent(UnsignedInt dataId, TextInputEvent& event) virtual private

Handle a text input event.

Parameters
dataId Data ID the event happens on. Guaranteed to be less than capacity() and point to a valid data.
event Event data

Implementation for textInputEvent(), which is called from AbstractUserInterface::textInputEvent(). See its documentation for more information about text input event behavior. It's guaranteed that doUpdate() was called before this function with up-to-date data for dataId.

If the implementation handles the event, it's expected to call TextInputEvent::setAccepted() on it to prevent it from being propagated further.

Default implementation does nothing, i.e. the event gets implicitly propagated further.

void Magnum::Ui::AbstractLayer::doVisibilityLostEvent(UnsignedInt dataId, VisibilityLostEvent& event) virtual private

Handle a visibility lost event.

Parameters
dataId Data ID the event happens on. Guaranteed to be less than capacity() and point to a valid data.
event Event data

Implementation for visibilityLostEvent(), which is called from AbstractUserInterface::update() if the currently hovered, pressed, captured or focused node containing dataId can no longer receive events due to NodeFlag::Hidden, NodeFlag::NoEvents or NodeFlag::Disabled being set on the node or any of its parents, or if a currently focused node is no longer NodeFlag::Focusable.

Default implementation does nothing.