A layout is a description of 2-D content: boxes placed inside boxes, words, pictures, drawn shapes and controls a visitor can press. Handed to the layout(...) source (Images), it becomes an image at the size it ends up: over the picture as a HUD, on a sign or a screen in the world, on a wrist, in a speech bubble above someone's head. This page is the 2-D vocabulary: what a world can say about boxes, text, images, shapes, controls and styles, and how a layout is placed over a point in the 3-D world.

The vocabulary is drawn from what the browser's packages already do: uilayout (boxes, layout, controls, themes, the pointer), msdftext (letters sharp at any size, outline and shadow), richtext (paragraphs with styled runs, lists, pictures in the text), vector2d (paths, fills, strokes, gradients) and uipickers (number, color and choice controls). Where those packages lack something, this page says so. The names of the vocabulary are provisional: what is fixed is what each element means.

Terms used on this page. A node is one box of a layout: a panel, a line of text, a button. A container is a node that places its children by a rule (in a row, in a grid). Padding is space inside a box's edge, margin space outside it. A style is a named set of looks (colors, border, rounding) that many nodes share. A glyph is the drawn shape of one letter.

Units and coordinates

Boxes and layout

What it is

Every element of a layout is a box. A parent box hands each child a smallest and largest width and height it may take; the child answers with the size it wants within them; the parent then places its children by its container's rule. This is the layout model of Flutter and WPF, with CSS flexbox's single-line rows and columns. A layout is laid out again only where something changed: a change marks its box and the boxes above it, and a box of fixed size stops the marking from spreading further.

Containers

ContainerPlaces its children
column([...], ...), row([...], ...)one after another, down or across, as CSS flexbox's single line: each child may grow into spare room, shrink when room is short, and start from a basis size; the line is justified (start, center, end, space between, around or evenly) and children aligned across it (start, center, end, stretch), with a gap between
stack([...])on top of each other, each aligned or filling
grid([...], columns: [...], rows: [...])in cells of tracks that are fixed, a share of what is left, or as big as their content; a child may span several cells
anchored([...])each child pinned to fractions of the parent, moved by offsets, about a pivot point of its own
absolute([...])each child at a stated position
dock([...])children against the edges in order, the last filling what remains
split([first, second], ratio:, vertical:)two panes with a divider the visitor can drag
scroll([child], across:, down:)a window onto a child larger than itself, with scrollbars

Every container takes its children as one list, its first argument, and then its settings as named options. Any child of any container may be taken out of its parent's flow and placed by position instead.

Properties of every box

These are properties of any node, not of particular kinds, so that anchors, grid cells and flex settings work the same on a panel, a label or a button:

PropertyMeaning
size: (w, h), minSize, maxSizethe size wanted, and its limits; auto for "as the content needs"
padding:, margin:one length all round, or four (left, top, right, bottom)
grow:, shrink:, basis:how a child of a row or column shares spare room or gives it up
alignSelf:this child's own alignment across its row or column
cell: (col, row), span: (cols, rows)its place in a grid
anchor:, offset:, pivot:its place in an anchored parent: two fractions pin it to a point of the parent; four (left, top, right, bottom) stretch it between two points, so anchor: (0, 0, 1, 1) fills the parent, as a dialogue's darkened backdrop does
side:its edge in a dock
visible:, enabled:shown or not; answers the pointer or not (a disabled button is shown dimmed)
clip:hides whatever of its children falls outside it
style:the style it takes its looks from (below)
tooltip:words shown when the pointer rests on it

How you write it

Each container is a function: its children are its first argument, one list, and its settings are named options after it. A plain panel is box([], fill: ...), or box([child], style: ...) with children. A status panel, its first two lines and its corner place, written this way (the clock text is clockText):

part hud
  derive(status, layout(
    anchored([
      column([
          text(clockText(now(world), weather.state)),         // "12:00   fair"
          text(if connected { "connected   " <- rttText } else { "single player" })],
        gap: 3 px, style: styles.hudGlass,                    // top left, 14 px in from each edge, 172 px wide
        anchor: (0, 0), pivot: (0, 0), offset: (14 px, 14 px), size: (172 px, auto))])))

A layout may make many children from a list with map(list, x => element), which gives the list of elements:

column(map(questions, q => button(questionText(q), does: ask(q, to: g))),   // a talk panel's buttons, one per question
       gap: 8 px)

What it means

A layout is a value: a pure description, recomputed by the engine whenever an input changes (here the clock and the weather), and laid out again only where it changed. It runs on the machine that draws it, each visitor's own. A layout may not act; what a control does when pressed is an action, performed by the visitor (below, "Controls").

Not yet supported. The uilayout package has no flex wrap (a row that wraps onto a second line), no grid auto-placement and no animation of properties of its own. The last is not missing from the language: a layout's numbers can be fields that animators drive (Animators).

Text

What it is

Words in a layout are kept as a description (the words, the font, the size, the color) until they are drawn, so they are sharp at any size and angle. The msdftext package reads TrueType fonts, keeps each glyph as a multi-channel signed distance field (each texel holds distances to the glyph's edges, from which an edge one screen pixel wide is drawn at any size), lays words out with kerning, breaks lines at a width and aligns them left, center or right. The richtext package builds documents on it: paragraphs and headings whose words carry styles, lists, and pictures in the flow of the text.

How you write it

text("Lanterns lit at dusk", size: 6 cm, font: inter, color: rgba(60, 40, 20, 255),
     align: center, wrap: 1 m, lineHeight: 1.2,
     outline: outline(rgba(0, 0, 0, 255), width: 2 px),
     shadow: shadow(rgba(0, 0, 0, 160), offset: (2 px, 2 px), softness: 3 px))   // illustration
SettingMeaning
size:the em, the font's own size unit, in px or meters
font:a font the world has (below)
color:the fill, with transparency
align:left, center, right; in rich text also justify (lines widened to fill)
wrap: (to be built)the width at which lines break; none: only at line ends in the words. A label that wraps is (to be built) in uilayout; the dialogue on Overlays writes it
lineHeight:times the font's own line spacing
kerning:pair kerning (on by default)
outline:a color and a width round each glyph
shadow:a color, an offset and a soft edge's width, under each glyph

Outline and shadow are read from the same distance field as the glyph, so they cost no extra pictures; they reach no further than half the field's range beyond the glyph.

Runs and rich text

A run is a stretch of words with one style. Rich text is a list of blocks (paragraphs, headings, list items), each a list of runs:

rich([
  heading(1, "The labyrinth"),                                      // illustration
  paragraph([run("One of us "), run("never", italic: true), run(" lies.")]),
  bullet("Ask one question."),
  bullet("Then choose a door.", depth: 0),
  paragraph([picture(labyrinthPlan, float: right, size: (120 px, 120 px), alt: "the plan"),
             run("The middle lies beyond the two doors.")], align: justify),
  paragraph([run("Read the "), run("rules", underline: true, link: 1), run(".")])])

A layout only describes; a pressed link is an event the world answers in a handler, like a pressed button (on(linkPressed(notice, 1), ...), an illustration of the trigger's name).

Fonts

A font is a TrueType file. Bold and italic are separate faces of a family, up to four (regular, bold, italic, bold italic); a missing face falls back to the regular one, and there is no slanted imitation of italic, because the glyph quads cannot slant. When a font cannot be read, text falls back to a system font, or is not drawn. Which fonts a world may use, and whether a world may bring its own file, is not settled; the license of any font a world embeds is an open question of the text package.

What it means, and what is not yet supported

Images in a layout

picture(image, size: (w, h), tint: color, region: (u0, v0, u1, v1))
minimap(hud.map, size: 176 px, shape: rect, center: (u, v), zoom: 6,
        markers: [marker((u, v), rgba(255, 255, 255, 255), 9 px)])

Any image (Images) may be placed in a layout, tinted and cut to a region. A minimap is a picture with a window that moves over it, a zoom, a rectangular or round frame, and markers placed on it in fractions of the picture; a press on it reports where, as fractions across and down.

Vector shapes

What it is

Drawn shapes: lines, curves, rounded panels, icons. The vector2d package draws them anti-aliased (with smooth edges) on the graphics card, by cutting each shape into triangles whose edges fade over one pixel.

How you write it

shape(path([move(0, 0), line(40, 0), quad(60, 0, 60, 20), cubic(60, 40, 30, 50, 0, 40), close]),
      fill: linear((0, 0), rgba(240, 180, 60, 255), (0, 50), rgba(160, 90, 20, 255)),
      stroke: stroke(width: 2 px, cap: round, join: round, dash: [6 px, 3 px]))   // illustration
shape(roundRect(0, 0, 200 px, 80 px, radius: 10 px), fill: solid(rgba(50, 100, 200, 255)))
shape(circle(20 px, 20 px, 8 px), stroke: stroke(width: 1.5 px), arrows: none)
turned(0.3, shape(...)), moved((10 px, 0), shape(...)), scaled(2, shape(...))
PartWhat vector2d offers
Pathsmove, line, quadratic and cubic curves, arcs, close; rectangles, rounded rectangles, ellipses, circles, polylines
Filla solid color or a linear gradient between two colors along a line
Strokewidth; caps butt, square, round; joins miter (with a limit), bevel, round; dashes with an offset
Arrowheadstriangle, open vee, diamond, circle, bar; solid or hollow
Transformstranslate, scale, rotate, with save and restore
Texta line, a wrapped block, and text with a halo

Rounded panels

A panel's look needs no path: any box's style gives it a fill, a border color and width, a corner radius and a drop shadow. A HUD's panels are typically drawn this way: dark glass with a rounded corner and a thin border.

Not yet supported

Controls

What it is

A control is a box the visitor can press, drag or type into. In a world, a control does not run code of its own: it is one more way for a visitor to perform an action, beside a key, a gamepad button or a voice command. The world never sees keys, only actions (Visitors and their actions), and a control is bound to an action the same way.

The controls the packages offer

ControlWhat the visitor doesWhat it reports
button(label, does:)presses ita click
toggle(label, shows:, does:), radio groupsturns it on or off; one of a grouptoggled
slider(range:, step:, shows:, does:), knob(...)drags a value within a rangechanged, with the value
textField(shows:, placeholder:, does:)types; caret, selection, word movement, clipboardtext changed; submitted with Enter
choices(items, shows:, does:)picks a row from a listselected; activated
tabs(...)picks a pagetab chosen
window(title, movable:, resizable:, closable:)moves, resizes, closes, raises itmoved, resized, closed
hotbar(slots), slot(does:, key:, cooldown:)uses a slot by a key or a press; a slot cools down after useused; busy (with the time left)
inventory(container, columns:), bagsdrags items between slots and bagsa drop done or refused
numberDrag, vectorField, colorPicker, dropdowndrags a number sideways, sets two to four numbers, picks a color, picks from a list that opens in placea change with its value before and after
inspector(part)edits a part's fields, each with the control its kind wantsa change per field

(The package named controls is something else: it steers cameras from input, orbiting and flying; see Cameras.)

How you write it

button("Which door leads to the middle?", does: ask(middle, to: g))
button("Walk away", does: close(talk))         // the action's name is an illustration
slider(range: 0 ..= 1, step: 0.05, shows: visitorSettings.volume,
       does: setVolume)                                                 // illustration: the action carries the value
hotbar([slot(does: paint, key: F1, cooldown: real(1.5 s), icon: icons.splat),
        slot(does: zap,   key: F2, cooldown: real(1.5 s), icon: icons.bolt),
        slot(does: jig,   key: F3, cooldown: real(1.5 s), icon: icons.dancer)])

Written as a world's layout, the slot and the action are one thing: the slot names the action it performs, and cools down after use.

What it means

Styles

What it is

A style is a named set of looks that a world sets once and many boxes share: the parchment of a medieval village's notices, the green-on-black of a ship's terminal, the dark glass of a HUD. Styles inherit from a parent style, and vary with a box's state: hovered, pressed, disabled, focused, checked, selected, a drop accepted or refused.

How you write it

part styles
  field hudGlass: style = style(like: panel,
    background: rgba(9, 13, 20, 170), border: rgba(150, 162, 184, 130), borderWidth: 1 px,
    radius: 8 px, padding: 10 px, spacing: 3 px)
  field hotbar: style = style(like: hotbarSlot,
    background: rgba(9, 13, 20, 170), border: rgba(150, 162, 184, 150), borderWidth: 1.5 px,
    radius: 6 px, size: 46 px,
    hover: style(background: rgba(30, 36, 48, 190)))
  field question: style = style(like: button,
    background: rgba(52, 70, 104, 255), border: rgba(130, 146, 178, 255),
    hover: style(background: rgba(70, 94, 140, 255)),
    pressed: style(background: rgba(40, 54, 82, 255)),
    disabled: style(background: rgba(36, 40, 50, 255)))
  field parchment: style = style(like: panel,                 // illustration
    background: rgba(236, 222, 186, 255), border: rgba(120, 90, 50, 255), borderWidth: 2 px,
    radius: 4 px, textColor: rgba(60, 40, 20, 255), font: serifBook)
  field terminal: style = style(like: panel,                  // illustration
    background: rgba(0, 10, 0, 230), textColor: rgba(80, 255, 120, 255), font: mono, radius: 0 px)

// at the top of the world's text (the root), not inside a part:
field theme: theme = theme(panel: styles.hudGlass, hotbarSlot: styles.hotbar)    // illustration
LookVaries with state?
background, textColor, border, accent (a fill showing a value: a slider's, a selection), track (the groove behind it), shadowyes
borderWidth, radiusyes
font, fontSize, padding, spacing, sizeno: these change a box's size, and are read with no state, so hovering never moves anything

What it means

Placing a layout over a 3-D anchor

What it is

A bubble above a speaker, a name above a head, a marker over a quest-giver: a 2-D layout whose place on the screen follows a point in the 3-D world, drawn in each visitor's picture where that point falls in it.

How you write it

overlay(plate(nameOf(b)),                  // a name plate
  above: b, by: b.height + 0.45 m,         // the point 0.45 m over the head
  pivot: (0.5, 1),                         // the plate's bottom middle sits on that point
  within: 80 m,                            // how far from the eye it is still drawn
  whenBlocked: fade(real(0.17 s)),         // with no clear line from the eye to the point
  keepOnScreen: false)                     // hide a plate off the screen's edge
SettingMeaning
above:, by:the part and the height over its place where the anchor point is
pivot:which point of the layout sits on the anchor
within:how far from the eye it is still drawn
whenBlocked:what happens when something stands between the eye and the anchor
keepOnScreen:held inside the screen's edge when the anchor is off it, pointing toward it; with false a plate is drawn only when its point is in front of the eye and inside the screen
depthhidden pixel by pixel by what stands in front: half a bubble behind a pillar's edge (not yet supported for overlays)

An overlay is drawn over the picture, the same size on the screen however far away its anchor is. The other way to put words at a point is to place them in the world: msdftext can place text in the world fixed, or turned to face the camera (a billboard, either fully or only about the upright axis), lit and hidden by depth like any surface, and growing smaller with distance. The two are the spatial and the non-diegetic interface of Overlays. Text in the world that keeps a constant size on the screen is later work of the text package.

A layout on a surface

part noticeBoard, at: (...)                             // illustration
  part board
    field picture: image
  derive(board.picture,
    layout(column([text("Lanterns lit at dusk", size: 6 cm),
                   text(clockText(lanterns.nextLighting), size: 4 cm)],
                  style: styles.parchment, padding: 4 cm), width: 1.2 m))

The same layout that could sit over the picture is drawn into the board's image, at the resolution the board takes up on the screen (Images). Its controls are pressed by the ray from the eye: the ray is turned into the pixel it hits on the board's image, so hover, press and drag work as on the screen.

How it connects

Open questions

  1. A layout over a list. A layout writes map(list, x => element), a function giving the list of elements; Cone's own collection form, List[element]() <- each x in list yield ..., builds the same list. A layout is a value, so it needs a form that gives a value; whether both stay is open (A boss and its army; Control flow).
  2. The vocabulary is to be shaped. The element names on this page are placeholders; which elements the browser's vocabulary offers, and how they are qualified, is open (the namespacing question of Packages).
  3. Text and layout meet at a closure. Whether the text package also answers caret and selection measures, and the height of a wrapped label for a given width.
  4. One renderer for 2-D. Whether vector2d becomes the renderer of uilayout's drawing list.
  5. Fonts. Which fonts a world may name, and the license of any font a world embeds.
  6. Accessibility. The layout tree has roles by kind, but nothing passes it to a screen reader; from the start, or later.
  7. Two inventories. uilayout's inventory model and worldinteract's inventory are two models; one should own and the other show.

← Images | Contents | Overlays, HUDs and speech →