Class rtk.Container

Class Hierarchy

In rtk, containers are special widgets that can hold one or more other widgets (called children) and manage their position and dimension according to the semantics of the specific container type. Containers can be nested to create complex layouts. And because containers are themselves widgets, the attributes, methods, and event handlers defined by rtk.Widget are all applicable here as well.

rtk.Container is the simplest type of container, and serves as the base class to all other container widgets, and so other containers also offer at least this interface. It is a generic, unopinionated container that expects children to specify their geometry (relative to the parent container position).

Children of containers are placed into cells. A cell is that portion of the container that has been allocated to one child widget. In an rtk.Container, cells given to child widgets are independent and don't affect one another. Subclasses can and do change this aspect. For example, box containers layout children adjacent to each other, so prior siblings will affect the position of later siblings.

Containers ask their children to constrain their dimensions so that all children can fit within the container (whether the container has an explicit size, or its own bounding box specified by its parent), but sometimes children can ignore these constraints, for example if you have specified an explicit size of a child widget that's larger than the container can hold without itself overflowing. Containers don't clip their children, rather it's up to the child widgets to clip themselves if necessary (e.g. rtk.Text.overflow).

As with all widgets, container geometry (coordinates and dimensions) can be static values, or specified relative to the parent (e.g. 80% of parent width).

When no explicit width or height is set, a container's intrinsic size is dictated by the children contained within it.

Example
-- Create a new container that's 50% the width of its parent.
local c = rtk.Container{w=0.5}
-- This centers the label within the button but doesn't affect its
-- position within the container because it's an attribute on the
-- widget itself, not a cell attribute.
local b = rtk.Button{label='Hello world', halign='center'}
-- Ask the button to fill its container.
c:add(b, {fillw=true})
-- rtk.Windows are themselves rtk.Containers.  This adds the container
-- to the window, and because the container has w=0.5, it will end up
-- having a width that's 50% of the window's widget (its parent).
-- Meanwhile, these alignment cell attributes center the container
-- within the window both horizontally and vertically.
window:add(c, {halign='center', valign='center'})

Box Model With Containers

The diagram below depicts how the widget box model works in the context of containers, using a 200px fixed height container. The blue area represents the cell, while the purple box is the widget itself.

The following code generated the above image (minus the dotted border around the text which was added later as a visual aid to denote the boundary of the inner content):

local c = window:add(rtk.Container{h=200, border='4px black'})
local text = rtk.Text{'intrinsic\nsize', halign='center', padding=20, bg='purple', border='violet'}
c:add(text, {bg='cornflowerblue', padding=30})

Cell Alignment

With rtk.Container, cells are all overlaid one atop the other, with either widget margin or cell padding affecting the position of each cell.

Unless the container is given an explicit size, alignment is done relative to the current size of the container based on all previous siblings. For example:

local c1 = rtk.Container()
-- Makes a big orange box
c1:add(rtk.Spacer{w=0.5, h=0.5, bg='orange'})
-- Because the rtk.Spacer widget was first, this button will be
-- aligned relative to the previous widget, which dictates the
-- current intrinsic size of the container when the button is
-- reflowed.
c1:add(rtk.Button{'Hello'}, {valign='center', halign='center'})

-- Meanwhile, let's reverse the order.
local c2 = rtk.Container()
-- Add the button first, but use a higher z-index so it's drawn above
-- the rtk.Spacer.  But here the center alignment is relative to the
-- current container size, which is empty.  So it ends up being top/left
-- of the container.
c2:add(rtk.Button{'Hello', z=1}, {valign='center', halign='center'})
-- Now the spacer stretches out the container further, but doesn't affect
-- the alignment of the button, which was already decided.
c2:add(rtk.Spacer{w=0.5, h=0.5, bg='orange'})

Cell attributes

When adding a child to a container (e.g. via add()), you can optionally set additional attributes that control how the container will layout that specific child widget within its cell.

These are the base layout attributes for all containers, but specific container implementations usually include additional ones, or may extend the possible values for these cell attributes, or change the semantics.

fillw boolean

If true, the child widget is asked to fill its width to the right edge of the container (unless the child itself has an explicitly defined width in which case this cell attribute is ignored).

fillh boolean

Like fillw but for height, where the child widget is asked to fill its height to the bottom edge of the container.

halign alignmentconst

One of the alignment constants (or a corresponding string, e.g. 'center') that defines how the child widget will be horizontally aligned within its cell.

Note

Cell alignment is distinct from widget alignment because it controls how the container positions the widget within its cell, but doesn't affect the visual appearance of the widget itself, while the alignment attributes defined on child widgets controls how the child displays its contents within its own box.

When halign is specified as an attribute on the container widget overall, it affects the default horizontal alignment of all cells. Cells can override this default by explicitly specifying the halign cell attribute.

valign alignmentconst

Like halign but for vertical alignment

padding number, table or string

The amount of padding around the child widget within the cell. This is equivalent to widget margin and if both are defined then they add together. Value formats are also the same as rtk.Widget.padding.

tpadding number

Top cell padding in pixels; if specified, overrides padding for the top edge of the cell (default 0).

rpadding number

Right padding in pixels; if specified, overrides padding for the right edge of the cell (default 0).

bpadding number

Bottom padding in pixels; if specified, overrides padding for the bottom edge of the cell (default 0).

lpadding number

Left padding in pixels; if specified, overrides padding for the left edge of the cell (default 0).

minw number or nil

The minimum cell width allowed for the child widget. This doesn't necessarily mean the widget will be this width, rather just that the container will allow the widget at least this amount of space. To ensure the widget itself is at least this width, also specify fillw.

Relative values are supported, so for example a value of 0.5 indicates the child widget in this cell should be offered a box width that is 50% of the containers own width.

This is distinct from the minw widget attribute in that the minw cell attribute dictates how much space the container offers to the child (its box), while rtk.Widget.minw controls how much of that box it will use. In other words, the two combine together. For example, if the minw cell attribute is 200, and the minw widget attribute is 0.5, then the effective minimum width for the widget will be 100px.

minh number or nil

Like minw but for height.

maxw number or nil

The maximum cell width allowed for the child widget. Unless the widget explicitly defines a larger width for itself, it is expected to shrink or clip as needed to fit within this value.

As with minw, relative values are supported and may be combined with the maxw widget attribute.

maxh number or nil

Like maxw but for height.

bg colortype or nil

Background of the cell or nil for transparent. This is different from rtk.Widget.bg in that cell padding (or widget margin) is also colored. Opacity of the color is respected, if specified.

z number

The z-index or "stack level" of the child widget, which is exactly equivalent to rtk.Widget.z. If defined, it will override rtk.Widget.z. Widgets at the same z-index will be drawn in the order they were added to the container, so that widgets added later will appear above those added before.

The z-index doesn't affect the order widgets are reflowed, only drawn.

Class API

Synopsis

Attributes
children table

read-only

An array of child widgets, where each element in the array is {widget, cell attributes table}

Methods
rtk.Container()

Create a new container, initializing with the given attributes

add()

Adds a widget to the container

update()

Updates the cell attributes of a previously added widget

insert()

Adds a widget to the container at a specific position

replace()

Replaces the widget at the given position

remove_index()

Removes a widget by position

remove()

Removes a widget from the container

remove_all()

Empties the container

reorder()

Moves an existing child widget to a new index, shifting surrounding widgets

reorder_before()

Moves an existing child widget ahead of another child

reorder_after()

Moves an existing child widget after another child

get_child()

Returns the widget at the given index

get_child_index()

Returns the position of the given widget

Attributes

rtk.Container.children table read-only

An array of child widgets, where each element in the array is {widget, cell attributes table}

Methods

rtk.Container(attrs, ...)

Create a new container, initializing with the given attributes.

Any positional arguments passed will be automatically added as children of the container via add(). In this case, cell attributes can optionally be specified via a cell field on the child widget.

Example
local c = rtk.Container{
    bg='red',
    padding=10,
    w=200,
    rtk.Text{'Some Random Text'},
    rtk.Button{'I Do Nothing', cell={halign='center'}},
}

Which is equivalent to:

local c = rtk.Container{bg='red', padding=10, w=200}
c:add(rtk.Text{'Some Random Text'})
c:add(rtk.Button{'I Do Nothing'}, {halign='center'})

This makes it possible to specify a full widget hierarchy for the entire window (or arbitrary subsections of it) using a single declaration of nested widgets (otherwise known as an S-expression).

rtk.Container:add(widget, attrs)

Adds a widget to the container.

The cell attributes table is optional, and may be passed either as the second argument, or as the cell field in the widget instance itself.

-- Given this box ...
local box = rtk.HBox()
-- ... these two lines are equivalent
box:add(rtk.Text{'Hello World'}, {halign='center', valign='center'})
box:add(rtk.Text{'Hello World', cell={halign='center', valign='center'}})

The second form, while slightly more verbose, may be preferred when you want to dictate the cell behavior at widget instantiation time, rather than during add().

Parameters
widget (rtk.Widget)

the widget to add to the container

attrs (table or nil)

the cell attributes to apply to the given widget

rtk.Container:update(widget, attrs, merge)

Updates the cell attributes of a previously added widget.

Parameters
widget (rtk.Widget)

the widget whose cell attributes are to be updated

attrs (table or nil)

the new cell attributes

merge (bool or nil)

if false (default), the cell attributes will be completely replaced with the given attrs, otherwise they will be merged such that previous cell attributes will be preserved unless overridden in attrs.

rtk.Container:insert(pos, widget, attrs)

Adds a widget to the container at a specific position.

For rtk.Container containers, it isn't really necessary to insert widgets at specific positions as the z-index more easily controls display order. But for subclasses such as boxes where widget order is visually relevant and independent of z-index, this method can be used to control the layout of widgets relative to one another.

Parameters
pos (number)

the position of the widget, where 1 inserts at the front of the list.

widget (rtk.Widget)

the widget to insert

attrs (table or nil)

the cell attributes to apply to the given widget

rtk.Container:replace(index, widget, attrs)

Replaces the widget at the given position.

The existing widget at the given index is unparented from the container and the new widget is added in its place.

Parameters
index (number)

the position of the widget, where 1 is the first widget.

widget (rtk.Widget)

the widget to add to the container that ejects the existing widget at index.

attrs (table or nil)

the cell attributes to apply to the given widget

Return Values
(rtk.Widget)

the old widget that was removed and replaced by the given one, or nil if the index was out of bounds

rtk.Container:remove_index(index)

Removes a widget by position.

Parameters
index (number)

the widget position to remove, where 1 is the first widget.

Return Values
(rtk.Widget)

the child that was deleted, or nil if index was out of bounds

rtk.Container:remove(widget)

Removes a widget from the container.

Parameters
widget (rtk.Widget)

the widget to remove

rtk.Container:remove_all()

Empties the container.

rtk.Container:reorder(widget, targetidx)

Moves an existing child widget to a new index, shifting surrounding widgets.

Parameters
widget (rtk.Widget)

the widget to be repositioned.

targetidx (number)

the new position for the widget, where 1 is the first cell in the container, 2 is the second cell, and so on. Out-of-bounds indexes are clamped. If the target index is the same as the widget's current index then no action is taken.

Return Values
(boolean)

true if the widget if the widget changed positions, or false if the widget was already at targetidx or if the given widget is not in this container

rtk.Container:reorder_before(widget, target)

Moves an existing child widget ahead of another child.

Parameters
widget (rtk.Widget)

the widget to move in front of the target

target (rtk.Widget)

the other child

Return Values
(boolean)

whether the widget changed positions

rtk.Container:reorder_after(widget, target)

Moves an existing child widget after another child.

Parameters
widget (rtk.Widget)

the widget to move after the target

target (rtk.Widget)

the other child

Return Values
(boolean)

whether the widget changed positions

rtk.Container:get_child(idx)

Returns the widget at the given index.

Parameters
idx (number)

the widget position, where 1 is the first widget.

Return Values
(rtk.Widget or nil)

the widget at the given index, or nil if the index is out of range.

rtk.Container:get_child_index(widget)

Returns the position of the given widget.

Parameters
widget (rtk.Widget)

the widget whose position to fetch

Return Values
(number or nil)

the index of the given child widget where 1 is the first position, or nil if the child could not be found.