Skip to content

Layout

Default (centering) layout

By default, Scene and Group use a centering layout that places each child at the center of the container. This makes it easy to add a single centered element without manually computing coordinates:

with Scene():
    Rect().size(160, 90).color("steelblue")

Output image

fairyflow frame 0

Positioning

For absolute coordinates, proportional alignment, and cross-node positioning (including across groups), see Positioning.


Column layout

Call .column(gap, align, reserve) on a Group to stack its children vertically:

  • gap — vertical spacing between children in pixels (default 0)
  • align — horizontal alignment: 0.0 = left, 0.5 = center, 1.0 = right (default 0.5)
  • reserve — whether inactive children still occupy space (default True; see below)
with Scene():
    with Group().size(120, 180).column(gap=10, align=0.5):
        Rect().size(100, 40).color("steelblue")
        Rect().size(100, 40).color("coral")
        Rect().size(100, 40).color("mediumseagreen")

Output image

fairyflow frame 0

Left-aligned column with varying widths:

with Scene():
    with Group().size(200, 160).column(gap=8, align=0.0):
        Rect().size(180, 30).color("steelblue")
        Rect().size(120, 30).color("cornflowerblue")
        Rect().size(80, 30).color("lightskyblue")
        Rect().size(40, 30).color("aliceblue").stroke_color("steelblue").stroke_width(1)

Output image

fairyflow frame 0

Row layout

Call .row(gap, align, reserve) on a Group to place its children horizontally:

  • gap — horizontal spacing between children in pixels (default 0)
  • align — vertical alignment: 0.0 = top, 0.5 = center, 1.0 = bottom (default 0.5)
  • reserve — whether inactive children still occupy space (default True; see below)
with Scene():
    with Group().size(260, 90).row(gap=10, align=0.5):
        Rect().size(60, 60).color("tomato")
        Rect().size(60, 60).color("gold")
        Rect().size(60, 60).color("mediumseagreen")
        Rect().size(60, 60).color("steelblue")

Output image

fairyflow frame 0

Bottom-aligned row with varying heights:

with Scene():
    with Group().size(240, 120).row(gap=8, align=1.0):
        Rect().size(40, 30).color("lightskyblue")
        Rect().size(40, 60).color("cornflowerblue")
        Rect().size(40, 90).color("steelblue")
        Rect().size(40, 60).color("cornflowerblue")
        Rect().size(40, 30).color("lightskyblue")

Output image

fairyflow frame 0

Nested layouts

Groups can be nested to build complex grids and hierarchies:

with Scene(width=300, height=200):
    with Group().size(280, 160).row(gap=10, align=0.5):
        with Group().size(80, 160).column(gap=8, align=0.5):
            Rect().size(80, 48).color("steelblue")
            Rect().size(80, 48).color("cornflowerblue")
            Rect().size(80, 48).color("lightskyblue")
        with Group().size(80, 160).column(gap=8, align=0.5):
            Rect().size(80, 48).color("tomato")
            Rect().size(80, 48).color("coral")
            Rect().size(80, 48).color("lightsalmon")
        with Group().size(80, 160).column(gap=8, align=0.5):
            Rect().size(80, 48).color("mediumseagreen")
            Rect().size(80, 48).color("lightgreen")
            Rect().size(80, 48).color("honeydew").stroke_color("mediumseagreen").stroke_width(1)

Output image

fairyflow frame 0

The reserve parameter

When items appear or disappear across frames, column and row layouts need to decide whether absent children still take up space.

reserve=True (default) — every child always occupies its full size in the layout, even when it is not yet visible or has already been removed. This keeps the positions of all siblings stable: nothing shifts when a new item appears or an old one disappears.

with Scene(width=200, height=120):
    with Group().column(gap=10):   # reserve=True by default
        Rect().size(160, 30).color("steelblue")
        next_frame()
        Rect().size(160, 50).color("coral")

Frame 0

fairyflow frame 0

Frame 1

fairyflow frame 1

reserve=False — only currently active children occupy space. The layout shrinks and grows as items come and go, so siblings recentre or reflow on each frame.

with Scene(width=200, height=120):
    with Group().column(gap=10, reserve=False):
        Rect().size(160, 30).color("steelblue")
        next_frame()
        Rect().size(160, 50).color("coral")

Frame 0

fairyflow frame 0

Frame 1

fairyflow frame 1

Use reserve=True when you want a stable layout where items "drop in" to their final position without displacing neighbours. Use reserve=False when you want the group to tightly fit its visible children at every frame.


Layout and animation

Groups with column or row layout can still be animated — position, rotation, scale, alpha, and the clipping window all work as usual. The layout controls where children are placed; animation moves or transforms the group as a whole.

with Scene():
    with Group().size(220, 70).align_x(0.5).align_y(0.5).row(gap=8, align=0.5) as g:
        Rect().size(60, 50).color("steelblue")
        Rect().size(60, 50).color("coral")
        Rect().size(60, 50).color("gold")
    g.rotate(180, tr=1.5)

Output video