Magnum::YogaIntegration::Layouter class new in Git master

Yoga layouter.

A layouter implementation for the Ui library using Yoga Layout, which exposes a subset of CSS Flexbox.

Setting up a layouter instance

The layouter doesn't have any shared state or configuration, so it's just about constructing it from a fresh Ui::AbstractUserInterface::createLayouter() handle and passing it to setLayouterInstance(). After that, it's ready to use:

YogaIntegration::Layouter& layouter = ui.setLayouterInstance(
    Containers::pointer<YogaIntegration::Layouter>(ui.createLayouter()));

Adding layouts to nodes

A layout is added to a particular node by calling add(). You can save the returned Ui::LayoutHandle if you need to refer the layout later, the layout assigned to a particular node is also accessible via Ui::AbstractUserInterface::nodeUniqueLayout() later. But the common use case is just adding a layout and not touching it in any way afterwards:

Ui::NodeHandle root = ui.createNode({}, {});
Ui::NodeHandle header = ui.createNode(root, {}, {150, 10});
Ui::NodeHandle top = ui.createNode(root, {}, {150, 30});
Ui::NodeHandle bottom = ui.createNode(root, {}, {150, 20});

layouter.add(root);
layouter.add(header);
layouter.add(top);
Ui::LayoutHandle bottomLayout = layouter.add(bottom);

Yoga by default lays out the nodes in a column and in the order add() is called. The layouts inherit node sizes, if specified, so in the above snippet the children all have a width of 150, and heights of 10, 30 and 20 respectively. The root node has no size specified and thus Yoga implicitly sizes it to fit all children. When visualizing node placement, for example with Ui::DebugLayer node highlight, the layout looks like this, with root being underneath the three children:

Image

The add() function optionally accepts a layout before which to insert given child. To switch the layout direction for children of a particular node, use setFlexDirection(). The following snippet inserts a row above the bottom node, with three nested nodes next to each other:

Ui::NodeHandle middle = ui.createNode(root, {}, {0, 40});
Ui::NodeHandle left = ui.createNode(middle, {}, {15, 0});
Ui::NodeHandle center = ui.createNode(middle, {}, {70, 0});
Ui::NodeHandle right = ui.createNode(middle, {}, {15, 0});

/* Insert the middle node before the bottom one */
Ui::LayoutHandle middleLayout = layouter.add(middle, bottomLayout);
layouter.setFlexDirection(middleLayout, YogaIntegration::FlexDirection::Row);
layouter.add(left, YogaIntegration::Flag::PercentageNodeSize);
layouter.add(center, YogaIntegration::Flag::PercentageNodeSize);
layouter.add(right, YogaIntegration::Flag::PercentageNodeSize);

In the above Flag::PercentageNodeSize is used, so their width is interpreted not in UI units but as 15%, 70% and 15%, respectively. The height is unspecified, which makes them expand to the whole height of middle, i.e. 40 units:

Image

When adding layouts, restrictions coming from Ui::LayouterFeature::UniqueLayouts apply, i.e. a particular node can have only at most one layout from given layouter instance, and if adding layouts to both a parent node and its children, the parent layout has to be added first so it can correctly track and order its children.

Absolute positioning

Node offsets, if specified, by default shift the node relatively to its layout position. Calling setNodeOffsetType() allows you to change the offset to absolute, excluding given node from layout calculations and positioning it exactly as specified. Together with Flag::NodeOffsetFromRight, NodeOffsetFromBottom or NodeOffsetFromRightBottom the offset can be then interpreted from bottom right instead of top left. Here a footer is placed 15 units from the bottom right corner of the bottom node:

Ui::NodeHandle footer = ui.createNode(bottom, {5, 5}, {60, 10});
Ui::LayoutHandle footerLayout = layouter.add(footer,
    YogaIntegration::Flag::NodeOffsetFromRightBottom);
layouter.setNodeOffsetType(footerLayout, YogaIntegration::NodeOffsetType::Absolute);
Image

Offsets, including absolute offsets, can be interpreted as percentages as well by enabling Flag::PercentageNodeOffset. Just note that absolute positioning is basically sidesteping most of Yoga's layouting capabilities, and if applied extensively you might want to consider not using Yoga at all.

Direct access to Yoga functionality

Right now, the Layouter exposes only a subset of Yoga functionality. It's however possible to call Yoga APIs directly on the Yoga node references returned by yogaNode(). Note that, however, you need to call setNeedsUpdate() afterwards to make the layouter aware of the changes and update the layout appropriately:

#include <yoga/Yoga.h>



Ui::LayoutHandle layout = layouter.add();
YGNodeStyleSetFlexWrap(layouter.yogaNode(layout), YGWrapWrapReverse);
layouter.setNeedsUpdate();

In case the implicit behavior of inheriting node offset and size is conflicting with direct Yoga API use, you might want to enable Flag::IgnoreNodeOffset, IgnoreNodeSize and related flags on affected nodes. With those, the Yoga nodes are only used to query the the calculated layout results, but they're never updated when node offsets or sizes change, leaving them completely under your control.

Global Yoga configuration

All created layouts share the same Yoga configuration, which is exposed through yogaConfig(). Compared to Yoga defaults, snapping to a pixel grid is disabled to avoid stutter when animating node offsets and sizes. Similarly as with direct Yoga node access shown above, you can access and update the configuration yourself, but again you need to call setNeedsUpdate() afterwards to make the layouter aware of the changes. For example, the following snippet enables pixel grid snapping back:

YGConfigSetPointScaleFactor(layouter.yogaConfig(), 1.0f);
layouter.setNeedsUpdate();

Debug layer integration

When using DebugLayer node inspect and Ui::DebugLayerSource::NodeLayoutDetails is enabled, passing this layouter to Ui::DebugLayer::setLayouterName() will make it list basic properties of a particular layout. Because the layout inherits node offset and size, it's useful to enable Ui::DebugLayerSource::NodeOffsetSize as well. For example:

Node {0x8, 0x2}
  Offset: {15, 5}, size: {75, 0}
  Layout {0x3, 0x3} from layouter {0x1, 0x2} Yoga
    Flags: NodeOffsetFromRight|PercentageNodeSizeX
    Flex direction: Row
    Node offset type: Absolute

Base classes

class Magnum::Ui::AbstractLayouter new in Git master
Base for layouters.

Public types

class DebugIntegration
Debug layer integration.

Constructors, destructors, conversion operators

Layouter(Ui::LayouterHandle handle) explicit
Constructor.
Layouter(const Layouter&) deleted
Copying is not allowed.
Layouter(Layouter&&) noexcept
Move constructor.

Public functions

auto operator=(const Layouter&) -> Layouter& deleted
Copying is not allowed.
auto operator=(Layouter&&) -> Layouter& noexcept
Move assignment.
auto yogaConfig() -> YGConfigRef
Yoga configuration used for all layout nodes.
auto yogaConfig() const -> YGConfigConstRef
auto add(Ui::NodeHandle node, Ui::LayoutHandle before = Ui::LayoutHandle::Null, Flags flags = {}) -> Ui::LayoutHandle
Add a layout assigned to given node.
auto add(Ui::NodeHandle node, Flags flags) -> Ui::LayoutHandle
auto add(Ui::NodeHandle node, Ui::LayouterDataHandle before, Flags flags = {}) -> Ui::LayoutHandle
Add a layout assigned to given node and before given layout assuming the layout belongs to this layouter.
void remove(Ui::LayoutHandle handle)
Remove a node from this layouter.
void remove(Ui::LayouterDataHandle handle)
Remove a node from this layouter assuming it belongs to it.
auto yogaNode(Ui::LayoutHandle handle) -> YGNodeRef
Yoga node associated with given layout.
auto yogaNode(Ui::LayoutHandle handle) const -> YGNodeConstRef
auto yogaNode(Ui::LayouterDataHandle handle) -> YGNodeRef
Yoga node associated with given layout assuming it belongs to this layouter.
auto yogaNode(Ui::LayouterDataHandle handle) const -> YGNodeConstRef
auto flags(Ui::LayoutHandle handle) const -> Flags
Layout flags.
auto flags(Ui::LayouterDataHandle handle) const -> Flags
Layout flags assuming it belongs to this layouter.
auto flexDirection(Ui::LayoutHandle handle) const -> FlexDirection
Layout flex direction.
auto flexDirection(Ui::LayouterDataHandle handle) const -> FlexDirection
Layout flex direction assuming it belongs to this layouter.
void setFlexDirection(Ui::LayoutHandle handle, FlexDirection direction)
Set layout flex direction.
void setFlexDirection(Ui::LayouterDataHandle handle, FlexDirection direction)
Set layout flex direction assuming it belongs to this layouter.
auto nodeOffsetType(Ui::LayoutHandle handle) const -> NodeOffsetType
Layout node offset type.
auto nodeOffsetType(Ui::LayouterDataHandle handle) const -> NodeOffsetType
Layout node offset type assuming it belongs to this layouter.
void setNodeOffsetType(Ui::LayoutHandle handle, NodeOffsetType type)
Set layout node offset type.
void setNodeOffsetType(Ui::LayouterDataHandle handle, NodeOffsetType type)
Set layout node offset type assuming it belongs to this layouter.

Function documentation

Magnum::YogaIntegration::Layouter::Layouter(Ui::LayouterHandle handle) explicit

Constructor.

Parameters
handle Layouter handle returned from Ui::AbstractUserInterface::createLayouter()

Magnum::YogaIntegration::Layouter::Layouter(Layouter&&) noexcept

Move constructor.

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

YGConfigRef Magnum::YogaIntegration::Layouter::yogaConfig()

Yoga configuration used for all layout nodes.

YGConfigConstRef Magnum::YogaIntegration::Layouter::yogaConfig() const

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

Ui::LayoutHandle Magnum::YogaIntegration::Layouter::add(Ui::NodeHandle node, Ui::LayoutHandle before = Ui::LayoutHandle::Null, Flags flags = {})

Add a layout assigned to given node.

Parameters
node Node to assign the layout to
before A layout to order before or Ui::LayouterHandle::Null if ordered as last
flags Flags
Returns New layout handle

The node is expected to not have a layout assigned from this layouter yet. If before is not null, it's expected to be valid, belong to the same layouter and have the same non-null parent layout as node.

If before is Ui::LayoutHandle::Null, the operation is performed in an amortized $ \mathcal{O}(1) $ complexity. If it's non-null, the insertion is done in a $ \mathcal{O}(n) $ complexity where $ n $ is sibling layout count, due to a linear lookup in the layouter and insertion in the middle of an array inside Yoga.

Ui::LayoutHandle Magnum::YogaIntegration::Layouter::add(Ui::NodeHandle node, Flags flags)

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

Ui::LayoutHandle Magnum::YogaIntegration::Layouter::add(Ui::NodeHandle node, Ui::LayouterDataHandle before, Flags flags = {})

Add a layout assigned to given node and before given layout assuming the layout belongs to this layouter.

Like add(Ui::NodeHandle, Ui::LayouterDataHandle, Flags) but without checking that before indeed belongs to this layouter. See its documentation for more information.

void Magnum::YogaIntegration::Layouter::remove(Ui::LayoutHandle handle)

Remove a node from this layouter.

Expects that handle is valid and has no child layouts from the same layouter. To remove a layout that has children, either remove the child layouts first or remove the whole node along with its children using Ui::AbstractUserInterface::removeNode().

void Magnum::YogaIntegration::Layouter::remove(Ui::LayouterDataHandle handle)

Remove a node from this layouter assuming it belongs to it.

Like remove(Ui::LayoutHandle) but without checking that handle indeed belongs to this layouter. See its documentation for more information.

YGNodeRef Magnum::YogaIntegration::Layouter::yogaNode(Ui::LayoutHandle handle)

Yoga node associated with given layout.

Expects that handle is valid.

YGNodeConstRef Magnum::YogaIntegration::Layouter::yogaNode(Ui::LayoutHandle handle) const

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

YGNodeRef Magnum::YogaIntegration::Layouter::yogaNode(Ui::LayouterDataHandle handle)

Yoga node associated with given layout assuming it belongs to this layouter.

Like yogaNode(Ui::LayoutHandle) but without checking that handle indeed belongs to this layouter. See its documentation for more information.

YGNodeConstRef Magnum::YogaIntegration::Layouter::yogaNode(Ui::LayouterDataHandle handle) const

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

Flags Magnum::YogaIntegration::Layouter::flags(Ui::LayoutHandle handle) const

Layout flags.

Expects that handle is valid. Note that, to reduce implementation complexity, the flags can be only specified in add() and cannot be modified afterwards.

Flags Magnum::YogaIntegration::Layouter::flags(Ui::LayouterDataHandle handle) const

Layout flags assuming it belongs to this layouter.

Like flags(Ui::LayoutHandle) const but without checking that handle indeed belongs to this layouter. See its documentation for more information.

FlexDirection Magnum::YogaIntegration::Layouter::flexDirection(Ui::LayoutHandle handle) const

Layout flex direction.

Expects that handle is valid.

FlexDirection Magnum::YogaIntegration::Layouter::flexDirection(Ui::LayouterDataHandle handle) const

Layout flex direction assuming it belongs to this layouter.

Like flexDirection(Ui::LayoutHandle) const but without checking that handle indeed belongs to this layouter. See its documentation for more information.

void Magnum::YogaIntegration::Layouter::setFlexDirection(Ui::LayoutHandle handle, FlexDirection direction)

Set layout flex direction.

Corresponds to the CSS flex-direction property, see the linked documentation for detailed explanation. Expects that handle is valid. Default is FlexDirection::Column, which matches behavior of flex-direction: column; in CSS.

Calling this function causes Ui::LayouterState::NeedsUpdate to be set.

void Magnum::YogaIntegration::Layouter::setFlexDirection(Ui::LayouterDataHandle handle, FlexDirection direction)

Set layout flex direction assuming it belongs to this layouter.

Like setFlexDirection(Ui::LayoutHandle, FlexDirection) but without checking that handle indeed belongs to this layouter. See its documentation for more information.

NodeOffsetType Magnum::YogaIntegration::Layouter::nodeOffsetType(Ui::LayoutHandle handle) const

Layout node offset type.

Expects that handle is valid.

NodeOffsetType Magnum::YogaIntegration::Layouter::nodeOffsetType(Ui::LayouterDataHandle handle) const

Layout node offset type assuming it belongs to this layouter.

Like nodeOffsetType(Ui::LayoutHandle) const but without checking that handle indeed belongs to this layouter. See its documentation for more information.

void Magnum::YogaIntegration::Layouter::setNodeOffsetType(Ui::LayoutHandle handle, NodeOffsetType type)

Set layout node offset type.

Corresponds to the CSS position property, see the linked documentation for detailed explanation, Expects that handle is valid. Default is NodeOffsetType::Relative, which matches behavior of position: relative; in CSS.

Calling this function causes Ui::LayouterState::NeedsUpdate to be set.

void Magnum::YogaIntegration::Layouter::setNodeOffsetType(Ui::LayouterDataHandle handle, NodeOffsetType type)

Set layout node offset type assuming it belongs to this layouter.

Like setNodeOffsetType(Ui::LayoutHandle, NodeOffsetType) but without checking that handle indeed belongs to this layouter. See its documentation for more information.