Guides | GUI > Layout

Layout Management

Using layout classes to manage distribution of child views within parents

The purpose of layouts is to distribute the amount of space given to the view on which they are installed among the children of that view. Each subclass of Layout has a specific pattern of space distribution (a line, a 2D grid, etc.). See their documentation for details.

A layout is installed on a view to manage the space the view occupies and distribute it among its child views. These child views can in turn have other layouts installed, managing the space given to them. But a layout can also manage other layouts directly - one layout can directly occupy a place in another layout's distribution pattern. A basic unit on which a layout operates is therefore abstractly called an item and can be a view or another layout.

NOTE: While layouts can form a hierarchy on their own, in terms of view hierarchy all views managed by those layouts are direct children of the view on which the top layout is installed.

The following is an example of a VLayout, organizing a series of TextFields in a vertical line, and its last item is a HLayout, organizing a series of Buttons in a horizontal line.

(
w = Window(bounds:Rect(200,200,200,200)).layout_(
    VLayout(
        TextField(), TextField(), TextField(),
        HLayout( Button(), Button(), Button() )
    )
).front;
)

How a layout does its job

A layout does its job by resizing and moving items within the parent view to form a specific distribution pattern, in accord with items' own size preferences and constraints, and with the common sense of what makes the GUI useful.

To explain how the layout operates, let's first take a look at intrinsic size preferences and constraints of views:

Intrinsic view sizes

Every view intrinsically has a preferred size and a minimum size, returned by View: -sizeHint and View: -minSizeHint, respectively. The preferred size is a suitable size for the view to comfortably draw itself, display its contents and allow interaction, while the minimum size is the absolute minimum that the view needs to properly draw itself (including its contents). Both may change when the view's contents change; for example, most views that display some text will report a different sizeHint when the text changes (unless the text is scrollable).

A view takes on its preferred size at construction, if the 'bounds' argument is omitted. However, there is usually no way to set the text on a view at construction, so the size it automatically gets will not reflect the changes in text done after construction. You can remedy that by resizing the view to its sizeHint after the text has been set - but the purpose of layouts (explained below) is exactly to do that automatically for you. Here is an example of manually using the sizeHint:

// Create a Window with a StaticText
(
w=Window().alwaysOnTop_(true);
t=StaticText(w);
w.front;
)

// There's no text set yet. Post the sizeHint of StaticText:
t.sizeHint

// Set the text, and post the sizeHint again:
t.string_("This is a looooooong text");
t.sizeHint

// Adjust the size to the sizeHint:
t.bounds = t.bounds.size_(t.sizeHint);

// Now you can see the whole text

As their names suggest, sizeHint and minSizeHint are only hints, and do not prevent one from setting a different size. You can, however, set a hard limit on the size using View: -minSize, View: -maxSize and similar methods:

x=View(bounds:Rect(30,30,100,100)).alwaysOnTop_(true).front;

// Set the minimum size limit:
x.minSize = Size(200,200);

// The view automatically resized to the minimum size.
// Now try to shrink it back:
x.resizeTo(100,100);

// The view did not allow a smaller size than minSize.

Automatic and dynamic space distribution

A layout automatically distributes its space among its items, possibly in unequal parts, based on the intrinsic preferences and constraints of views described above. Moreover, views also have intrinsic preferences as to whether they profit from being extended horizontally or vertically, or whether they prefer their size to be fixed in a certain direction. This is also taken into account by a layout.

A layout works dynamically, meaning that it redistributes the space whenever the amount of it changes (the view on which it is installed or the parent layout is resized), whenever items are added or removed, and whenever the size constraints and preferences of items change. The latter may happen for instance when a property of a view that affects its appearance is changed.

A layout will affect space distribution up the layout hierarchy - it will define its own constraints and preferences according to its distribution pattern as well as the sum of constraints and preferences of its items. Ultimately, this means that the user's ability to resize a window will be limited by size constraints determined on the basis of window's contents.

For example: in a HLayout (a layout organizing items in a horizontal line) containing a Button and a TextField, the Button will be given a fixed amount of width according to the text it displays, while the TextField will be given all the width that is left. The other Button in the example code below will occupy all the width of the window, since there is no other item competing for that particular space. Note that both Button and TextField have an intrinsically fixed height and so their height never changes when resizing the window. The size constraints also limit the minimum size that the window can be resized to.

(
w = Window.new(bounds:Rect(100,100,300,80)).layout_(
    VLayout (
        HLayout(
            Button().states_([["Super"]]),
            TextField().string_("Collider")
        ),
        Button().states_([["SuperCollider"]])
    )
).front;
)

User customization

Stretch factors

Layouts typically allow the user to override their default distribution policy by assigning stretch factors to items or aspects of the layout's distribution pattern.

(
w = Window.new(bounds:Rect(100,100,400,80)).layout_(
    HLayout(
        [Button().states_([["Super"]]), stretch:1],
        TextField().string_("Collider")
    )
).front;
)

Size constraints

The user can override a view's intrinsic size constraints and preferences that the layout will take into account, by placing a hard-limit on a view's size, as described above.

(
w = Window.new(bounds:Rect(100,100,300,300)).layout_(
    VLayout(
        TextField().string_("Super").minHeight_(80),
        TextField().string_("Collider").maxWidth_(150)
    )
).front;
)

The minimum size of a view is especially important for UserViews which doesn't have by default a minimum size. In the example the first UserView is invisible.

(
w = Window.new(bounds:Rect(100,100,200,200)).layout_(
    VLayout(
        UserView.new.background_(Color.rand),
        TextField().string_("Super").maxWidth_(100),
        UserView.new.background_(Color.rand).minSize_(200@200),
        TextField().string_("Collider").maxWidth_(100)
    )
).front;
)

Alignment

The combination of size constraints and preferences of all items in a layout hierarchy may result in a larger amount of space given to an item than its own constraints allow. In that case the item will only grow up to its maximum allowed size, and its position within its extra available space may be controlled by user by assigning alignment to an item.

(
w = Window.new.layout_(
    HLayout(
        [Button.new.states_([["Super"]]), align:\bottom],
        TextView(),
        [Button.new.states_([["Collider"]]), align:\top]
    )
).front;
)

View vs. layout hierarchies

A layout starts to operate on the space that a view occupies from the moment it is installed on that view on. However, it will not automatically affect child views that where created before the layout was. For views to be managed by a layout they have to be created as children of a view after the layout has been installed on it, or they have to be explicitly inserted into the layout via layout's constructor or its instance methods for this purpose.

View constructed with a parent

When a view is created with another view as parent it will implicitly become subject to the management of the parent's layout - it will be inserted into the layout in some default way. However, layouts like GridLayout have a complex space distribution pattern and so you will need to use their dedicated methods to specify exactly what place in the layout's distribution pattern a view will occupy.

View explicitly inserted into a layout

A view can also be constructed with no parent given; after it is explicitly inserted into a layout via the layout's constructor or an instance method, it will automatically become a child of the view on which the layout is or will be installed. In case the layout occupies place directly in another layout, the view will become a child of the view on which the topmost layout is installed.