Native UI Layout Reference

This document is the authoritative reference for eepp’s native application UI layout system.

It describes the behavior of the layout primitives used by normal eepp applications such as ecode, eterm, eproc, dialogs, settings panels, forms, workspaces, toolbars, and application-specific tools.

For the recommended high-level authoring model and conventions, read `ui_authoring.md` first.

This document intentionally describes current eepp behavior, not Android, browser, Qt, or other framework behavior that may look similar.

HTML formatting contexts such as block, inline, Flexbox, CSS Grid, table layout, floats, and positioned HTML are a separate system primarily used by UIWebView, UIMarkdownView, and the HTML compatibility layer. They are not covered here except where a distinction is important.

1. Native layout model

The normal eepp layout model consists of:

UIWidget
    size policy
    margins
    padding
    layout gravity
    min/max constraints
    optional layout weight
    optional relative-position relationship

UILayout
    owns or participates in positioning/sizing children

specific layouts
    UILinearLayout
    UIRelativeLayout
    UIGridLayout
    UIFlowLayout
    UISplitter

The most important idea is:

The parent layout controls the final allocation and position of its children.

A child supplies a sizing/placement contract through properties such as:

layout_width
layout_height
layout_weight
layout_gravity
margin
min-width
max-width
min-height
max-height

The exact meaning of those properties depends on the parent layout.

2. XML and CSS spelling

eepp accepts aliases, but normal project style distinguishes XML attributes from CSS properties.

Preferred XML spelling:

<TextInput
    layout_width="0dp"
    layout_height="wrap_content"
    layout_weight="1"
    layout_gravity="center_vertical" />

Preferred CSS spelling:

.form-input {
    layout-width: 0dp;
    layout-height: wrap_content;
    layout-weight: 1;
    layout-gravity: center_vertical;
}

Common compact aliases:

layout_width    -> lw
layout_height   -> lh
layout_weight   -> lw8
layout_gravity  -> lg

match_parent    -> mp
wrap_content    -> wc

Both full and compact forms are common in production eepp code.

3. Default widget layout state

A newly constructed UIWidget starts with:

layout weight   = 0
layout gravity  = unset / 0
width policy    = WrapContent
height policy   = WrapContent
position policy = None

Specific widgets may override their own defaults.

Do not assume every widget behaves identically under wrap_content; intrinsic sizing is widget-specific.

4. Size policies

The native size-policy enum is:

enum class SizePolicy {
    Fixed,
    MatchParent,
    WrapContent
};

These policies are applied independently to width and height.

4.1 <tt>wrap_content</tt>

XML:

layout_width="wrap_content"
layout_height="wrap_content"

Compact:

lw="wc"
lh="wc"

A WrapContent axis asks the widget to derive its size from its intrinsic/content size.

The exact intrinsic size is widget-specific.

Examples:

  • text widgets derive size from their text and font metrics;

  • image widgets derive size from the drawable;

  • buttons derive size from their internal content/theme;

  • native layouts may derive their wrapped axis from their visible children.

Padding contributes to normal content sizing where the widget implementation defines it.

wrap_content is the default native size policy for a plain UIWidget.

4.2 <tt>match_parent</tt>

XML:

layout_width="match_parent"
layout_height="match_parent"

Compact:

lw="mp"
lh="mp"

MatchParent requests the available parent content size on that axis.

The generic match-parent calculation is:

parent size
- parent content offsets
- non-auto child margins

Parent content offsets include padding and, where applicable, borders.

max-width / max-height can cap the resulting match-parent size.

Conceptually:

child match-parent width =
    parent width
    - parent left/right content offsets
    - child left/right margins

The exact layout can perform additional sizing rules around this value.

Do not treat match_parent as a synonym for CSS width: 100%. They may often produce similar geometry, but they belong to different layout systems.

4.3 Fixed size

Any explicit dimension normally selects Fixed sizing on that axis:

layout_width="240dp"
layout_height="32dp"

The value is resolved and stored as the widget size.

The literal value:

fixed

can also select the fixed policy without supplying a new dimension.

Use explicit sizes where the dimension itself is meaningful. Do not use arbitrary fixed dimensions to compensate for a parent-layout mistake.

4.4 Zero size and weighted LinearLayout children

There is one important special case.

Inside a UILinearLayout, the canonical weighted-child pattern intentionally uses zero on the weighted axis:

Horizontal parent:

<TextInput
    layout_width="0dp"
    layout_weight="1" />

Vertical parent:

<ListView
    layout_height="0dp"
    layout_weight="1" />

When eepp parses a zero layout dimension for a weighted child whose parent is a UILinearLayout, it does not immediately force the stored size in the normal fixed-size path. The linear layout assigns the weighted size during packing.

5. Minimum and maximum constraints

Widgets support:

min-width
max-width
min-height
max-height

These can constrain native layout results.

For example, generic match_parent width calculation applies a max-width cap when one exists.

Some native layout algorithms also explicitly fit wrapped container sizes against min/max constraints.

Do not assume min/max handling is identical to browser intrinsic-sizing rules. This document describes the native layout system; HTML layout has additional rules.

6. Margin and padding

Margin

Margins belong to the child and participate in parent layout allocation/positioning.

Example:

<PushButton margin-left="8dp" />

Margins affect:

  • linear packing;

  • match-parent available size;

  • relative positioning;

  • flow wrapping;

  • grid placement;

  • splitter/container alignment where relevant.

Padding

Padding belongs to the container/widget.

Example:

<vbox padding="12dp">
    ...
</vbox>

Native layouts generally place children inside the padded content area.

Conceptually:

container border box
    padding
        child layout area

7. <tt>gravity</tt> and <tt>layout_gravity</tt>

These are different properties.

<tt>gravity</tt>

gravity controls the widget’s own content or, for layouts that explicitly use it, the layout’s internal arrangement.

Examples include:

left
right
center_horizontal
top
bottom
center_vertical
center

Widget behavior varies because different widget types own different content.

<tt>layout_gravity</tt>

layout_gravity describes how the widget should be placed by its parent layout when that layout supports gravity.

XML:

layout_gravity="right|center_vertical"

CSS:

layout-gravity: right|center_vertical;

The distinction is:

gravity
    content/layout behavior inside this widget

layout_gravity
    placement of this widget by its parent

This distinction is especially important in UILinearLayout.

8. <tt>gravity-owner</tt>

Layouts normally respect the positioning policy of their parent.

Some parent widgets advertise that they own child positioning with UI_OWNS_CHILDREN_POSITION.

A layout can set:

gravity-owner: true

to force its own layout_gravity alignment against the parent even when the parent normally owns child positions.

This is an advanced escape hatch and should not be a default solution to layout problems.

9. Layout invalidation and update behavior

Native layouts are invalidated when relevant state changes, including:

  • child count changes;

  • layout-affecting child attributes;

  • layout size changes;

  • padding changes;

  • parent-size changes.

A dirty layout is normally scheduled through the owning UISceneNode.

During an active layout pass, some updates can occur synchronously to avoid waiting for another frame.

Calling UILayout::getSize() on a dirty layout forces that layout to update before returning its size.

Nested layouts are tracked by their parent UILayout and participate in layout-tree updates.

This matters mainly when implementing custom layout widgets. Ordinary application code should set properties and allow the scene to perform layout.

10. Visibility and native layouts

Native layouts generally operate on visible widget children.

Examples:

  • UILinearLayout ignores invisible children while packing;

  • UIFlowLayout ignores invisible children while creating rows;

  • UIGridLayout ignores invisible children;

  • invisible native layouts may collapse their own layout size to zero during their update path.

Do not use invisible children as spacers.

11. <tt>UILinearLayout</tt>

UILinearLayout is the primary sequential application layout.

XML aliases:

<vbox>
<hbox>

C++:

UILinearLayout::NewVertical();
UILinearLayout::NewHorizontal();

Default orientation:

vertical

11.1 Vertical linear layout

A vertical layout packs visible children from top to bottom.

Each child’s vertical margins contribute to the consumed main-axis space.

The child’s layout_gravity controls horizontal placement inside the layout:

left
center_horizontal
right

Example:

<vbox
    layout_width="match_parent"
    layout_height="match_parent"
    padding="12dp">

    <TextView
        text="Left"
        layout_gravity="left" />

    <PushButton
        text="Centered"
        layout_gravity="center_horizontal" />

    <PushButton
        text="Right"
        layout_gravity="right" />
</vbox>

11.2 Horizontal linear layout

A horizontal layout packs visible children from left to right.

Each child’s horizontal margins contribute to consumed main-axis space.

The child’s layout_gravity controls vertical placement:

top
center_vertical
bottom

Example:

<hbox
    layout_width="match_parent"
    layout_height="40dp">

    <TextView
        text="Name"
        layout_gravity="center_vertical" />

    <TextInput
        layout_width="0dp"
        layout_weight="1"
        layout_gravity="center_vertical" />
</hbox>

12. LinearLayout size-policy behavior

Before packing, the layout applies child policies on the cross axis.

Vertical layout

For child width:

  • WrapContent : child auto-sizing is enabled;

  • MatchParent : width becomes the layout’s content width minus child horizontal margins;

  • Fixed : existing width remains.

A child with layout_height="match_parent" and zero weight can also be resized to the parent’s available height.

Horizontal layout

For child height:

  • WrapContent : child auto-sizing is enabled;

  • MatchParent : height becomes the layout’s content height minus child vertical margins;

  • Fixed : existing height remains.

A child with layout_width="match_parent" and zero weight can also be resized to the parent’s available width.

13. LinearLayout weights

layout_weight distributes remaining space along the layout orientation.

Canonical horizontal use:

<hbox lw="mp" lh="wc">
    <TextView text="Project" min-width="90dp" lg="center_vertical" />
    <TextInput lw="0dp" lw8="1" />
</hbox>

Canonical vertical use:

<vbox lw="mp" lh="mp">
    <TextView text="Header" />
    <ListView lw="mp" lh="0dp" lw8="1" />
    <hbox lw="mp" lh="wc">
        <!-- footer -->
    </hbox>
</vbox>

13.1 Weight calculation

The implementation treats positive weights as relative weights.

For all visible children:

totalWeight = sum(max(childWeight, 0))

The layout first calculates the main-axis space already consumed by:

  • visible non-weighted fixed/wrapped children;

  • visible child margins.

Then each positively weighted child gets:

remainingSpace * childWeight / totalWeight

Therefore these are equivalent ratios:

1, 1
0.5, 0.5
50, 50

and:

1, 3
25, 75

produce equivalent proportions.

Current implementation does not require weights to be normalized to 0..1 or sum to 1.

Older documentation that describes weights as normalized should be considered overly restrictive.

13.2 Example calculation

Container:

horizontal content width = 600dp

Children:

A = fixed 100dp
B = weight 1
C = weight 3

Ignoring margins:

remaining = 600 - 100 = 500
totalWeight = 4

B = 125
C = 375

14. LinearLayout wrap-content behavior

A vertical UILinearLayout with layout_height="wrap_content" grows to the visible child heights, vertical margins, and padding.

A horizontal UILinearLayout with layout_width="wrap_content" similarly grows from visible child widths, margins, and padding.

The cross-axis wrapped size is based on the maximum visible non-match-parent child extent plus container padding, with min/max constraints considered.

A wrapped linear layout can repack after its cross-axis size changes because that size may affect child alignment or nested layout.

15. LinearLayout properties

Layout-specific:

orientation
gravity-owner

orientation values:

vertical
horizontal

Default:

vertical

Child properties commonly used with a linear layout:

layout_width
layout_height
layout_weight
layout_gravity
margin
min-width
max-width
min-height
max-height

16. <tt>UIRelativeLayout</tt>

UIRelativeLayout is a native layout for:

  • alignment against the parent;

  • positioning one child relative to a sibling.

It owns child positioning.

Use it when sibling relationships are more natural than a nested vbox / hbox hierarchy.

Do not choose it merely because it permits more arbitrary positioning.

17. RelativeLayout child sizing

For each widget child:

Width:

WrapContent  -> enable auto-sizing
MatchParent  -> layout width - child horizontal margins - layout horizontal padding
Fixed        -> keep existing width

Height:

WrapContent  -> enable auto-sizing
MatchParent  -> layout height - child vertical margins - layout vertical padding
Fixed        -> keep existing height

18. RelativeLayout parent alignment

When a child has no sibling-relative position policy, layout_gravity positions it against the UIRelativeLayout.

Horizontal:

left
center_horizontal
right

Vertical:

top
center_vertical
bottom

Example:

<RelativeLayout
    layout_width="match_parent"
    layout_height="match_parent"
    padding="8dp">

    <PushButton
        text="Bottom right"
        layout_gravity="right|bottom" />
</RelativeLayout>

19. RelativeLayout sibling relationships

Supported relationships are:

layout_to_left_of
layout_to_right_of
layout_to_top_of
layout_to_bottom_of

The value is the target sibling ID.

Example:

<RelativeLayout
    layout_width="match_parent"
    layout_height="match_parent">

    <Widget
        id="sidebar"
        layout_width="220dp"
        layout_height="match_parent" />

    <Widget
        id="content"
        layout_width="match_parent"
        layout_height="match_parent"
        layout_to_right_of="sidebar" />
</RelativeLayout>

The relationship is resolved by finding the referenced node and storing the target widget.

It is used only when target and positioned widget share the same parent.

Only one stored PositionPolicy is active at a time.

This is not a general constraint solver.

20. RelativeLayout wrap-content caveat

UIRelativeLayout::updateLayout() applies its own size policy and lays out children, but it does not compute a new bounding rectangle from all positioned children.

For application containers, prefer a defined or match_parent size when using UIRelativeLayout.

Do not assume wrap_content behaves like a general constraint-layout “measure descendants and fit” pass.

21. <tt>UIGridLayout</tt>

UIGridLayout is eepp’s native repeated-cell grid.

It is not CSS Grid.

Use it for normal application UI when children should occupy repeated cells with a common row/column size policy.

Do not infer CSS track sizing behavior from this class.

22. GridLayout sizing model

Grid cells use one column mode and one row mode.

column-mode:
    size
    weight

row-mode:
    size
    weight

Defaults:

column-mode   = weight
row-mode      = weight
column-weight = 0.25
row-weight    = 0.25
column-width  = 0
row-height    = 0
column-margin = 0
row-margin    = 0

When column-mode=size, cell width is column-width.

When column-mode=weight, cell width is approximately:

available grid width * column-weight

When row-mode=size, cell height is row-height.

When row-mode=weight, cell height is approximately:

available grid height * row-weight

23. GridLayout placement

Visible widget children are processed sequentially.

For each child:

  1. determine target cell size;

  2. set child size policy to Fixed, Fixed;

  3. resize child to target size;

  4. place child at current grid position;

  5. advance by cell width plus column margin;

  6. wrap to the next row when the next cell no longer fits.

This is a repeated-cell wrapping grid, not a named-track CSS grid.

24. GridLayout child weight override

UIGridLayout has a special behavior:

If a child has non-zero layout_weight, that child’s target width becomes:

child layout weight
* grid content width

This is not UILinearLayout ‘s remaining-space distribution algorithm.

Do not assume layout_weight means the same algorithm in every layout type.

For straightforward grids, prefer the layout-level row/column sizing properties unless this per-child width override is intentionally needed.

25. GridLayout alignment

The grid’s own horizontal gravity affects row placement:

left
center
right

This is container gravity, not child layout_gravity.

26. GridLayout wrap-content behavior

With:

layout_height="wrap_content"

the layout derives its height from generated rows plus vertical padding.

A match-parent grid width/height is resolved from the parent before cell placement.

Children are converted to fixed cell sizes during grid layout.

27. GridLayout properties

column-mode
column-weight
column-width
column-margin

row-mode
row-weight
row-height
row-margin

Container gravity also affects horizontal grid alignment.

28. <tt>UIFlowLayout</tt>

UIFlowLayout is eepp’s native horizontal wrapping flow layout.

Conceptually:

A B C D
E F G
H I

Children are consumed in order from left to right.

When the next visible child no longer fits the current row, a new row is created.

29. FlowLayout width behavior

A notable design choice:

When the flow layout itself uses:

layout_width="wrap_content"

it initially uses the available match-parent width as its wrapping width.

After layout, if all visible children effectively fit on one row and use less than the available width, the layout can shrink its wrapped width to the used width.

So wrap_content for UIFlowLayout is not simply “sum all child widths without a constraint.” Available parent width participates in wrapping first.

30. FlowLayout row creation

For each visible child:

  1. apply the child’s size policy;

  2. test whether the child would exceed the current row;

  3. if necessary, create a new row;

  4. place the child;

  5. include child margins in row consumption;

  6. track the row’s maximum height.

The implementation can also create a new row after placement when the resulting cursor exceeds the layout width.

31. FlowLayout child size policies

Before rows are built:

WrapContent width/height
    enable child auto-sizing

MatchParent width
    available match-parent width

MatchParent height
    available match-parent height

Fixed
    retain explicit size

After row heights are known, a child with:

layout_height="match_parent"

is resized to that row’s maximum height.

32. FlowLayout alignment

The flow layout’s own gravity controls row-group placement.

Horizontal gravity:

left
center
right

controls each row’s horizontal displacement.

Vertical gravity:

top
center_vertical
bottom

can shift the complete row group inside a container taller than the total flow content.

Within each row, row-valign controls child vertical alignment.

33. <tt>row-valign</tt>

Values:

top
center
bottom

Default:

bottom

Meaning:

top
    child aligns near row top, respecting top margin

center
    child is centered in row maximum height

bottom
    child aligns near row bottom, respecting bottom margin

This is separate from the container’s vertical gravity.

34. FlowLayout wrap-content height

With:

layout_height="wrap_content"

height becomes:

top padding
+ sum(row maximum heights)
+ bottom padding

with child vertical margins contributing to row heights.

35. FlowLayout properties

Layout-specific:

row-valign
gravity-owner

Important general properties:

gravity
layout_width
layout_height
padding
margin

UIFlowLayout does not use child layout_weight as a remaining-space distribution mechanism.

36. <tt>UISplitter</tt>

UISplitter is a two-pane resizable layout.

Default orientation:

horizontal

Horizontal:

[first pane] | [second pane]

Vertical:

[first pane]
-------------
[second pane]

The separator is an internal draggable widget.

37. Splitter child model

UISplitter supports at most two external widget children.

Internally it also owns its separator widget.

The first external child becomes the first pane; the second becomes the last pane.

Additional external children are rejected/closed.

Children placed into the splitter are forced to:

Fixed, Fixed

because the splitter owns their exact geometry.

38. Splitter with one child

If only one pane exists:

  • the separator is hidden and disabled;

  • the first child fills the splitter’s padded content area.

39. Split partition

Property:

splitter-partition

Default:

50%

Meaning:

Desired space occupied by the first pane along the splitter axis.

The separator’s own size is removed from available split space when visible.

Conceptually:

totalSpace =
    splitter content axis size
    - visible separator size

first = resolve(splitter-partition, totalSpace)
second = totalSpace - first

40. Splitter minimum sizes

The first pane’s minimum size constrains the initial partition.

During dragging, both pane minimum sizes constrain separator movement.

A requested partition may therefore not be achievable when it violates minimum sizes.

Do not assume 0% always produces a physically zero-sized first pane if its minimum size is non-zero.

41. Splitter visibility properties

splitter-always-show

Default:

true

When true, the divider remains visible whenever two panes exist.

splitter-hide-on-edge

Default:

false

When enabled and splitter-always-show is false, percentage partitions exactly at 0% or 100% can hide the divider so one pane consumes the full splitter.

42. Splitter dragging

Horizontal orientation:

separator drags along x
cursor = horizontal resize

Vertical orientation:

separator drags along y
cursor = vertical resize

Dragging immediately resizes both children.

After a drag, the stored split partition is recalculated as a percentage of available split space.

43. Splitter properties

orientation
splitter-partition
splitter-always-show
splitter-hide-on-edge

Orientation values:

horizontal
vertical

44. Choosing the correct native layout

Need

Native layout

Vertical sequence

Horizontal sequence

Flexible remaining space in a row/column

Child positioned relative to a sibling

Repeated equal/weighted cells

Horizontal items that wrap into rows

Two user-resizable panes

Tabs

Split/tab editor workspace

Web/document formatting

HTML compatibility layout

UITabWidget and UITabWidgetSplitter are higher-level containers rather than low-level generic layout algorithms and belong in the widget/application references.

45. Production patterns

Flexible form row

<hbox
    layout_width="match_parent"
    layout_height="wrap_content"
    margin-bottom="8dp">

    <TextView
        text="Project"
        min-width="90dp"
        layout_gravity="center_vertical" />

    <TextInput
        id="project"
        layout_width="0dp"
        layout_weight="1" />
</hbox>

Wrapping option group

<FlowLayout
    layout_width="match_parent"
    layout_height="wrap_content"
    row-valign="center">

    <CheckBox text="Linux" margin-right="8dp" />
    <CheckBox text="macOS" margin-right="8dp" />
    <CheckBox text="Windows" margin-right="8dp" />
    <CheckBox text="FreeBSD" margin-right="8dp" />
</FlowLayout>

Two-pane workspace

<Splitter
    layout_width="match_parent"
    layout_height="match_parent"
    orientation="horizontal"
    splitter-partition="30%">

    <TreeView />
    <CodeEditor />
</Splitter>

46. Important differences from browser layout

Do not import these as semantic equivalences:

match_parent == width:100%
layout_weight == flex-grow
UIGridLayout == CSS Grid
UIFlowLayout == display:flex + flex-wrap
layout_gravity == justify-content / align-items

They can be useful analogies, but native eepp layouts are separate algorithms.

47. Important differences from Android intuition

eepp deliberately borrows several Android-style concepts, especially:

LinearLayout
layout_width
layout_height
match_parent
wrap_content
layout_weight
layout_gravity
RelativeLayout-style sibling relations
dp

Android documentation is not authoritative for eepp.

Notable example:

Current eepp UILinearLayout weights are arbitrary relative positive values; they do not need to be normalized or sum to 1.

Always prefer eepp behavior over external analogy.

48. Inspecting native layout at runtime

Use the Runtime UI Inspector & Automation Protocol rather than guessing geometry.

Typical process:

ui.contexts
    ↓
ui.query target
    ↓
ui.inspect target geometry/layout properties
    ↓
inspect parent
    ↓
inspect relevant siblings
    ↓
screenshot if useful

Stable debugging questions:

What parent layout owns this child?
What are its width/height policies?
What are its actual bounds?
Does it have weight?
What are its margins?
What is the parent's padding?
What gravity is active?
What min/max constraints exist?

For weighted linear layouts, inspect all siblings participating in the same axis.

49. Layout debugging checklist

When size is wrong:

1. identify parent layout type
2. inspect layout_width / layout_height
3. determine Fixed / MatchParent / WrapContent
4. check layout_weight
5. check margins
6. check parent padding/content offsets
7. check min/max constraints
8. check sibling sizes/weights
9. check visibility
10. only then consider an explicit fixed size

When position is wrong:

1. identify parent layout
2. check layout_gravity
3. distinguish layout_gravity from gravity
4. check margins
5. check RelativeLayout position relationship
6. check parent gravity where Grid/Flow uses it
7. check gravity-owner only if parent positioning ownership matters

50. Implementation-oriented notes

These details matter mainly when implementing custom layouts.

Layout dirtiness

UILayout maintains dirty state and participates in the scene’s layout invalidation queue.

Reentrancy guard

Layouts use mPacking to prevent recursive packing.

Nested layouts

A UILayout tracks direct child layouts and updates the layout tree recursively.

Auto-size children

A layout can request onAutoSize() for ordinary children or temporarily update a child layout as wrapping content.

Parent notifications

When a layout’s wrapped size changes, implementations notify the parent so enclosing layouts can repack.

Custom layout implementations should preserve these lifecycle expectations.

51. Scope boundaries

This document intentionally does not specify:

  • HTML Flexbox;

  • HTML CSS Grid;

  • block/inline formatting;

  • HTML table layout;

  • floats;

  • sticky/fixed/absolute HTML positioning;

  • HTML intrinsic min-content/max-content behavior.

See `ui_html_compatibility.md` for HTML/web layout semantics.

For related topics:

  • `ui_introduction.md` — introduction to eepp’s UI system.

  • `ui_authoring.md` — recommended application UI mental model and authoring conventions.

  • `ui_css_for_applications.md` — CSS behavior and conventions for normal application widgets.

  • `ui_inspector.md` — runtime UI inspection and automation protocol.

  • `ui_databinding.md` — UI data binding.

  • `ui_charts.md` — charting widgets and chart-specific behavior.

52. Source of truth

When uncertain about native layout behavior, use this order:

  1. current eepp implementation

  2. current eepp unit/integration tests

  3. production eepp/ecode/eterm/eproc usage

  4. this reference

  5. `css_specification.md` and other general docs

  6. Android/browser/other-framework analogy

If a documented rule and the current implementation disagree, treat the implementation as authoritative and update the documentation.