DEV Community

Cover image for UI Engine Layout: Additive, Partitioned and Layered
edA‑qa mort‑ora‑y
edA‑qa mort‑ora‑y

Posted on • Originally published at mortoray.com

UI Engine Layout: Additive, Partitioned and Layered

Layout determines how children are positioned and sized within their parent element. There are three approaches to placing child elements within a parent:

  • Additive: The children combine to define the size of the parent
  • Partitioned: The parent's space is divided among the children
  • Layered: The children are independent of each other, all occupying the same area of the parent

Here we'll cover the basics of each of these approaches.

For now we're considering application layout, which serves a different need than document, or text, layout. Rich text uses a flow-based layout, which is suitable for documents but not for application structure. It is a distinct approach from those listed here, and we'll get back to it eventually.

This article is part of a series on Writing a UI Engine.

Additive - Simple

A stack panel is the most common, and most straightforward to understand, additive control. The size of the stack panel is the cumulative size of all its children.

StackPanel{Alignment="Bottom"}
    ChildA
    ChildB
    ChildC
Enter fullscreen mode Exit fullscreen mode

Assuming this is a vertical panel, the height of the StackPanel is the sum of the height of all three children. As we add more children the height increases; as we remove children the height decreases.

Additive layouts create elements that have a natural size, in one dimension or two. These layouts allow building components that gain a size from their children.

The creation of a StackPanel element is optional. In the API I implemented, a StackPanel was a normal panel with a StackLayout attached to it. The use of a generic panel with layout implementations is a good code structure. Having high-level wrapping types like StackPanel can, however, be a friendly choice for coders in the framework. There is a bit of a battle between code cleanliness and API friendliness.

Partitioned - Simple

A grid is the most common partitioned control. In a basic form, it divides the available space among its children. The goal of this type of layout is to ensure the children each get an appropriate piece of the screen and don't overlap.

Grid {RowCount="2" ColumnCount="2"}
    ChildA
    ChildB
    ChildC
    ChildD
Enter fullscreen mode Exit fullscreen mode

This example grid will create four cells of equal size. Each of those children may then perform layout within the assigned area.

A partitioning layout may consider the size of the children, and need not divide space equally.

Grid {RowCount="2" Columns="auto,1*,2*"}
    Text {Value="Short"}
    Child11
    Child12

    Text {Value="Longer Text"}
    Child21
    Child22
Enter fullscreen mode Exit fullscreen mode

Assuming row-major ordering, the first column contains the text Short and Longer Text. The grid will determine the size of these elements, taking the wider of the two as the column width. The remaining space in the display will be divided between the two other columns, with the third column being twice as wide as the second.

A DockPanel is another familiar partitioned layout.

DockPanel
    TitleBar {Dock="Top"}
    ActionBar {Dock="Bottom"}
    PrimeChild
Enter fullscreen mode Exit fullscreen mode

The TitleBar takes away space from the top of the control, and the ActionBar takes away space from the bottom of the control. Like the auto column on the grid, we're using the size of the child here to reduce the available space. This type of partitioning, the only kind a DockPanel offers, is a subtractive layout: each successive child takes away more space, leaving less for the following children. The final child gets the remaining space.

Layered

In a layered layout, the children are considered independent of another. No child influences the layout of the other children, nor do they add or subtract from the available space. It's up to the UI coder to position the children correctly, including the Z-ordering of the potentially overlapping children.

Panel
    Text {Alignment=TopLeft Value="User Portrait"}
    Icon {Alignment=BottomRight Style=Back}
    Image {Alignment=Center File="portrait.jpg" Width=50%}
    Rectangle {Color=#aaa}
Enter fullscreen mode Exit fullscreen mode

For layering children, you need to define what the default Z-order is. Here we're assuming the first child is the one on top and the last child at the bottom. This layout positions a few controls, the title text and back button, on top of a user portrait. The solid coloured rectangle is behind all of them.

Additive - Complex

A grid and docking layout can also be used in an additive form. These are a bit harder to implement but yield some helpful layout constructs.

A Grid with a variable number of rows, either of fixed height, or natural height, can be placed within another layout, or positioned in a parent.

StackPanel
    Text {Value="Profile"}
    Grid {ColumnCount=2 DefaultRow="auto"}
        Label {Value="First Name"}
        TextEdit

        Label {Value="Last Name"}
        TextEdit

    Button {Action="Submit"}
Enter fullscreen mode Exit fullscreen mode

The grid grows with the number of children. It's treated as an ordinarily sized element in the stack panel and placed between the text header and submit button.

A DockPanel can be used similarly to construct naturally sized compound elements.

StackPanel
    DockPanel
        Image {Dock="Left" File="tommy.png"}
        UserStatus {Dock="Top"}
        Text {Value="Tommy"}
Enter fullscreen mode Exit fullscreen mode

The three elements of the DockPanel each have a natural size and are added together to create a compound card. Many of these could then be added to a stack panel, or used as a natural sized popup on the screen.

Getting a layout to work in both modes is challenging. It's tempting to say the two different modes should be explicit or have different type names. That is perhaps not user-friendly, as we can usually look at layout code and understand what is meant. It's also important to see there are four modes, not just two because each dimension can independently be additive or partitioning. For example, the grid and dock in the last two code snippets had a dependent width, but additive height.

Just an overview

These various goals of layout are used to build a sensible layout engine. Understanding the difference between the approaches is required when we start defining the specific API for layout. You don't want to start with just one layout and try to add in the other forms later -- unless you're comfortable with significant API restructuring.

If you wish to include rich text flow as just another layout you'll have a significant uphill battle. If we look at HTML, we see it's solved document layout reasonably, but has massive limitations with application layout. Even with things like flex-box, some simple applications are still tricky in the mixed model approach. I'm certain I'd be able to combine them effectively now, but I'd have to build the API from the ground-up to support it.

In later articles, I'll take a more in-depth look at each of the specific layouts. It's important to know both these general concepts as well as the details of specific layouts.


Read more articles about writing a UI Engine. Follow me on Twitter or Facebook, and to keep me writing, consider becoming a patron.

Top comments (0)