Search guides and the APICtrl K
  1. API reference
  2. Native GUI

gfx/relative_position.h

Relative positioning: a component's edges as EXPRESSIONS against its parent, its siblings and named markers -- "parent.width - 20", "slider.right + 5" -- re-evaluated whenever anything they mention moves.

#include <sonore/gfx/relative_position.h>
On this page

Design notes

What it is for

The reference had this before it had FlexBox and Grid, and layouts written in it are still out there: component bounds stored as four strings, in a file or a data tree, so a designer could move things without a rebuild. A port of such an editor needs those strings to mean the same thing here. For new layouts FlexBox and Grid (layout.h) are the better tool, and nothing here replaces them.

The pieces

RelativeCoordinate
one number, as an expression
RelativePoint
two of them, "x, y"
RelativeRectangle
four, "left, top, right, bottom", and applyToComponent
RelativeParallelogram
three points: an image's corners under a shear
RelativePointPath
a Path whose points are RelativePoints
MarkerList
named coordinates a parent publishes for its children
RelativeCoordinatePositionerBase
what keeps a component where its expressions say, listening to everything they mention

The names an expression can use

parent.left / right / top / bottom / x / y / width / height / centreX /
  centreY -- the parent, in its OWN coordinates (left and top are 0)
<id>.<edge>   a sibling found by Component::componentID, in the parent's
              coordinates, which is where the positioned component lives
this.<edge>   the component's own current bounds
<marker>      a marker from a MarkerList given to the positioner
numbers, + - * / and parentheses, and min(a, b), max(a, b), abs(a)

Cycles

Two kinds, both detected rather than looped on. A marker defined in terms of itself, directly or through others, fails to evaluate with the chain named. Two components each positioned against the other is subtler: each move notifies the other, and the pair would chase each other forever. A positioner re-entered while it is still applying -- which is exactly that chase -- checks whether its answer has CHANGED: if not, the layout has converged and nothing is wrong (A's x from B and B's y from A is a valid layout); if so, it stops, and every positioner in the loop reports hasCycle().

The evaluator

detail::RelativeExpression is a small recursive-descent parser and tree evaluator written for this header, because the SDK has no general Expression class. It takes untrusted text (a layout read from a file), so it caps length and nesting and never recurses on input it has not bounded. If a core Expression class lands later, RelativeExpression's interface -- parse(text, &error), evaluate(resolver, &error), toString(), symbols() -- is the seam to swap it behind; nothing outside this header touches the tree.

Types 8

Where an expression's names are looked up: the parent whose coordinates everything is in, the component being positioned (for "this"), and the marker lists in force.

One coordinate, as an expression.

A constant is the degenerate case and the common one; it still goes through the same type so that a layout mixing "10" and "parent.width - 10" needs no second representation.

Named coordinates a parent publishes for the components inside it: "the gutter", "the centre line", defined once and used by every child's expression. A marker may itself be an expression, including one in terms of other markers; a chain that loops is reported, not followed.

Two coordinates. Written "x, y".

Four coordinates: the edges. Written "left, top, right, bottom".

Three points -- top-left, top-right, bottom-left -- and so a rectangle under any affine transform: what a sheared or rotated image is placed by.

A Path whose points are RelativePoints.

class RelativeCoordinatePositionerBase : private ComponentListener, private MarkerList::Listener

Keeps one component where its expressions put it.

Listens to the component (for a new parent), its parent (moved, resized, children changed), every sibling an expression names, and every marker list in force -- and re-applies when any of them changes. The dependencies are worked out again on every apply, so an expression naming a sibling that does not exist yet starts working when it is added.

↑↓ to move↵ to openEvery public name in the headers, and every guide