Introduction
This document describes the process of writing the NLayout engine that executes layout for Delphi and how it works.
This part, the layout engine, has been the most difficult part of the Delphi project to date, executing layout with so many variables that interact in so many different ways depending on layout context and such.
The layout engine is part of the delphirender
module which
handles DOM rendering and Minecraft entity management.
The Name
NLayout just means "New Layout", after the Java class I started
writing it in, which was the 2nd version of the layout engine. The first being
very primitive, just taking in a layout-direction
(Either X or Y)
and tiling elements in that direction.
Layout passes
Layout is divided into passes, and this section describes the initial concept for the engine and the passes it executed.
Measurement
Iterate over each element, and measure it, calculate style properties, padding, margins, etc.
This pass uses a stack of vector2s called parentSizes
, as
elements are measured and we move onto their child elements, we push the
element's content size onto the stack. And pop after we finish
If an element's size is set, then we use that as the element's size, otherwise, we calculate the size based off of it's child elements and layout algorithm.
Firstly, if an element's size is based off of it's child elements, we assume the element's initial size to be the screen's size and then calculate its child elements. We then adjust the element's size to be the measured result. Then, the whole process is executed over and over until the child elements stop changing their size, aka, until the layout stabilizes.
Layout
Layout is performed by Layout algorithms, each executing it differently. At time of writing only Flow is supported.
Flow layout
Take the child elements of the element we're laying out and break them into
lines. To do that, we iterate over each one, skipping ones with
display: hide;
. If an element is not display: inline
or its too big for the current line, a line break is triggered. If the
element is display: block
another line break is triggered after
the element.
Then all the elements are laid out. Elements on the same line are stacked horizontally, while lines are stacked vertically.
After layout has finished, iterate over all child elements with
display: block
. And check if they have any horizontal auto
margins. If they do, properly move the element according to those margins: Do
nothing if only right margin is set to auto. Move element to the ride side of
the parent's content if only left margin is set, and center element if both
are set.