Skip to content

SGUI Layout System

Person8880 edited this page Sep 2, 2023 · 9 revisions

Overview

The layout system provides an easy way to layout controls without having to manually provide a bunch of positions and sizes. It works using the same principles most layout systems use, i.e. paddings, margins and different sizing units.

Sizing Units

Units can be found under SGUI.Layout.Units.

Spacing

Defining padding and margins requires using the Spacing unit. This unit takes 4 single-value units representing the left, up, right and down respectively. Number values are auto-converted to instances of the Absolute unit.

local Padding = Spacing( 0, 5, 0, 5 )

A Spacing.Uniform helper is provided as a shortcut when every direction is identical:

-- This is equivalent to Spacing( 5, 5, 5, 5 )
local Padding = Spacing.Uniform( 5 )

Unit Vector

To define sizes using units, make use of the UnitVector unit. Number values are auto-converted to instances of the Absolute unit.

local AutoSize = UnitVector( Percentage( 100 ), 5 )

A UnitVector.Uniform helper is provided as a shortcut when both directions are identical:

-- This is equivalent to UnitVector( 5, 5 )
local AutoSize = UnitVector.Uniform( 5 )

Single-value Units

The default set of single-value units are:

  • Absolute - Represents an absolute value. Does not change the input value.
  • Auto - Represents automatic sizing based on the contents of the element. This requires the control using it to implement GetContentSizeForAxis( Axis ) (see the auto_sizable_text.lua mixin for an example).
    • Labels implement this by returning the width/height of their text.
    • Text entries implement this for the Y-axis by returning the height of the font they are configured with.
    • Any control using a directional layout (horizontal/vertical layouts) implement this using the layout's known content size, calculated during layout.
    • Controls with no layout implement this by adding the size of all children along the given axis.
  • GUIScaled - Takes the input, and calls SGUI.LinearScale() on it when being computed.
    • This used to use GUIScale, but that gets modified by HUD scaling options and thus is no longer used.
  • HighResScaled - When the screen resolution is larger than 1920x1080, this will call SGUI.LinearScale() on the value when being computed. If the screen resolution is 1920x1080 or less, it just returns the value with no scaling.
  • Integer - Wraps another unit, returning its value as an integer (rounded up).
  • Max - Returns the maximum value out of all units it is constructed with. Allows for defining constraints on sizes, such as the maximum width out of two auto-sized buttons.
  • Min - Returns the minimum value out of all units it is constructed with.
  • MultipleOf2 - Wraps another unit, returning the nearest multiple of 2 (rounded half-up) to the value computed from the wrapped unit value.
  • Scaled - Takes a value and a scale. When computed, returns Value * Scale.
  • Percentage - Represents a percentage based on the size of the parent on the axis the value is provided for. That is, Percentage( 80 ) would represent 80% of the parent's width if used as a width, or 80% of the parent's height if used as a height.
  • OppositeAxisPercentage - Represents a percentage based on the size of the parent on the opposite axis. That is, OppositeAxisPercentage( 80 ) would represent 80% of the parent's height if used as a width, or 80% of the parent's width if used as a height.
  • PercentageOfElement - Represents a percentage of another element's size, usually an ancestor. The axis by default matches the axis the value is used with, but can be overridden if desired.

Using a Layout

Shine comes with two layouts by default, Horizontal and Vertical. You are free to compose layouts inside each other to create whatever nested layout you wish.

Generally, layouts are intended to be used directly against a given control, or as nested children where there is no need for an additional element (e.g. for static content with no scrolling).

The Row and Column controls provide a convenient element wrapper around horizontal and vertical layouts respectively which can be used as children of scrollable panels.

With SGUI:BuildTree()

In most cases, SGUI:BuildTree is the easiest way to work with layouts (and elements in general). To add a layout, specify the Type as "Layout" and then Class as the desired layout direction, e.g.

SGUI:BuildTree( {
    Parent = self,
    {
        Class = "Vertical",
        Type = "Layout",
        Props = {
            Padding = Spacing.Uniform( HighResScaled( 8 ) )
        },
        Children = {
            -- Add more layouts or elements as children of the layout...
        }
    }
} )

Manual Construction

To manually construct a layout, create it as follows:

local Layout = Shine.GUI.Layout:CreateLayout( Name, ParametersTable )

The parameters table should look something like this:

{
    Elements = {}, -- A table containing your objects/layouts that live inside this one
    Pos = Vector2( 0, 0 ), -- Usually, position can be left out, as it will be dependent on padding.
    AutoSize = UnitVector( Percentage( 100 ), Percentage( 100 ) ), -- AutoSize determines how to size this layout when it's part of another layout.
    Size = Vector2( 0, 0 ), -- Sets the absolute size of the layout, usually determined by the parent.
    Margin = Spacing( 0, 0, 0, 0 ), -- Sets the space between this layout and other elements of its parent layout.
    Padding = Spacing( 0, 0, 0, 0 ), -- Sets how much space to add inside the layout.
    Parent = Element, -- Sets the layout's parent element.
    Fill = true, -- Sets whether the layout should fill the space of its container, if omitted this defaults to true.
    Alignment = SGUI.LayoutAlignment.MIN, -- Sets the alignment of this layout. Applies only if the layout is nested inside another layout.
    CrossAxisAlignment = SGUI.LayoutAlignment.MIN -- Sets the alignment on the cross-axis. Applies only if the layout is nested inside another layout.
}

To assign a layout to an SGUI control, just use:

Control:SetLayout( Layout )

The control will now manage the layout. The layout will inherit the control's size (minus any padding set), and whenever the control's layout is invalidated, so too will the layout object's.

Element Sizing

Elements inside a layout can have their size updated dynamically. This is achieved through one of two ways.

Fill Elements

Setting an element to fill, by calling Control:SetFill( true ) (or setting the Fill = true prop) means that it will take up any remaining space in a layout once all other non-filling elements have been positioned. If there are multiple fill elements in a layout, then they will evenly split the remaining space between them, so that they are all the same size.

Auto-sized Elements

Alternatively, you can specify a dynamic size for an element by setting its AutoSize property. This should be a UnitVector containing your size units. For example:

Control:SetAutoSize( UnitVector( Percentage.ONE_HUNDRED, GUIScaled( 32 ) ) )

would set the control to take 100% of the layout width, and be GUIScale( 32 ) units tall, where the scale is calculated at the moment the layout is.

Fixed-size Elements

If you do not set a control to fill or have an automatic size, then it will not have its size altered by the layout.

Alignment

Layouts also support alignment. There are currently 3 forms of alignment for layouts: MIN, MAX and CENTRE.

For vertical, MIN means the element will be positioned from the top, while MAX means it will be positioned from the bottom. For horizontal, MIN means the left and MAX means the right. CENTRE starts from the middle of the given direction, aligning elements symmetrically around the central point.

To set a control or layout's alignment, use:

Control:SetAlignment( SGUI.LayoutAlignment.MIN )

or set the Alignment prop when building the tree.

Cross-Axis Alignment

In addition to aligning along the main axis of a layout, the alignment along the "cross-axis" (the opposite direction) can be specified. The same alignment enum is used with the CrossAxisAlignment property, e.g.

Control:SetCrossAxisAlignment( SGUI.LayoutAlignment.CENTRE )

Layout Invalidation

The layout system centres around invalidation. At a given moment, the layout of an object is considered valid until it is invalidated by a call to Control:InvalidateLayout(). When this call is made, the control will run its PerformLayout() method on the next frame.

If you want a control or a layout to update itself immediately, use Control:InvalidateLayout( true ). There's also Control:InvalidateParent( [true] ) for a shortcut to invalidating the parent's layout.

Most major property changes will already cover layout invalidation. Changing an element's size, its padding or margin, or adding/removing elements from a layout all trigger invalidation for you.

Clone this wiki locally