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

Debug layer.

Provides a non-intrusive and extensible way to inspect node hierarchy and layer data attachments in any existing UI for debugging purposes. You can use either the DebugLayer base for inspection alone, or the DebugLayerGL subclass that provides also a way to visualize highlighted nodes directly in the inspected UI.

Setting up a debug layer instance

A debug layer, either DebugLayer or DebugLayerGL, is constructed using a fresh AbstractUserInterface::createLayer() handle, a combination of DebugLayerSource values that the debug layer should track and a DebugLayerFlag combination describing the actual behavior; and is subsequently passed to AbstractUserInterface::setLayerInstance(). For correct behavior it should be added as the very last layer so it's drawn on top of all other layers and reacts to events first.

Ui::DebugLayer& debugLayer = ui.setLayerInstance(
    Containers::pointer<Ui::DebugLayerGL>(ui.createLayer(),
        Ui::DebugLayerSource::NodeHierarchy|
        Ui::DebugLayerSource::NodeData,
        Ui::DebugLayerFlag::NodeInspect));

With this, assuming AbstractUserInterface::draw() is called in an appropriate place, the layer is ready to use and it will track changes in enabled sources without further involvement. In case of the base DebugLayer that doesn't draw, or when inspecting the UI programmatically, calling just AbstractUserInterface::update() (which is otherwise called from draw()) is enough to make it aware of latest state changes.

Inspecting nodes

The setup shown above, in particular with DebugLayerFlag::NodeInspect together with at least DebugLayerSource::Nodes enabled, makes it possible to highlight any node in the hierarchy and see its details. NodeOffsetSize lists raw node offset and size before any layouters are run, NodeHierarchy additionally shows info about parent and child nodes and NodeData also lists data attachments. Let's say we have a Ui::Button placed somewhere in the UI, reacting to a tap or click:

Ui::NodeHandle button = Ui::button(, Ui::Icon::Yes, "Accept");

ui.eventLayer().onTapOrClick(button, []{
    
});
Image

With the DebugLayer set up, clicking on this button with Ctrl right mouse button (or Ctrl pen eraser in case of a pen input) highlights the node, showing a magenta rectangle over, and prints details about it to the console like shown below. Clicking on any other node will inspect that one instead, clicking again on the inspected node will remove the highlight.

Image
Node {0x9, 0x2}
  Nested at level 1 with 0 direct children
  4 data from 3 layers

With DebugLayerSource::NodeLayouts and NodeAnimations you can list also layouts and animations attached to particular nodes. The Ui::Button doesn't have any by default. Finally, DebugLayerFlag::NodeInspectSkipNoData makes the click ignore nodes with no data attachments, which is useful if the UI contains invisible nodes that otherwise make it impossible to inspect what's underneath.

Naming nodes, layers, layouters and animators

In the details we can see that the node is placed somewhere and it has four data attachments. Because the widget is simple we can assume it's the background, the icon, the text and the event handler. It would be better if the layer could tell us that, but because naming various resources isn't essential to UI functionality, and because the DebugLayer is designed to work with any custom user interface containing any custom layers, not just the builtin ones, it can't have any knowledge about layer names on its own.

We have to supply those with setLayerName(). In this case we'll name all layers exposed on the UserInterface instance, you can do the same for any custom layer as well. Similarly, setNodeName() allows to assign names to particular nodes.

debugLayer.setLayerName(ui.eventLayer(), "Event");
debugLayer.setLayerName(ui.baseLayer(), "Base");
debugLayer.setLayerName(ui.textLayer(), "Text");
debugLayer.setNodeName(button, "Accept button");

Inspecting the same node then groups the data by layer, showing them in the order they're drawn. Besides the names now being listed in the printed details, you can query them back with layerName() and nodeName().

Node {0x9, 0x2} Accept button
  Nested at level 1 with 0 direct children
  1 data from layer {0x0, 0x1} Base
  2 data from layer {0x1, 0x1} Text
  1 data from layer {0x2, 0x1} Event

Similarly to layers, setLayouterName() and setAnimatorName() can name layouters and animators.

Showing details about node data, layouts and animations

Enabling DebugLayerSource::NodeDataDetails in addition to NodeData makes use of debug integration implemented by a particular layer. In case of BaseLayer, TextLayer and other layers derived from AbstractVisualLayer this makes the output show also style assignment as well as any style transitions, if present. In case of EventLayer it shows the event that's being handled:

Node {0x9, 0x2} Accept button
  Nested at level 1 with 0 direct children
  Data {0x3, 0x2} from layer {0x0, 0x1} Base
    Inactive out style: 1
    Inactive over style: 2
    Pressed out style: 3
    Pressed over style: 4
    Disabled style: 5
  Data {0x6, 0x1} from layer {0x1, 0x1} Text
    Inactive style: 3
    Pressed style: 7
    Disabled style: 11
  Data {0x7, 0x1} from layer {0x1, 0x1} Text
    Inactive style: 4
    Pressed style: 8
    Disabled style: 12
  Data {0x0, 0x1} from layer {0x2, 0x1} Event reacting to tap or click

The way this works is that by passing a concrete layer type, the setLayerName(const T&, const Containers::StringView&) overload gets picked if the type contains an AbstractLayer::DebugIntegration inner class. Instance of which then gets used to print additional details. The integration can take various optional parameters, such as a function to provide style names in case of visual layers. Documentation of all builtin layers has information about what's provided in their debug integration. It's also possible to implement this integration for custom layers, see DebugLayer integration for custom layers, layouters and animators below for details.

With DebugLayerSource::NodeLayoutDetails, a similar functionality is available for layouters that implement a AbstractLayouter::DebugIntegration inner class. Then, DebugLayerSource::NodeAnimationDetails enables this for for animators that implement a AbstractAnimator::DebugIntegration inner class, which is the case for example with NodeAnimator.

Node inspect options

Node inspect has defaults chosen in a way that makes the highlight clearly visible on most backgrounds, and with the pointer gesture unique enough to not conflict with usual event handlers. You can change both with setNodeInspectColor() and setNodeInspectGesture().

The default set of accepted gestures does not include touch input however, as on a touch device it usually isn't possible to press any modifier keys to distinguish a "debug tap" from a regular tap. It's however possible to use addFlags() and clearFlags() to enable DebugLayerFlag::NodeInspect only temporarily and during that time accept touches without any modifiers, for example in a response to some action in the UI that enables some sort of a debug mode:

/* Enable node inspection with just a touch */
debugLayer
    .addFlags(Ui::DebugLayerFlag::NodeInspect)
    .setNodeInspectGesture(Ui::Pointer::Finger, {});



/* Disable it again and revert to a safe gesture when not used anymore */
debugLayer
    .clearFlags(Ui::DebugLayerFlag::NodeInspect)
    .setNodeInspectGesture(Ui::Pointer::MouseRight, Ui::Modifier::Ctrl);

Besides visual inspection using a pointer, inspectNode() allows to inspect a node programmatically.

Finally, while the inspected node details are by default printed to Debug, a console might not be always accessible. In that case you can direct the message to a callback using setNodeInspectCallback(), and populate for example a Label directly in the UI with it instead. The callback also gets called with an empty string when the highlight is removed, which can be used to hide the label again. For example:

Ui::Label details{};



debugLayer.setNodeInspectCallback([&details](Containers::StringView message) {
    details
        .setText(message)
        .setHidden(!message);
});

Limitations

Currently, it's only possible to visually inspect nodes that are visible and are neither NodeFlag::Disabled nor NodeFlag::NoEvents, To help with their discovery a bit at least, clicking their (event-accepting) parent will list how many children are hidden, disabled or not accepting events. Inspecting such nodes is only possible by passing their handle to inspectNode().

Additionally, if a top-level node covers other nodes but is otherwise invisible and doesn't react to events in any way, with DebugLayerFlag::NodeInspect it will become inspectable and it won't be possible to inspect any nodes underneath. Presence of such a node in the UI is usually accidental, currently the workaround is to either restrict its size to cover only the necessary area, move it behind other nodes in the top-level node hierarchy or mark it with NodeFlag::NoEvents if it doesn't have children that need to react to events.

Highlighting nodes

Besides inspecting a single individual node to see details about what's all attached to it, it's possible to programmatically highlight nodes in the UI by passing their handle to highlightNode(). This is useful for example in case you have a node in the code and want to see where it appears in the UI. In the following snippet, the button from above would get highlighted like this, resulting in it having a cyan overlay. The function returns false in case given node handle is unknown to the debug layer, which you can use for various sanity checks:

CORRADE_INTERNAL_ASSERT(debugLayer.highlightNode(button));
Image

The highlights are additive, meaning additional calls to highlightNode() will highlight more nodes. The set of currently highlighted node IDs can be queried with currentHighlightedNodes() and all highlights cleared with clearHighlightedNodes(). To better distinguish multiple highlighted nodes from each other, a color map can be supplied with setNodeHighlightColorMap(), where different colors get used based on node ID. The DebugTools::ColorMap namespace contains several builtin ones or you can supply any arbitrary array of 8-bit colors:

debugLayer.setNodeHighlightColorMap(DebugTools::ColorMap::turbo());

The highlightNodes() overloads then allow highlighting nodes based on various conditions either on the nodes themselves or on data, layouts and animations attached to those. Each of these overloads accepts a function which if returns true, causes given node (or node given data / layout / animation is attached to) to be highlighted. For example, highlighting all top-level nodes in the UI to discover unnecessary extra draw calls:

debugLayer.highlightNodes(
    [](const Ui::AbstractUserInterface& ui, Ui::NodeHandle node) {
        return ui.isNodeTopLevel(node);
    });

Or highlighting all nodes that have base and text data with custom colors, to discover pieces of the UI that might have their styling hardcoded. The highlights will be combined from these two calls:

debugLayer.highlightNodes(ui.baseLayer(),
    [](const Ui::BaseLayer& layer, Ui::LayerDataHandle data) {
        return layer.color(data) != Color4{1.0f};
    });
debugLayer.highlightNodes(ui.textLayer(),
    [](const Ui::TextLayer& layer, Ui::LayerDataHandle data) {
        return layer.color(data) != Color4{1.0f};
    });

Node highlight is orthogonal to node inspection, so if you have DebugLayerFlag::NodeInspect enabled as well, you can then inspect the highlighted nodes one by one. Inspection has a priority over the highlight, so it displays a magenta overlay instead, and when it's removed, the original highlight is shown on given node again.

Making the DebugLayer opt-in

As mentioned above, having the layer always present may have unintended performance implications, and a node highlight can be confusing if triggered accidentally by an unsuspecting user. One possibility is to create it only with a certain startup option, such as is the case with --debug in magnum-ui-gallery.

Another way is to create it with no DebugLayerFlag present, and enable them temporarily only if some debug mode is activated, similarly to what was shown for touch input above. Such setup will however still make it track all enabled DebugLayerSource bits all the time. To avoid this, it's also possible to defer the layer creation and setup to the point when it's actually needed, and then destroy it again after. Note that, as certain options involve iterating the whole node tree, with very complex UIs such on-the-fly setup might cause stalls.

DebugLayer integration for custom layers, layouters and animators

To make a custom layer provide detailed info for DebugLayerSource::NodeDataDetails, implement an inner type named DebugIntegration containing at least a print() function. In the following snippet, a layer that exposes per-data color has the color printed in the DebugLayerFlag::NodeInspect output. To make the output fit with the other text, it's expected to be indented and end with a newline:

class ColorLayer: public Ui::AbstractLayer {
    public:
        struct DebugIntegration;

        Color3 color(Ui::LayerDataHandle handle) const;

        
};

struct ColorLayer::DebugIntegration {
    void print(Debug& debug, const ColorLayer& layer,
               Containers::StringView layerName, Ui::LayerDataHandle data) {
        /* Convert to an 8-bit color for brevity */
        Color3ub color8 = Math::pack<Color3ub>(layer.color(data));
        debug << "  Data" << Debug::packed << data
            << "from layer" << Debug::packed << layer.handle()
            << Debug::color(Debug::Color::Yellow) << layerName << Debug::resetColor
            << "with color";
        /* If colors aren't disabled, print also a color swatch besides the
           actual value. All other coloring will be automatically ignored if
           DisableColors is set. */
        if(!(debug.flags() & Debug::Flag::DisableColors))
            debug << Debug::color << color8;
        debug << color8 << Debug::newline;
    }
};

Assuming the concrete layer type is passed to setLayerName(), nothing else needs to be done and the integration gets used automatically. If there's multiple data attached to the same node, the print() gets called for each of them.

ColorLayer& colorLayer = ui.setLayerInstance();


debugLayer.setLayerName(colorLayer, "Shiny");
Node {0xc, 0x1}
  Data {0x4, 0x3} from layer {0x4, 0x1} Shiny with color ▓▓ #2f83cc
  Data {0x7, 0x1} from layer {0x4, 0x1} Shiny with color ██ #3bd267

If the integration needs additional state, a constructor can be implemented, and the instance then passed to DebugLayer::setLayerName(const T&, const Containers::StringView&, const typename T::DebugIntegration&) or setLayerName(..., typename T::DebugIntegration&&):

class ColorLayer::DebugIntegration {
    public:
        /*implicit*/ DebugIntegration(DebugFlags flags = {}): _flags{flags} {}

        void print(Debug& debug, const ColorLayer& layer,
                   Containers::StringView layerName, Ui::LayerDataHandle data);

    private:
        DebugFlags _flags;
};

If additional details can be provided even without supplying additional state, it's recommended to have the class default-constructible as well so at least some functionality can be used even if users aren't aware of the extra options. Additionally, in the above code the constructor isn't made explicit, which allows the debug integration arguments to be passed directly to setLayerName():

/* Default integration setup */
debugLayer.setLayerName(colorLayer, "Shiny");

/* Passing extra arguments */
debugLayer.setLayerName(colorLayer, "Shiny",
    ColorLayer::DebugFlag::PrintColor|ColorLayer::DebugFlag::ColorSwatch);

For layouters and DebugLayerSource::NodeLayoutDetails the workflow is similar, just with a slightly different signature for the print function. Same goes for DebugLayerSource::NodeAnimationDetails, which also has its own signature.

Overriding the integration in subclasses

Any layer, layouter or animator derived from a base that already has an integration, such as from AbstractVisualLayer or EventLayer, will implicitly inherit the base implementation. If the subclass wants to provide its own output, it can either provide a custom type and hide the original, or derive from it by calling the base print() in its own implementation. Here the ColorLayer from above is made to derive from AbstractVisualLayer now instead, and its DebugIntegration derives from AbstractVisualLayer::DebugIntegration as well and inherits its output:

class ColorLayer: public Ui::AbstractVisualLayer {
    public:
        struct DebugIntegration;

        
};

struct ColorLayer::DebugIntegration: Ui::AbstractVisualLayer::DebugIntegration {
    void print(Debug& debug, const ColorLayer& layer,
               Containers::StringView layerName, Ui::LayerDataHandle data) {
        Ui::AbstractVisualLayer::DebugIntegration::print(debug, layer, layerName, data);

        /* Print just what isn't already provided by AbstractVisualLayer */
        Color3ub color8 = Math::pack<Color3ub>(layer.color(data));
        debug << "    Color:";
        if(!(debug.flags() & Debug::Flag::DisableColors))
            debug << Debug::color << color8;
        debug << color8 << Debug::newline;
    }
};

Base classes

class AbstractLayer new in Git master
Base for data layers.

Derived classes

class DebugLayerGL new in Git master
OpenGL implementation of the debug layer.

Constructors, destructors, conversion operators

DebugLayer(LayerHandle handle, DebugLayerSources sources, DebugLayerFlags flags) explicit
Constructor.
DebugLayer(const DebugLayer&) deleted
Copying is not allowed.
DebugLayer(DebugLayer&&) noexcept
Move constructor.

Public functions

auto operator=(const DebugLayer&) -> DebugLayer& deleted
Copying is not allowed.
auto operator=(DebugLayer&&) -> DebugLayer& noexcept
Move assignment.
auto sources() const -> DebugLayerSources
Tracked data sources.
auto flags() const -> DebugLayerFlags
Behavior flags.
auto setFlags(DebugLayerFlags flags) -> DebugLayer&
Set behavior flags.
auto addFlags(DebugLayerFlags flags) -> DebugLayer&
Add behavior flags.
auto clearFlags(DebugLayerFlags flags) -> DebugLayer&
Clear flags.
auto nodeName(NodeHandle handle) const -> Containers::StringView
Node name.
auto setNodeName(NodeHandle handle, Containers::StringView name) -> DebugLayer&
Set node name.
auto layerName(LayerHandle handle) const -> Containers::StringView
Layer name.
auto setLayerName(const AbstractLayer& layer, Containers::StringView name) -> DebugLayer&
Set layer name.
template<class T>
auto setLayerName(const T& layer, const Containers::StringView& name) -> DebugLayer&
Set name for a layer with AbstractLayer::DebugIntegration implemented.
template<class T>
auto setLayerName(const T& layer, const Containers::StringView& name, const typename T::DebugIntegration& integration) -> DebugLayer&
Set name for a layer with AbstractLayer::DebugIntegration implemented.
template<class T>
auto setLayerName(const T& layer, const Containers::StringView& name, typename T::DebugIntegration&& integration) -> DebugLayer&
auto layouterName(LayouterHandle handle) const -> Containers::StringView
Layouter name.
auto setLayouterName(const AbstractLayouter& layouter, Containers::StringView name) -> DebugLayer&
Set layouter name.
template<class T>
auto setLayouterName(const T& layouter, const Containers::StringView& name) -> DebugLayer&
Set name for a layouter with AbstractLayouter::DebugIntegration implemented.
template<class T>
auto setLayouterName(const T& layouter, const Containers::StringView& name, const typename T::DebugIntegration& integration) -> DebugLayer&
Set name for a layouter with AbstractLayouter::DebugIntegration implemented.
template<class T>
auto setLayouterName(const T& layouter, const Containers::StringView& name, typename T::DebugIntegration&& integration) -> DebugLayer&
auto animatorName(AnimatorHandle handle) const -> Containers::StringView
Animator name.
auto setAnimatorName(const AbstractAnimator& animator, Containers::StringView name) -> DebugLayer&
Set animator name.
template<class T>
auto setAnimatorName(const T& animator, const Containers::StringView& name) -> DebugLayer&
Set name for an animator with AbstractAnimator::DebugIntegration implemented.
template<class T>
auto setAnimatorName(const T& animator, const Containers::StringView& name, const typename T::DebugIntegration& integration) -> DebugLayer&
Set name for an animator with AbstractAnimator::DebugIntegration implemented.
template<class T>
auto setAnimatorName(const T& animator, const Containers::StringView& name, typename T::DebugIntegration&& integration) -> DebugLayer&
auto nodeInspectColor() const -> Color4
Node inspect color.
auto setNodeInspectColor(const Color4& color) -> DebugLayer&
Set node inspect color.
auto nodeInspectGesture() const -> Containers::Pair<Pointers, Modifiers>
Node inspect gesture.
auto setNodeInspectGesture(Pointers pointers, Modifiers modifiers) -> DebugLayer&
Set node inspect gesture.
auto hasNodeInspectCallback() const -> bool
Whether a node inspect callback is set.
auto setNodeInspectCallback(Containers::Function<void(Containers::StringView message)>&& callback) -> DebugLayer&
Set node inspect callback.
auto currentInspectedNode() const -> NodeHandle
Node inspected by last pointer press.
auto inspectNode(NodeHandle node) -> bool
Inspect a node.
auto nodeHighlightColorMap() const -> Containers::ArrayView<const Vector3ub>
Node highlight color map.
auto nodeHighlightColorMapAlpha() const -> Float
Node highlight color map alpha.
auto setNodeHighlightColorMap(Containers::ArrayView<const Vector3ub> colors, Float alpha = 0.25f) -> DebugLayer&
Set node highlight color map.
auto currentHighlightedNodes() const -> Containers::BitArrayView
Currently highlighted nodes.
auto clearHighlightedNodes() -> DebugLayer&
Clear all highlighted nodes.
auto highlightNode(NodeHandle node) -> bool
Highlight a node.
auto highlightNodes(bool(*)(const AbstractUserInterface&ui, NodeHandle node) condition) -> bool
Highlight nodes based on a condition.
template<class T, class U>
auto highlightNodes(const T& layer, bool(*)(const U&layer, LayerDataHandle data) condition) -> bool
Highlight nodes based on a condition queried on data attachments.
template<class T, class U>
auto highlightNodes(const T& layouter, bool(*)(const U&layouter, LayouterDataHandle data) condition) -> bool
Highlight nodes based on a condition queried on layout assignments.
template<class T, class U>
auto highlightNodes(const T& animator, bool(*)(const U&animator, AnimatorDataHandle data) condition) -> bool
Highlight nodes based on a condition queried on animation attachments.

Function documentation

Magnum::Ui::DebugLayer::DebugLayer(LayerHandle handle, DebugLayerSources sources, DebugLayerFlags flags) explicit

Constructor.

Parameters
handle Layer handle returned from AbstractUserInterface::createLayer()
sources Data sources to track
flags Behavior flags

See particular DebugLayerFlag values for information about which DebugLayerSource is expected to be enabled for a particular feature. While sources have to specified upfront, the flags can be subsequently modified using setFlags(), addFlags() and clearFlags().

Note that you can also construct the DebugLayerGL subclass instead to have the layer with visual feedback.

Magnum::Ui::DebugLayer::DebugLayer(DebugLayer&&) noexcept

Move constructor.

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

DebugLayer& Magnum::Ui::DebugLayer::setFlags(DebugLayerFlags flags)

Set behavior flags.

Returns Reference to self (for method chaining)

See particular DebugLayerFlag values for information about which DebugLayerSource is expected to be enabled for a particular feature.

If a node was inspect and DebugLayerFlag::NodeInspect was cleared by calling this function, the highlight gets removed. The function doesn't print anything, but if a callback is set, it's called with an empty string. Additionally, if the layer is instantiated as DebugLayerGL, it causes LayerState::NeedsDataUpdate to be set.

DebugLayer& Magnum::Ui::DebugLayer::addFlags(DebugLayerFlags flags)

Add behavior flags.

Returns Reference to self (for method chaining)

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

DebugLayer& Magnum::Ui::DebugLayer::clearFlags(DebugLayerFlags flags)

Clear flags.

Returns Reference to self (for method chaining)

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

Containers::StringView Magnum::Ui::DebugLayer::nodeName(NodeHandle handle) const

Node name.

Expects that the debug layer has been already passed to AbstractUserInterface::setLayerInstance() and that handle isn't NodeHandle::Null, handle doesn't have to be valid however. If DebugLayerSource::Nodes isn't enabled or handle isn't known, returns an empty string.

If not empty, the returned string is always Containers::StringViewFlag::NullTerminated. Unless setLayerName() was called with a Containers::StringViewFlag::Global string, the returned view is only guaranteed to be valid until the next call to setLayerName() or until the set of layers in the user interface changes.

DebugLayer& Magnum::Ui::DebugLayer::setNodeName(NodeHandle handle, Containers::StringView name)

Set node name.

Returns Reference to self (for method chaining)

Expects that the debug layer has been already passed to AbstractUserInterface::setLayerInstance() and that handle isn't NodeHandle::Null, handle doesn't have to be valid however. If DebugLayerSource::Nodes is enabled, the name will be used to annotate given handle, otherwise the function does nothing.

If name is Containers::StringViewFlag::Global and NullTerminated, no internal copy of the string is made. If DebugLayerSource::Nodes isn't enabled, the function does nothing.

Containers::StringView Magnum::Ui::DebugLayer::layerName(LayerHandle handle) const

Layer name.

Expects that the debug layer has been already passed to AbstractUserInterface::setLayerInstance() and that handle isn't LayerHandle::Null, handle doesn't have to be valid however. If DebugLayerSource::Layers isn't enabled or handle isn't known, returns an empty string. For handle() returns "Debug" if a different name wasn't set.

If not empty, the returned string is always Containers::StringViewFlag::NullTerminated. Unless setLayerName() was called with a Containers::StringViewFlag::Global string, the returned view is only guaranteed to be valid until the next call to setLayerName() or until the set of layers in the user interface changes.

DebugLayer& Magnum::Ui::DebugLayer::setLayerName(const AbstractLayer& layer, Containers::StringView name)

Set layer name.

Returns Reference to self (for method chaining)

Expects that the debug layer has been already passed to AbstractUserInterface::setLayerInstance() and that layer is part of the same user interface. If DebugLayerSource::Layers is enabled, the name will be used to annotate attachments from given layer, otherwise the function does nothing.

If name is Containers::StringViewFlag::Global and NullTerminated, no internal copy of the string is made. If DebugLayerSource::Layers isn't enabled, the function does nothing.

If a concrete layer type gets passed instead of just AbstractLayer, the setLayerName(const T&, const Containers::StringView&) overload may get picked if given layer implements debug integration, allowing it to provide further details.

template<class T>
DebugLayer& Magnum::Ui::DebugLayer::setLayerName(const T& layer, const Containers::StringView& name)

Set name for a layer with AbstractLayer::DebugIntegration implemented.

Returns Reference to self (for method chaining)

In addition to setLayerName(const AbstractLayer&, Containers::StringView), if DebugLayerSource::NodeDataDetails is enabled, the layer's DebugIntegration implementation gets called for each attachment, allowing it to provide further details. See documentation of a particular layer for more information. Use the setLayerName(const T&, const Containers::StringView&, const typename T::DebugIntegration&) overload to pass additional arguments to the debug integration.

template<class T>
DebugLayer& Magnum::Ui::DebugLayer::setLayerName(const T& layer, const Containers::StringView& name, const typename T::DebugIntegration& integration)

Set name for a layer with AbstractLayer::DebugIntegration implemented.

Returns Reference to self (for method chaining)

In addition to setLayerName(const AbstractLayer&, Containers::StringView) the passed layer DebugIntegration instance gets called for each attachment, allowing it to provide further details. See documentation of a particular layer for more information.

template<class T>
DebugLayer& Magnum::Ui::DebugLayer::setLayerName(const T& layer, const Containers::StringView& name, typename T::DebugIntegration&& integration)

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

Containers::StringView Magnum::Ui::DebugLayer::layouterName(LayouterHandle handle) const

Layouter name.

Expects that the debug layer has been already passed to AbstractUserInterface::setLayerInstance() and that handle isn't LayouterHandle::Null, handle doesn't have to be valid however. If DebugLayerSource::Layouters isn't enabled or handle isn't known, returns an empty string.

If not empty, the returned string is always Containers::StringViewFlag::NullTerminated. Unless setLayouterName() was called with a Containers::StringViewFlag::Global string, the returned view is only guaranteed to be valid until the next call to setLayouterName() or until the set of layouters in the user interface changes.

DebugLayer& Magnum::Ui::DebugLayer::setLayouterName(const AbstractLayouter& layouter, Containers::StringView name)

Set layouter name.

Returns Reference to self (for method chaining)

Expects that the debug layer has been already passed to AbstractUserInterface::setLayerInstance() and that layouter is part of the same user interface. If DebugLayerSource::Layouters is enabled, the name will be used to annotate assignments from given layouter, otherwise the function does nothing.

If name is Containers::StringViewFlag::Global and NullTerminated, no internal copy of the string is made. If DebugLayerSource::Layouters isn't enabled, the function does nothing.

If a concrete layouter type gets passed instead of just AbstractLayouter, the setLayouterName(const T&, const Containers::StringView&) overload may get picked if given layouter implements debug integration, allowing it to provide further details.

template<class T>
DebugLayer& Magnum::Ui::DebugLayer::setLayouterName(const T& layouter, const Containers::StringView& name)

Set name for a layouter with AbstractLayouter::DebugIntegration implemented.

Returns Reference to self (for method chaining)

In addition to setLayouterName(const AbstractLayouter&, Containers::StringView), if DebugLayerSource::NodeLayoutDetails is enabled, the layouters's DebugIntegration implementation gets called for each attachment, allowing it to provide further details. See documentation of a particular animator for more information. Use the setLayouterName(const T&, const Containers::StringView&, const typename T::DebugIntegration&) overload to pass additional arguments to the debug integration.

template<class T>
DebugLayer& Magnum::Ui::DebugLayer::setLayouterName(const T& layouter, const Containers::StringView& name, const typename T::DebugIntegration& integration)

Set name for a layouter with AbstractLayouter::DebugIntegration implemented.

Returns Reference to self (for method chaining)

In addition to setLayouterName(const AbstractLayouter&, Containers::StringView), if DebugLayerSource::NodeLayoutDetails is enabled, the passed layouter DebugIntegration instance gets called for each attachment, allowing it to provide further details. See documentation of a particular layouter for more information.

template<class T>
DebugLayer& Magnum::Ui::DebugLayer::setLayouterName(const T& layouter, const Containers::StringView& name, typename T::DebugIntegration&& integration)

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

Containers::StringView Magnum::Ui::DebugLayer::animatorName(AnimatorHandle handle) const

Animator name.

Expects that the debug layer has been already passed to AbstractUserInterface::setLayerInstance() and that handle isn't AnimatorHandle::Null, handle doesn't have to be valid however. If DebugLayerSource::Animators isn't enabled or handle isn't known, returns an empty string.

If not empty, the returned string is always Containers::StringViewFlag::NullTerminated. Unless setAnimatorName() was called with a Containers::StringViewFlag::Global string, the returned view is only guaranteed to be valid until the next call to setAnimatorName() or until the set of animators in the user interface changes.

DebugLayer& Magnum::Ui::DebugLayer::setAnimatorName(const AbstractAnimator& animator, Containers::StringView name)

Set animator name.

Returns Reference to self (for method chaining)

Expects that the debug layer has been already passed to AbstractUserInterface::setLayerInstance() and that animator is part of the same user interface. If DebugLayerSource::Animators is enabled, the name will be used to annotate attachments from given animator, otherwise the function does nothing.

If name is Containers::StringViewFlag::Global and NullTerminated, no internal copy of the string is made. If DebugLayerSource::Animators isn't enabled, the function does nothing.

If a concrete animator type gets passed instead of just AbstractAnimator, the setAnimatorName(const T&, const Containers::StringView&) overload may get picked if given animator implements debug integration, allowing it to provide further details.

template<class T>
DebugLayer& Magnum::Ui::DebugLayer::setAnimatorName(const T& animator, const Containers::StringView& name)

Set name for an animator with AbstractAnimator::DebugIntegration implemented.

Returns Reference to self (for method chaining)

In addition to setAnimatorName(const AbstractAnimator&, Containers::StringView), if DebugLayerSource::NodeAnimationDetails is enabled, the animator's DebugIntegration implementation gets called for each attachment, allowing it to provide further details. See documentation of a particular animator for more information. Use the setAnimatorName(const T&, const Containers::StringView&, const typename T::DebugIntegration&) overload to pass additional arguments to the debug integration.

template<class T>
DebugLayer& Magnum::Ui::DebugLayer::setAnimatorName(const T& animator, const Containers::StringView& name, const typename T::DebugIntegration& integration)

Set name for an animator with AbstractAnimator::DebugIntegration implemented.

Returns Reference to self (for method chaining)

In addition to setAnimatorName(const AbstractAnimator&, Containers::StringView), if DebugLayerSource::NodeAnimationDetails is enabled, the passed animator DebugIntegration instance gets called for each attachment, allowing it to provide further details. See documentation of a particular animator for more information.

template<class T>
DebugLayer& Magnum::Ui::DebugLayer::setAnimatorName(const T& animator, const Containers::StringView& name, typename T::DebugIntegration&& integration)

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

DebugLayer& Magnum::Ui::DebugLayer::setNodeInspectColor(const Color4& color)

Set node inspect color.

Returns Reference to self (for method chaining)

Used only if DebugLayerFlag::NodeInspect is enabled and if the layer is instantiated as DebugLayerGL to be able to draw the highlight rectangle, ignored otherwise. Default is 0xff00ffff_rgbaf*0.5f.

If the layer is instantiated as DebugLayerGL, calling this function causes LayerState::NeedsDataUpdate to be set.

DebugLayer& Magnum::Ui::DebugLayer::setNodeInspectGesture(Pointers pointers, Modifiers modifiers)

Set node inspect gesture.

Returns Reference to self (for method chaining)

Used only if DebugLayerFlag::NodeInspect is enabled, ignored otherwise. An inspect happens on a press of a pointer that's among pointers with modifiers being exactly modifiers. Pressing on a different node moves the highlight to the other node, pressing on a node that's currently being inspected removes the highlight. Expects that pointers are non-empty. Default is a combination of Pointer::MouseRight and Pointer::Eraser, and Modifier::Ctrl, i.e. pressing either Ctrl right mouse button or Ctrl pen eraser will inspect a node under the pointer. The currently inspected node is available in currentInspectedNode(), you can also use inspectNode() to perform a node inspect programmatically.

DebugLayer& Magnum::Ui::DebugLayer::setNodeInspectCallback(Containers::Function<void(Containers::StringView message)>&& callback)

Set node inspect callback.

Returns Reference to self (for method chaining)

Used only if DebugLayerFlag::NodeInspect is enabled, ignored otherwise. The callback receives a UTF-8 message with details when a highlight happens on a pointer press, and an empty string if a highlight is removed again. If not empty, the message is guaranteed to be Containers::StringViewFlag::NullTerminated.

If the callback is not set or if set to nullptr, details about the inspected node are printed to Debug instead.

NodeHandle Magnum::Ui::DebugLayer::currentInspectedNode() const

Node inspected by last pointer press.

Expects that DebugLayerFlag::NodeInspect is enabled. If no node is currently being inspected, returns NodeHandle::Null.

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

bool Magnum::Ui::DebugLayer::inspectNode(NodeHandle node)

Inspect a node.

Expects that DebugLayerFlag::NodeInspect is enabled and the layer has been already passed to AbstractUserInterface::setLayerInstance().

If node is a known handle, the function performs similarly to the node inspect gesture using a pointer press — currentInspectedNode() is set to node, details about the node are printed to Debug or passed to a callback if set, the node is visually highlighted if this is a DebugLayerGL instance, and the function returns true.

If node is NodeHandle::Null or it's not a known handle (for example an invalid handle of a now-removed node, or a handle of a newly created node but AbstractUserInterface::update() wasn't called since) and there's a currently inspected node, its highlight is removed. The function doesn't print anything, but if a callback is set, it's called with an empty string. If there's no currently inspected node, the callback isn't called. The functions returns true if node is NodeHandle::Null and false if the handle is unknown.

Note that, compared to the node inspect gesture, where the node details are always extracted from an up-to-date UI state, this function only operates with the state known at the last call to AbstractUserInterface::update(). As such, for example nodes or layers added since the last update won't be included in the output.

See highlightNode() and highlightNodes() for a way to just highlight particular nodes in the UI without inspecting them.

If the layer is instantiated as DebugLayerGL and node is different from the previously inspected node, calling this function causes LayerState::NeedsDataUpdate to be set.

DebugLayer& Magnum::Ui::DebugLayer::setNodeHighlightColorMap(Containers::ArrayView<const Vector3ub> colors, Float alpha = 0.25f)

Set node highlight color map.

Returns Reference to self (for method chaining)

Used only if DebugLayerSource::Nodes is enbaled and the layer is instantiated as DebugLayerGL to be able to draw the highlight rectangles, ignored otherwise. The colors are expected to have at least one element and the caller has to ensure the memory stays in scope for the whole layer lifetime or until setNodeHighlightColorMap() is called with a different color map.

The first element from colors is used to highlight a node with ID 0, the last element for a node with ID right below AbstractUserInterface::nodeCapacity(), IDs in between get a linear interpolation between nearest elements. All colors are made four-component and premultiplied with alpha when applied. Default is a map with a single 0x00ffff_rgb color, i.e. the same color used for all nodes.

If the layer is instantiated as DebugLayerGL, calling this function causes LayerState::NeedsDataUpdate to be set.

Containers::BitArrayView Magnum::Ui::DebugLayer::currentHighlightedNodes() const

Currently highlighted nodes.

Expects that DebugLayerSource::Nodes is enabled. Reflects the result of highlightNode() and highlightNodes() calls, a particular bit being set if a node with given handle ID is highlighted, Size of the returned array matches AbstractUserInterface::nodeCapacity() at the last time AbstractUserInterface::update() or draw() has been called, bits get reset for any previously highlighted nodes that become invalid at that point.

DebugLayer& Magnum::Ui::DebugLayer::clearHighlightedNodes()

Clear all highlighted nodes.

Returns Reference to self (for method chaining)

Expects that DebugLayerSource::Nodes is enabled. Clears all nodes highlighted by highlightNode() and highlightNodes(). Note that the currentInspectedNode() isn't affected by this function.

bool Magnum::Ui::DebugLayer::highlightNode(NodeHandle node)

Highlight a node.

Expects that DebugLayerSource::Nodes is enabled, the layer has been already passed to AbstractUserInterface::setLayerInstance() and that node isn't NodeHandle::Null.

If node is a known handle, the node is visually highlighted if this is a DebugLayerGL instance, and the function returns true.

If node is not a known handle (for example an invalid handle of a now-removed node, or a handle of a newly created node but AbstractUserInterface::update() wasn't called since), the function does nothing and returns false.

The highlights are additive, meaning calling this function multiple times will make more nodes highlighted. Use clearHighlightedNodes() to clear all highlights. You can use the highlightNodes() variants to highlight nodes based on various conditions queried on the nodes themselves or on data, layouts and animations attached to those. See also inspectNode() for highlighting a node and printing its details.

If the layer is instantiated as DebugLayerGL and node wasn't already highlighted before, calling this function causes LayerState::NeedsDataUpdate to be set.

bool Magnum::Ui::DebugLayer::highlightNodes(bool(*)(const AbstractUserInterface&ui, NodeHandle node) condition)

Highlight nodes based on a condition.

Expects that DebugLayerSource::Nodes is enabled, the layer has been already passed to AbstractUserInterface::setLayerInstance() and that condition isn't nullptr.

The function goes through all known and valid nodes and calls condition with the user interface instance and a NodeHandle. If condition returns true, given node is highlighted.

The passed handles are all guaranteed to be valid, however note that not yet known nodes (such as nodes that were newly created since the last call to AbstractUserInterface::update()) will not be taken into account. The function returns false if the debug layer doesn't know any valid nodes (such as when calling this function before any AbstractUserInterface::update() call) and thus condition was never called. The function returns true if there was at least one node to call condition on, regardless of whether any node was actually highlighted by it.

If the layer is instantiated as DebugLayerGL and nodes for which condition returned true weren't already highlighted, calling this function causes LayerState::NeedsDataUpdate to be set.

template<class T, class U>
bool Magnum::Ui::DebugLayer::highlightNodes(const T& layer, bool(*)(const U&layer, LayerDataHandle data) condition)

Highlight nodes based on a condition queried on data attachments.

Returns Reference to self (for method chaining)

Expects that DebugLayerSource::Nodes and Layers is enabled, that the debug layer has been already passed to AbstractUserInterface::setLayerInstance(), that layer is part of the same user interface and is not the debug layer itself and that condition isn't nullptr.

If layer is known to the debug layer, the function goes through all its data that are attached to known and valid nodes and calls condition with the layer instance and a LayerDataHandle. If condition returns true, the node the data is attached to is highlighted.

The passed handles are all guaranteed to be valid in given layer, however note that data attached to not yet known nodes (such as nodes that were newly created since the last call to AbstractUserInterface::update()) will not be taken into account. The function returns false if the debug layer doesn't know layer or none of its data are attached to known and valid nodes (such as when calling this function before any AbstractUserInterface::update() call) and thus condition was never called. The function returns true if there was at least one data to call condition on, regardless of whether any node was actually highlighted by it.

If the layer is instantiated as DebugLayerGL and nodes for which condition returned true weren't already highlighted, calling this function causes LayerState::NeedsDataUpdate to be set.

template<class T, class U>
bool Magnum::Ui::DebugLayer::highlightNodes(const T& layouter, bool(*)(const U&layouter, LayouterDataHandle data) condition)

Highlight nodes based on a condition queried on layout assignments.

Returns Reference to self (for method chaining)

Expects that DebugLayerSource::Nodes and Layouters is enabled, that the debug layer has been already passed to AbstractUserInterface::setLayerInstance(), that layouter is part of the same user interface and that condition isn't nullptr.

If layouter is known to the debug layer, the function goes through all its layouts that are assigned to known and valid nodes and calls condition with the layouter instance and a LayouterDataHandle. If condition returns true, the node the layout is assigned to is highlighted.

The passed handles are all guaranteed to be valid in given layouter, however note that layouts assigned to not yet known nodes (such as nodes that were newly created since the last call to AbstractUserInterface::update()) will not be taken into account. The function returns false if the debug layer doesn't know layouter or none of its layouts are assigned to known and valid nodes (such as when calling this function before any AbstractUserInterface::update() call) and thus condition was never called. The function returns true if there was at least one layout to call condition on, regardless of whether any node was actually highlighted by it.

If the layer is instantiated as DebugLayerGL and nodes for which condition returned true weren't already highlighted, calling this function causes LayerState::NeedsDataUpdate to be set.

template<class T, class U>
bool Magnum::Ui::DebugLayer::highlightNodes(const T& animator, bool(*)(const U&animator, AnimatorDataHandle data) condition)

Highlight nodes based on a condition queried on animation attachments.

Returns Reference to self (for method chaining)

Expects that DebugLayerSource::Nodes and Animators is enabled, that the debug layer has been already passed to AbstractUserInterface::setLayerInstance(), that animator is part of the same user interface and supports AnimatorFeature::NodeAttachment and that condition isn't nullptr.

If animator is known to the debug layer, the function goes through all its animations that are attached to known and valid nodes and calls condition with the animator instance and an AnimatorDataHandle. If condition returns true, the node the animation is attached to is highlighted.

The passed handles are all guaranteed to be valid in given animator, however note that animations attached to not yet known nodes (such as nodes that were newly created since the last call to AbstractUserInterface::update()) will not be taken into account. The function returns false if the debug layer doesn't know animator or none of its data are attached to known and valid nodes (such as when calling this function before any AbstractUserInterface::update() call) and thus condition was never called. The function returns true if there was at least one animation to call condition on, regardless of whether any node was actually highlighted by it.

If the layer is instantiated as DebugLayerGL and nodes for which condition returned true weren't already highlighted, calling this function causes LayerState::NeedsDataUpdate to be set.