Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Layout Phase

The layout phase is responsible for computing the size and the positions of the widgets. This computation is based on the layout constraints of the widgets.

Every widget has an associated layout constraint. A layout constraint defines:

  • The minimum size in pixels of the widget.
  • The padding in pixels that separates the widget boundaries from the contained widgets.
  • The gap in pixels that separates adjacent contained widgets.
  • The arrangement of contained widgets: either horizontal or vertical.
  • The width and height eighths that determine how much of the remaining space the widget will take.

The Figure 1 shows an example of layout constraints. In this example, the size of the widget fits its content. The contained widgets are arranged horizontally.

A widget with two children arranegd horizontally and separated from each other by a gap. The widget has also some space around its children called padding.
Figure 1: Widget layout constraint.

The layout phase consists in two steps. The first step computes the minimum sizes of every widget. It traverses the widget tree in a bottom-up fashion. The second step distributes the remaining space to the widgets and computes their absolute positions. It traverses the widget tree in a top-down fashion.

The minimum size of a widget corresponds to the component-wise maximum between its fitting size and the minimum size defined in its layout constraint. The fitting size corresponds to the size required to fit the content. In the Figure 1, the fitting width of the widget is equal to the sum of the widths of its two contained widgets, the gap, and twice the padding. The fitting height is equal to the maximum between the heights of its contained widgets plus twice the padding.

If a widget has nonzero width and height eighths, then it takes some of the remaining space of its parent widget. For a horizontal arrangement, the remaining width is the difference between the parent width and the parent fitting width. The remaining height is the difference between the parent height and the widget height. The root widget has an implicit parent that is the display.

The absolute positions are computed by simply taking the sizes of the widgets, the padding and gap between them.

Let’s take the widget tree in Figure 3. To simplify their designations, all widgets are indexed from 0 to 5. Their layout constraints are enclosed between parentheses. The Figure 3 shows the computed sizes and positions for a display size of 160x160.

└─ [0] Container (vertical, padding: 8, gap: 8, widthEighth: 8, heightEighth: 8)
   │
   ├─ [1] Container (horizontal, gap: 8, widthEighth: 8)
   │  │
   │  ├─ [2] TextField (minSize: 56x0, widthEighth: 4, heightEighth: 8)
   │  │
   │  └─ [3] Button (padding: 4)
   │     │
   │     └─ [4] Label "Search" (minSize: 48x8)
   │
   └─ [5] Container (widthEighth: 8, heightEighth: 8)
Figure 2: Widget tree.
WidgetMinimum sizeFinal sizePosition
0136x40160x160(0,0)
1120x16144x16(8,8)
256x068x16(8,8)
356x1656x16(88,8)
448x848x8(92,12)
50x0144x120(8,32)
Figure 3: Computed sizes and positions.

State of the Art

Cursor-Based Layout

In cursor-based approach, a widget is drawn at the current cursor position, then the cursor is moved to a new position.

The main disadvantage of this approach is that you cannot easily create responsive user interfaces. You have to manually compute the positions of the widgets. It also inherently imperative that prevents to perform some optimizations: It is not possible to easily render only the widgets that have changed and those that intersect the same lines. This doesn’t suit well retained modes.

This approach is often used by immediate mode libraries to avoid a layout phase entirely.

Windows Presentation Foundation (WPF)

WPF uses a two-pass approach: a measure pass and an “arrange” pass. The first pass computes the sizes of the widgets passing down the available space. The second pass computes the final sizes and the widget positions.

Flutter

Flutter lays out widgets by recursively traversing the tree. Each widget receives its size constraints from its parent, determines its size and the position of its children.

Size constraints are thus propagated from top to bottom. A widget derives the size constraints of each child from its own size constraints. The maximum size of the root corresponds to the available space. Size constraints are strict: each widget has to choose a size that fits its constraints. This is a key design decision that avoids backtracking and thus ensures a linear complexity. One consequence of this decision is that a widget can impose a size to a child. For example, a checkbox can be forced to take a larger size than its intrinsic size. In such cases, the widgets are often rendered using their intrinsic sizes and is padded with the remaining space. In practice, Flutter can backtrack when it lays out text. However, this is isolated to specific parts of the widget tree.

Once its children have computed their size, a widget computes its own size and places its children. Flutter uses relative positions. Thus, it doesn’t need to know the position of a widget to place its children. This also avoids recomputing the layout when scrolling: only an offset is updated. Also, Flutter has a concept of layout boundaries which allows isolating layout re-computations.

Flutter allows distributing the remaining space among the flexible children. The remaining space is determined by first visiting non-flexible children. Each flexible child defines a flex factor that determines how much of the remaining space it takes.

Slint

At first glance, Slint uses a system that looks like FLutter: Size constraints go from top to bottom, sizes and positioning go from bottom to top.

However, Slint uses a different architecture. While Flutter uses an explicit layout phase, Slint uses a reactive system. Sizes and positions are lazily computed and re-computed. Also, Slint leverages its compiler to perform some layout work at compilation time.

Web

The web uses a very complex layout system that use backtracking. Thus, the layout phase is usually not linear.

Constraint Solvers

More complex layout engines allow expressing rich constraints between widgets. So, they use solvers such as SMT solvers to lay out the widgets.