Skip to content

Layout and geometry

Every element takes x, y, w and h. They are measured from the top-left corner of the parent, not the screen:

<layer w={100} h={100}>
<rect x={10} y={20} w={30} h={30} color={0xff0000} fill />
</layer>

Instead of a number, x and y take an edge:

src/chrome.ts2
<sprite x="left" y="top" w={CORNER} h={CORNER} sprite={topLeft} tiling />
<sprite x="right" y="top" w={CORNER} h={CORNER} sprite={topRight} tiling />
<sprite x="left" y="bottom" w={CORNER} h={CORNER} sprite={bottomLeft} tiling />
<sprite x="right" y="bottom" w={CORNER} h={CORNER} sprite={bottomRight} tiling />
xy
"left""top"against that edge of the parent
"center""center"centred
"right""bottom"against that edge of the parent
"25%""25%"a quarter of the way across or down

Keywords follow the parent when it resizes.

The object form sits near an edge. A negative offset is allowed:

src/chrome.ts2
<sprite x={{ left: -15 }} y="center" w={36} h={{ fill: 60 }} sprite={Chrome.EDGE_LEFT} tiling />
<sprite x={{ right: 3 }} y={6} w={26} h={23} sprite={Chrome.CLOSE}
hover={{ sprite: Chrome.CLOSE_HOVER }} tiling options={['Close']} />

Every keyword has one: { left: n }, { center: n }, { right: n }, { top: n }, { bottom: n }. The offset may be a proportion of the parent:

src/layout.ts2
<sprite x={{ left: '15%' }} y="center" w={36} h={32} item={Items.RUNE_SCIMITAR} itemCount={-1} />
<sprite x="center" y="center" w={36} h={32} item={Items.AMULET_OF_POWER} itemCount={-1} />
<sprite x={{ right: '15%' }} y="center" w={36} h={32} item={Items.LOBSTER} itemCount={5} />

right and bottom are also props of their own, for pinning to a corner:

src/layout.ts2
<ClaimButton label="Claim" right={0} bottom={0} w={74} h={22} options={['Claim']} />

x="right" places the element; align="right" places the text inside it. A price label wants both.

w / h
a numberthat many pixels
"fill"as large as the parent
"50%"that proportion of the parent
{ fill: n }the parent, less n pixels
"aspect"keeps the natural proportions, given the other axis

{ fill: n } insets something inside a border:

src/shop.ts2
<Window title="General Store">
<layer x={10} y={42} w={{ fill: 20 }} h={{ fill: 52 }}>

A component’s root with no size fills the box it is placed in, and a size the caller writes on the tag wins over the root’s own; see Passing props.

<ui> takes the same vocabulary, measured against the slot the interface opens into. It defaults to the slot’s top-left corner; x="center" floats it:

src/inventory.ts2
<ui ref={inventoryRef} w={196} h={330}>

Size a <ui> with numbers, plain constants or keywords.

A <layer> with direction places its children one after another:

<layer w="fill" h={16} direction="row" gap={4}>
<text w={48} h={16} text="Buy" font={Fonts.PLAIN} align="center" />
<text w={48} h={16} text="Sell" font={Fonts.PLAIN} align="center" />
</layer>
Prop
direction"row" or "column"
gappixels between children
paddingoffset of the first child

Give every child a fixed size along the flow. Wrap a component in a fixed-size <layer>.

useUniverse() is the box your component was placed in. Its width and height read like any ref’s:

src/layout.ts2
const DropList = (): Component => {
const universe = useUniverse();
const list = useRef();
const content: Int = DROPS.length * ROW_H;

To follow a resize, use onResize on the element or useOnResize(universe); see Watching and reacting.

A <layer static> has an address of its own, so another interface can open into it or the server can name it. See Interfaces and slots; every geometry prop is on Common props.