Skip to main content
Version: 4.55.1

FlexBox Layout

A frequent case that comes working on UIs is the need to layout content along an axis, for example a menu of buttons in a list. This doesn’t need to be a lot of elements either, often it's useful even with two elements to have them positioned based on each other. The Asset Library contains three Custom Components to help manage this, Horizontal Layout, Vertical Layout, and Layout Element. The Horizontal and Vertical Layout Components work by positioning all the Layout Elements on child Scene Objects into the space allotted to the Layout Component by driving the properties of their ScreenTransform.

These Components will resolve the final sizes of elements using an established algorithm called "FlexBox". This originally comes from CSS but is used across many UI frameworks. This means that outside of these docs there are many resources available on how to use FlexBox layouts in powerful ways.

How to set it up

The Horizontal and Vertical Layout Components have some requirements for their hierarchy:

  • The Layout Component should be added to a scene object with Screen Transform
  • It should be a child of an Orthographic Camera or Canvas Component (We call this a valid ScreenTransform hierarchy)
  • All elements of the layout should be direct children of the SceneObject with the Layout Component and should have a LayoutElement Component.

An example of setting up a Vertical Layout in the inspector. Try the project here

// -----JS CODE-----
//@typename VerticalLayout
//@typename UIButton
//@typename LayoutElement
//@input Component.Camera uiCam
//@input Asset.Material backgroundMat
var DestructionHelper = require('DestructionHelper');
var helper = new DestructionHelper(script);

function createObj(parent) {
var obj = helper.createSceneObject(parent);
obj.layer = script.uiCam.renderLayer;
helper.createComponent(obj, 'Component.ScreenTransform');
return obj;
}

var regionObj = createObj(script.uiCam.getSceneObject());
var region = helper.createComponent(
regionObj,
'Component.ScreenRegionComponent'
);
region.region = ScreenRegionType.SafeRender;

var backgroundObj = createObj(regionObj);
backgroundObj.getComponent('Component.ScreenTransform').anchors = Rect.create(
-0.8,
0.8,
-0.9,
0.9
);
var background = helper.createComponent(backgroundObj, 'Component.Image');
background.mainMaterial = script.backgroundMat;
background.setRenderOrder(-1);
background.stretchMode = StretchMode.Fit;

var extentsTargetObj = createObj(backgroundObj);
background.extentsTarget = extentsTargetObj.getComponent(
'Component.ScreenTransform'
);

var layoutObj = createObj(extentsTargetObj);
var layout = helper.createComponent(layoutObj, script.VerticalLayout);
layout.alignment = 1;

function createSpacer() {
var obj = createObj(layoutObj);
var element = helper.createComponent(obj, script.LayoutElement);
element.requestedSize = new vec2(0, 0);
element.minimumSize = new vec2(0, 0);
element.maximumSize = new vec2(100, 100);
element.growWeight = 1;
}
function createButton(text, onClick) {
var obj = createObj(layoutObj);
var element = helper.createComponent(obj, script.LayoutElement);
//Only requested size matters since grow/shrink weights are 0
element.requestedSize = new vec2(6, 2);
element.growWeight = 0;
element.shrinkWeight = 0;
element.margins = Rect.create(0, 0, 0.1, 0.1);
var button = helper.createComponent(obj, script.UIButton);
button.changeAnimationType('Squish');
button.changeStateValue('normal', 'Color', new vec4(1, 1, 0, 1));
button.changeStateValue('pressed', 'Color', new vec4(0.5, 0.5, 0.2, 1));
button.getTextComponent().text = text;
button.onPress.add(onClick);
return button;
}
createSpacer();
createButton('Option One', function () {
print('Option One clicked');
});
createButton('Option Two', function () {
print('Option Two clicked');
});
createButton('Option Three', function () {
print('Option Three clicked');
});
createSpacer();

This script builds the same menu as the screenshot above. You can try the project here.

Configuring the layout

Horizontal & Vertical Layout

Alignment

Where on the "cross-axis" content should be placed. For example for the Vertical Layout this determines whether content should be placed on the left, center, or right of the available space once their size is resolved

Direction

Which direction on the "main-axis" content should be placed on. For example for the Vertical Layout this is top to bottom or bottom to top

Padding

This allows reserving some space on the interior edge of the layout to stay empty. Content will be laid out into the remaining space.

Grow Weight / Shrink Weight / Margins

The Horizontal and Vertical Layout Components are also Layout Elements, meaning they can be nested as children of other layouts. These properties are to control their behavior in that case and you can find more details in the Layout Element section.

Overrides

The Horizontal and Vertical Layout Components have a series of settings to allow overriding the properties of their child Layout Elements. If you enable one of these overrides, then the value for that property on children will be ignored and instead used from here. For more details on what each property means see the Layout Element section.

Layout Element

Many of the properties of Layout Element are "sizes". These sizes are in world units, meaning for an orthographic Camera this is relative to the “size” property of the Camera and for a Canvas in world space they are in cm.

Requested Size

The size that this element would ideally be. The element will start at this size before distributing any extra space or doing any necessary shrinking. In css this is "flex-basis".

Minimum Size

The minimum size that this element can shrink to. The layout won’t make it smaller than this and will shrink other elements instead or allow the layout’s size to overflow if all elements are at their minimum size. In css this corresponds to "min-height" and “min-width”.

Maximum Size

The maximum size that this element can grow to. The layout won’t grow it any further than this and will either distribute the size to other elements or leave the space empty. In css this corresponds to "max-height" and “max-width”.

Grow Weight

Determines how much of the extra space this element will receive. When the layout has extra space it will sum the grow weights of all of the elements and distribute the space according to their portion of the total weight. This means an element with grow weight 2 will grow twice as much as an element with grow weight 1. An element with grow weight 0 will never grow beyond its requested size. In css this is "flex-grow".

Shrink Weight

Determines how much this element will shrink when the available size is less than the total requested sizes. When the layout needs to shrink it will sum the shrink weights of all of the elements and shrink them according to their portion of the total weight. This means an element with shrink weight 2 will shrink twice as much as an element with shrink weight 1. An element with shrink weight 0 will never shrink below its requested size. In css this is "flex-shrink".

Margins

Margins are space this element reserves around itself to be empty. The content will only be inside the margins and they are a constant size that is unaffected by grow and shrink weights.

Margins

Writing your own Layouts and Layout Elements

Layout, and especially Layout Element, are patterns that are intended to be extended with new Custom Components in the future both by Snap and by creators for their own projects. You may want to have a Custom Component function as a Layout Element so that the properties described above can be calculated dynamically instead of explicitly defining them. For example both Vertical and Horizontal Layout are also Layout Elements which calculate requested, min, and max size based on the properties of the elements they contain.

To be a valid Layout Element, Custom Components should conform to the following interface:

  • Implement a function _isOfType(type) to return true for "LayoutElement"

  • Implement the following "getter" functions

    • getRequestedSize() which returns a vec2
    • getMinimumSize() which returns a vec2
    • getMaximumSize() which returns a vec2
    • getGrowWeight() which returns a Number
    • getShrinkWeight() which returns a Number
    • getMargins() which returns a Rect
  • Implement management of "dirty" state

    • Implement a function isDirty() which returns true if the element is marked "dirty"
    • Implement a function markClean() which sets "dirty" = false
    • Implement a function markDirty() which sets "dirty" = true
    • You would also want to set "dirty" = true anytime some state of the Layout Element changes in a way that would cause it to report different values for the “getter” functions above.

You could also write a custom Layout class which used all the properties of Layout Element to do layout in some other way. These don’t necessarily need to conform to any interface, but to make sure they work nested inside of the main layouts you should do a couple things to make sure child layouts resolve before parent ones.

  • Implement a function _isOfType(type) to return true for "Container"

  • Implement a function runLayout() which resolves the layout

Outside Reference

As mentioned before, these Layout and Layout Element Custom Components are based on an established algorithm "FlexBox". Not every part of FlexBox makes sense or is supported within Lens Studio, but there are many other applicable resources for learning how to use these powerful tools. Some valuable ones are:

Was this page helpful?
Yes
No