JustUI/doc/layout.md

4.2 KiB

JustUI: Space distribution and layout

Layout is the process through which space is allocated to widgets, which are then sized and positioned.

Content widgets and containers

Widgets in a scene usually fulfill one of two roles:

  • Content widgets such as labels, input fields, or menu browsers, display the interface and receive events. They often have no children, and do their own rendering.
  • Containers such as rows and columns, stacks, or grids, organize other widgets so they align nicely and don't overlap. The widgets they organize are their children; they themselves often perform no rendering.

JustUI does not enforce this separation, and a single widget can both handle contents and organize children. But the features are usually designed with one of the two roles in mind.

Layouts

Layouts are parameters that can be attached to widgets to automatically size and position their widgets in a useful fashion. This is mostly designed for containers. There are currently 4 types of layouts:

  • Horizontal boxes and vertical boxes arrange children in a row or a column, respectively. Each widget gets its desired size; if there is space left, widgets can expand according to stretch parameters (more on that later).
  • Stacks arrange all the widgets on top of each other. Only one widget is visible at a time. This is useful for tabbed interfaces.
  • Grids arrange all widgets in a grid. (TODO: WIP)

A widget that does not have a layout needs to manually determine its own desired size as well as the position its children.

The layout process

The layout process has two phases.

  1. Size estimation. In the first phase, each widget declares a desired size. This size often depends on the size of the children, so this phase proceeds bottom-up: first the children declare their desired sizes, then the parents deduce their own desired sizes, and so on.

  2. Space distribution. In the second phase, space is distributed by the parents to the children. If the parents have more available space than the children request, extra space is distributed as well. This phase is top-down: first the root distributes the available space to its children, then these children split their shares between their own children, etc.

All of this proceeds automatically unless some widgets cannot provide a natural size (or only a useless one), in which case the user should give a hint.

Internals

During the first phase, the content size of each widget is evaluated by either the layout's csize() function, or the polymorphic widget's csize() override. Then jwidget_msize() adds in the geometry and stores the margin-box size in the w and h attributes.

During the second phase, jwidget_layout_apply() distributes the space by dispatching to the layout's apply() function, or the polymorphic widget's apply() override. It proceeds recursively in a depth-first, prefix order.

Layout control

The widget type provides a natural content size, but the user has the final say in the size of any widget. Any widget can have a minimum and maximum size specified, and every layout guarantees that the allocated size falls in this range (note that limits are examined during the second phase, and the natural content size does not need to be in the acceptable range).

Additionally, the user can provide stretch rates indicating whether the widget can use more space horizontally and vertically. When there is space to distribute and several widgets are competing to use it, space is allocated in proportion to stretch rates. For instance, a widget with double the stretch rate of its competitors will get twice as much space as them.

In certain occasions, one might want to disregard the natural content size entirely and distribute space based only on stretch rates, for instance to split a screen in evenly-sized columns even when the contents of the columns have different natural sizes. In this case, one can set the columns to a fixed width of 0 while enabling stretching-beyond-limits. Stretching beyond limits will allow the widgets to grow despite being of fixed size, and because they all start with the same width of 0, they will all end up with the same size.