DEMIENGINEDOCUMENTATION Everything is free
UI & HUD · Create

Dock, stack, anchors, margins, padding, and safe areas.

Layout

Layout decides where every HUD node sits and how big it is. DemiEngine measures everything in canvas units from a design resolution you choose, then scales to the real screen. You describe intent — “bar on top”, “column with gaps”, “corner badge” — and the runtime resolves positions. This page explains each placement tool and when to reach for it.

Canvas units

A HUD document declares a design size:

{
  "format_version": 1,
  "canvas_size": [1280, 720],
  "children": []
}
  • canvas_size is [width, height] in design units. Every position, size, gap, and margin is measured against it.
  • A 1280×720 canvas on a 2560×1440 screen renders at 2× — a 140-unit button becomes 280 physical pixels.
  • Aspect ratio is preserved with letterboxing or safe-area padding rather than stretching, so layout does not shear on phones.

Why units instead of pixels: one authoring pass works for desktop, tablet, and phone. A 24-unit gap is the same visual proportion everywhere.

dock

dock pins a node to an edge or fills its parent. Values: fill, top, bottom, left, right, center.

fill

{
  "id": "hud_root",
  "type": "container",
  "dock": "fill",
  "children": []
}

Stretches the node to cover its parent completely. Use it for backgrounds and full-screen stacks.

top bar

{
  "id": "top_bar",
  "type": "panel",
  "dock": "top",
  "size": [1280, 64],
  "background_color": "#10121ccc",
  "children": []
}

Anchors the panel to the top edge of its parent. Its height comes from size; its width stretches across the parent, minus margins. Docked siblings do not reserve space for one another, so place a body below this bar with padding or an explicit position.

side panel

{
  "id": "score_panel",
  "type": "panel",
  "dock": "right",
  "size": [320, 720],
  "margin": [0, 16, 16, 16],
  "stack": "column",
  "pad": 16,
  "gap": 8,
  "children": []
}

dock: "right" anchors the panel's left edge to the parent's right edge. The negative left margin moves its 320-unit width back inside. bottom likewise needs a negative top offset for a fixed-height node. dock: "center" anchors the node's top-left at the parent's center; offset by half its size to center the rectangle.

Each dock shorthand sets anchor points for that node. A fill node spans its parent and can overlap docked siblings; use a layout container or explicit offsets where nodes must not overlap.

Right and bottom docking align the control's outer edge inside its parent; positive right/bottom margins inset it. Center docking centers the whole control in the margin-adjusted parent. Alignment uses the final size, including min/max constraints, and follows resizing. position or at adds an optional offset. Explicit anchors override docking and retain top-left-origin semantics.

Development migration: remove negative offsets previously used only to compensate for right, bottom or center docking. The editor now writes the dock value without baking a size-dependent position. Undocking preserves the visible position.

stack

stack lays children in a run. Values: row, column, grid.

row and column

[
{
  "id": "toolbar",
  "type": "container",
  "stack": "row",
  "gap": 8,
  "alignment": "center",
  "children": []
},
{
  "id": "menu",
  "type": "container",
  "stack": "column",
  "gap": 12,
  "alignment": "stretch",
  "children": []
}
]
  • stack — direction children flow. row is left to right, column is top to bottom.
  • gap — empty canvas units between children.
  • alignment — how children sit across the flow: start, center, end, or stretch (children fill the cross axis).

grid

{
  "id": "ability_grid",
  "type": "container",
  "stack": "grid",
  "columns": 4,
  "gap": 6,
  "children": []
}
  • columns — how many cells per row before wrapping. Required for grid.
  • gap applies between both rows and columns.

To fill each grid cell horizontally, give its child anchor_min: [0, 0], anchor_max: [1, 0] and a fixed height. A column additionally needs alignment: "stretch". A zero width alone does not mean stretch.

The grid fills cells left to right, top to bottom. Skyforge’s skill bar is a 4-column grid of 64-unit icons.

anchors and margin for corners

For a badge that hugs a corner at a fixed size, anchor a point of the node to a point of its parent and offset with margin.

{
  "id": "fps_badge",
  "type": "label",
  "text": "60 FPS",
  "anchor_min": [1, 1],
  "anchor_max": [1, 1],
  "margin": [-140, -48, 0, 0],
  "size": [124, 32]
}
  • anchor_min / anchor_max — each is [x, y] in 0…1 of the parent’s box. [0, 0] is the top-left, [1, 1] is the bottom-right. When min equals max, the node anchors to one point; when they differ, the node stretches between the two points.
  • margin — [left, top, right, bottom] in canvas units. For a fixed-size node anchored at the bottom-right, negative left/top values move it inward; right/bottom matter when the anchors stretch.
  • size — width and height in canvas units when the node is not stretched.

Corner recipes: top-left anchor_min/max [0, 0] with positive left/top margins; top-right [1, 0] with a negative left offset; bottom-left [0, 1] with a negative top offset; bottom-right [1, 1] with negative left/top offsets. Prefer dock when a node spans an edge.

pad, padding, margin

Three similar words, three different boxes:

FieldApplies toMeaning
pada containerShorthand: one number for equal inner space on all four sides. Keeps children away from the container’s edge.
paddinga containerFull form inner space: a number, or [left, top, right, bottom]. Same effect as pad with per-side control.
marginany nodeOuter space between this node and its parent or siblings (with anchors it offsets from the anchor point).
{
  "id": "card",
  "type": "panel",
  "padding": [16, 24, 16, 24],
  "margin": 12,
  "children": []
}

This card sits 12 units away from its neighbours and keeps 16/24 units of air inside around its children.

min_size and max_size

{
  "id": "tooltip",
  "type": "panel",
  "min_size": [160, 48],
  "max_size": [360, 240],
  "children": []
}
  • min_size — [width, height] the node never shrinks below. Stops tiny text from collapsing.
  • max_size — [width, height] the node never grows beyond. Stops a long label from crossing the screen.

The layout clamps the authored or flow-computed size into the min/max range; it does not measure text to choose an intrinsic size.

at for absolute placement

at places a node at an explicit canvas point instead of flowing it. Use it for overlays that must not shift with siblings.

{
  "id": "crosshair",
  "type": "image",
  "texture": "asset://ui/crosshair",
  "at": [624, 344],
  "size": [32, 32]
}
  • at — [x, y] of the node’s placement point in canvas units, measured from the parent’s top-left.
  • size — the node’s extent in canvas units.

The crosshair's top-left is at [624, 344], placing its 32×32 rectangle at the center of a 1280×720 canvas. Prefer dock and stack for anything that shares space with siblings.

Safe areas

Phone notches, rounded corners, and home bars cover parts of the screen. A safe area is the region free of those obstructions. DemiEngine respects it by default so text and buttons stay reachable.

{
  "id": "sky_bg",
  "type": "image",
  "texture": "asset://ui/sky",
  "dock": "fill",
  "respect_safe_area": false,
  "children": []
}
  • respect_safe_area — boolean, default true. The node lays out inside the safe rectangle.
  • Set respect_safe_area: false only for intentional edge-to-edge work: full-screen backgrounds, letterbox fills, decorative art that may run under a notch.
  • Keep readable text and interactive controls inside the safe area even on an edge-to-edge parent — a button under the home bar cannot be tapped.

Skyforge’s practice: the sky gradient image is respect_safe_area: false; the health bar, buttons, and labels stay on their safe defaults.

Next

Control rendering and input order

corner_radius rounds native panel/control fills and borders. Text inputs apply their padding to placeholder text, entered text, selection, composition and caret, and honor vertical text alignment. Sliders render a narrow track with a thumb; progress controls fill their authored bounds.

Pointer hit testing follows the same effective layer order as rendering, including inherited visibility and scroll clipping. focusable: false removes a control from keyboard focus traversal while retaining pointer activation. This is useful for a transparent outside-click target behind a popup; set both its background and hover colors transparent.