FREE TRIAL

|

☀
☾

Layouts

Nova's Layout System is responsible for determining the size and position of every active UIBlock in the scene and shares a lot in common with other modern UI frameworks and design tools (e.g. CSS Box Model), supporting a wide range of features including (but not limited to):

Feature Value
Edge-based Positioning Offset content from the edge of its parent.
Fixed and Relative Properties Dynamically resize/reposition elements as their parent size changes, while preserving the size/position of others.
Padding & Margin Apply space around elements in a more flexible manner than a pure size + position model.
Min/Max Properties Automatically clamp values to functional ranges without needing to write custom scripts.
AutoSize Configure a UIBlock to Expand to its parent's size or Shrink to the size of its children.
AutoLayout Automatically position all children of given parent along a selected axis using a specified Alignment and Spacing.

Layout Engine

At the core of Nova's Layout System is the highly-optimized Layout Engine, which was built from the ground-up for Unity's Burst Compiler and Job System to efficiently perform two key tasks:

  • Process the Layout configurations of UIBlocks and their dependencies to determine the calculated size and position used for rendering and input.
  • Keep the Layout properties and Transforms of UIBlocks in sync.

The Layout Engine runs once at the end of each frame and consists of three phases:

Phase Summary Description
1 Read Transform Positions Convert modified Transform positions into layout positions.
2 Calculate Layout Properties Process modified Layout and AutoLayout properties to determine where and how big or small a given UIBlock should be rendered.
3 Write Transform Positions Apply the position calculated in Phase 2 to each updated Transform, while honoring any preservable position overrides detected in Phase 1.

After the Layout Engine runs, the Layout properties and Transforms of all active UIBlocks will be in sync, and the calculated values for all layout properties can be accessed via their respective CalculatedX properties (e.g. CalculatedSize, CalculatedPosition, CalculatedMargin, etc.).

In-line Updates

In some situations, you may need the up-to-date Transform positions or calculated layout properties before the Layout Engine has run for the given frame. Two common scenarios where this may occur are:

  • The frame when a UIBlock's GameObject is set active (the Layout Engine will not have processed the just-activated UIBlock yet).
  • Multiple layout properties which affect each other (perhaps across UIBlocks) have been modified on the same frame.

For these cases, Nova provides the uiBlock.CalculateLayout() method which will perform an immediate layout update for the target UIBlock, accounting for all of its modified layout dependencies in the process.

// Modify the parent's padding
uiBlock.Parent.Padding.Left.Value = 5;

// Set the uiBlock width to be 50% of its parent's padded width
uiBlock.Size.X.Percent = 0.5f;

// Set horizontal Alignment and Position
uiBlock.Alignment.X = HorizontalAlignment.Left;
uiBlock.Position.X.Percent = 0.25f;

// Crunch the modified values
uiBlock.CalculateLayout();

// Now both the transform.localPosition and uiBlock.CalculatedSize 
// will be in sync with the newly applied layout properties, and we
// could then use them to construct a local Bounds of the target uiBlock. 
Bounds localBounds = new Bounds(uiBlock.transform.localPosition, uiBlock.CalculatedSize.Value);
Note

CalculateLayout() always overwrites transform.localPosition with the calculated layout position. See Synchronizing With Transforms to learn more.

UIBlock Positioning

Positioning content through Nova's Layout System differs from typical Transform positioning in that every UIBlock exposes an Alignment property, which defines a coordinate space for its Position relative to its Parent.

In doing so, the Layout System makes it quite easy to lock a child UIBlock at a fixed or relative offset from an edge of its Parent, even as its Size or its Parent's Size changes.

The following diagram on the right illustrates how changing horizontal Alignment impacts the interpretation of UIBlock.Position.X (which behaves analogously on the Y and Z axes):

Horizontal Alignment Position
Left Distance between left edge of parent and left edge of child.
Center Distance between center of parent and center of child.
Right Distance between right edge of parent and right edge of child.

Note that in this case, a positive position moves the child to the left while a negative position moves the child to the right.
Note

A UIBlock is positioned based on its LayoutSize and relative to its Parent's PaddedSize.

Synchronizing With Transforms

While the Layout System can typically honor positions assigned through a UIBlock's Transform directly (e.g. position and localPosition), there are a few situations where the Layout Position takes priority and will overwrite values assigned through the Transform. For this reason, general guidance for positioning Nova content via script or in an Animation Clip is as follows:

  1. When attempting to move UIBlock content within a UIBlock hierarchy, use UIBlock.Position.
  2. When attempting to move UIBlock content in the world, make your UIBlock.Root a child of a non-UIBlock object, and move that non-UIBlock object instead.

Following the guidance above is the easiest and most performant way to avoid any pitfalls or nuances when trying to use transform.localPosition and uiBlock.Position completely interchangeably.

NOTE

When uiBlock.Alignment is set to Center along a given axis, writing to UIBlock.Position will match the behavior of writing to Transform.localPosition.

// Align to center on all axes
uiBlock.Alignment = Alignment.Center;

// Assign a world position by converting it into a local position
uiBlock.Position = uiBlock.transform.parent.InverseTransformPoint(worldPosition);

Alternatively, if either modifying uiBlock.Alignment or setting uiBlock.Position to Value-type Lengths are out of the question, uiBlock.TrySetWorldPosition(worldPosition) and uiBlock.TrySetLocalPosition(localPosition) will convert the provided transform-space position into its equivalent uiBlock.Position while preserving the LengthType and Alignment. uiBlock.gameObject.activeInHierarchy must be true for this to work, otherwise both methods will exit early and return false.

if (uiBlock.TrySetWorldPosition(worldPosition))
{
    Debug.Log($"Converted position: {worldPosition} to Layout Position: {uiBlock.Position}!");    
}

Limitations with Using Transform Position

With all that in mind, should you still prefer to use transform.position/transform.localPosition as much as possible and only modify uiBlock.Position when absolutely necessary, here are the cases where writing to transform.position/transform.localPosition will not behave as expected because uiBlock.Position will be used instead:

Scenario Notes
Every first frame a UIBlock's GameObject is set active
  • If transform.localPosition is modified while a UIBlock's GameObject is disabled or on the first enabled frame and uiBlock.Position isn't modified to match, the object will be moved to its uiBlock.Position-prescibed location before it's rendered, not the assigned transform.localPosition.
  • Calls to uiBlock.TrySetWorldPosition(worldPosition) and uiBlock.TrySetLocalPosition(localPosition) can circumvent this limitation after uiBlock.gameObject.SetActive(true) is called (and assuming its parent hierarchy is active as well).
When a UIBlock's Parent's AutoLayout is enabled
  • Only transform.localPosition along the Parent's AutoLayout.Axis will be overwritten.
  • This is not unique to transform.localPosition, uiBlock.Position along the same axis will also be overwritten by the AutoLayout.
When a UIBlock is the direct child of a GridView
  • Calls to uiBlock.TrySetWorldPosition(worldPosition) and uiBlock.TrySetLocalPosition(localPosition) can circumvent this limitation for all axes except the cross axis.

Padding and Margin

Beyond setting just Size, Nova also allows you to specify a Padding and Margin for every UIBlock, which can have a wide variety of effects, depending on the scenario:

Scenario Effects
AutoSizing When AutoSizing, both Padding and Margin are factored in to the final CalculatedSize of the UIBlock being AutoSized, depending on the configuration:
  • Shrinking: accounts for the Padding of the UIBlock and the Margin of its children
  • Expanding: accounts for the Margin of the UIBlock and the Padding of its Parent
Non-Centered Alignment UIBlocks that are edge-aligned (left, right, top, bottom, front, and/or back) will be:
  • Aligned to the padded edge of their Parent
  • Offset by their Margin on the corresponding side
AutoLayout Padding can be used to modify the area in which a UIBlock will AutoLayout its children. Margin on the children is used to adjust the space between themselves and their adjacent siblings.
Note

Unlike CSS, Margin in Nova is purely additive for adjacent UIBlocks in a shared AutoLayout and does not collapse.

☀
☾
In This Article
Legal EmailContact Github
Copyright © 2022 Supernova Technologies, LLC