Open Asterune
Interface layout and input routing

Interface layout and input routing

Trace constraint resolution, automatic-size feedback, layout invalidation, clipping, draw preparation, and pointer ownership.

Asterune resolves interface instances into ordered rectangles before either rendering or pointer routing uses them. The shared layout result includes visibility, opacity, rotation, clipping, and resolved component state. Input therefore tests the geometry produced by layout instead of maintaining an independent approximation of each element's position.

Layout, text measurement, draw preparation, and interaction have separate caches and state. Changing a color, adding a child, scrolling a container, and resizing the window do not require the same work. The engine keeps those invalidation conditions distinct.

Canvas coordinates and constraint passes

A layer resolves against a canvas rectangle. Screen layout derives that canvas from viewport size and GUI insets. A surface or billboard supplies a canvas appropriate to its projected or local interface. The same subtree can therefore have different resolved layouts at different canvas sizes.

The traversal first collects layout settings from the parent's attached layout and constraint instances. It applies padding to establish the available child rectangle. Child scale dimensions follow that rectangle; pixel offsets and text sizes also inherit the product of ancestor UIScale values.

List, grid, page, and stack settings establish child placement before the child's own constraints finish its rectangle. Size constraints and aspect ratio operate on the resolved dimensions. An element's own scale then changes its size about its anchor point. Upright layout snaps rectangle edges to pixels; rotation retains the floating-point geometry needed by later transforms.

Stack layout first resolves visible in-flow children and their constrained sizes. Wrapping divides those items into lines using their main-axis extent and the configured gap. Each line then distributes positive free space among children according to their nonnegative grow weights. Without growth, the justification rule distributes that free space between or around items.

The grow calculation does not shrink an overflowing line: free space is clamped to zero before distribution. Cross-axis alignment uses either the single line's available extent or a wrapped line's tallest or widest item. Out-of-flow children do not contribute to those stack calculations.

Automatic sizing as bounded feedback

Automatic size can depend on child rectangles while child scale and wrapping depend on the parent's size. Asterune resolves that dependency through repeated layout passes rather than assuming one top-down traversal is sufficient.

The initial pass establishes each object's base size. The solver measures visible in-flow child extents and text bounds, adds padding, and uses the larger of content extent and base size on each automatic axis. It then lays out the tree with those automatic sizes as overrides. Wrapped text is measured against the content width produced by that pass.

The solver allows up to 32 feedback rounds. Sizes settle when both dimensions of every automatic override differ by less than 0.01 pixels from the preceding round. A nonautomatic subtree needs no feedback rounds.

Previous automatic sizes accelerate later solves. For nested stacks, a bottom-up estimate also supplies initial guesses from the leaves. These values are solver inputs, not an independently accepted layout result: wrapping and constraints still participate in the normal passes.

If a solve using guesses reaches its iteration limit, the engine reruns the bounded solve from fresh base sizes. It does not keep an unsettled result as the next edit's automatic-size guess. This prevents a cyclic layout from growing again on every scroll or paint change merely because the preceding bounded result became its next starting point. The iteration bound limits work; it does not establish that every cyclic constraint graph converges.

When only paint or position changes and the intrinsic sizes remain settled, the engine can reuse the size estimates and perform one rectangle pass. It still refreshes positions, visibility, and clips even when the measured sizes need no new feedback loop.

Layer caches and targeted invalidation

The layout memo keys an entry by layer identity, canvas rectangle, and enabled state, with a GUI-specific topology revision. It retains up to 128 entries in least-recently-used order. Canvas coordinates use their exact floating-point bits in the key, so differently sized views do not accidentally reuse one another's rectangles.

Resolved output is shared through reference-counted vectors. A cached lookup can return that output without rebuilding every rectangle. A secondary ID index supports individual rectangle lookup without repeatedly walking the whole layer. Parallel execution warms these entries and indexes before worker reads begin.

Invalidation follows the scope of the edit. Moving GUI objects invalidates the layers they leave and enter. Moving a layer changes the broader GUI topology revision. Spawning an unrelated world object does not require every interface layer to lay out again.

Specialized GUI edits compare component state before invalidating. Color, opacity, input flags, and other paint or interaction changes preserve settled intrinsic sizes. Changing fixed-size text can update the cached text payload without moving its rectangles or descendants. Automatic text size requires measurement because the same text edit can change its parent's content extent.

These distinctions continue into rendering. The GPU preparation cache compares viewport, render data, fallback-font generation, scene-image locations, and image-atlas slots. Unchanged input can reuse the prepared frame. A layout cache hit alone does not establish a render-data cache hit if text focus, image content, or an atlas location changed.

Paint order, clipping, and scrolling

Layer display order establishes the screen-layer order. Within a layer, sibling mode sorts children within their parent groups and emits a parent's subtree as one ordered traversal. Global mode additionally sorts resolved items across the layer by Z index. Text labels and text boxes occupy a later tie-breaking queue than nontext objects with the same Z index.

The clip representation contains an axis-aligned rectangle and one optional rounded or rotated mask. Upright rectangular clipping intersects the inherited rectangle. Rounded or rotated clipping uses the mask's local coordinates, including inverse rotation and per-corner radii. Compatible unrounded masks with the same rotation can merge their rectangles.

The representation is not an arbitrary stack of analytic masks. When complex masks cannot merge, the inner mask becomes the carried mask while accumulated upright rectangular bounds remain. This matters for deeply nested rotated or rounded clipping: the engine does not evaluate an unlimited intersection of every ancestor's curved boundary.

Scrolling changes the child canvas, not the frame's own visible rectangle. The resolver clamps canvas position to the available scroll range, offsets the child coordinates, and clips descendants to the scrolling viewport. The pointer test checks the resulting clip before testing an element itself.

Pointer geometry inverse-rotates the test point around the element's center before comparing it with the element's rectangle. The inherited rounded mask uses its own containment test. An element's own painted corner shape does not by itself replace its rectangular pointer bound; the relevant clip mask is a separate input.

Draw instances and upload reuse

Draw preparation expands resolved components into ordered quads and text runs. Those become instances of a shared unit quad, with paint and clip-table indices rather than separate geometry for every rectangle. The fragment shader handles shape coverage, gradients, shadows, images, scene images, and glyph masks from the instance's selected mode.

Batching preserves painter order. Adjacent instances with the same scissor can share a draw; the renderer does not freely sort transparent interface content by texture. Upright glyph quads can be trimmed on the CPU together with their UV coordinates. That trim lets neighboring labels share a broader scissor without allowing glyph pixels outside their original rectangular clips. Rotated or shape-dependent shading keeps its required local coordinates.

The renderer compares prepared paint and clip tables before uploading them. Instance data is compared in pages of 64 instances, and unchanged pages avoid another buffer write. Buffer growth uses larger capacities and forces the needed upload. This reduces transfer work for a mostly static interface while preserving the order of the final instance stream.

Font shaping and glyph-atlas invalidation have additional rules described in Text shaping and glyph rendering.

Pointer targets and press ownership

Pointer routing walks layers from front to back, and walks each layer's resolved items in reverse paint order. It distinguishes the first pointer-catching hover target from the first target that actively takes input. A decorative pointer-catching item can receive hover events while a lower active control owns the actionable press.

An active, interactable primary-button target is retained as the press owner. Moving away does not transfer that press to a different button. The router continues sending motion to the retained target, and release returns the up event to that owner. A click is generated only when the active hover target at release still matches the retained press target.

Dragging adds another retained target. Motion updates its offset and emits drag movement until release or cancellation. Cancellation clears hover, press, raw-input, and drag ownership, preventing a later release from completing an abandoned interaction.

The host coalesces pending pointer movement to the latest position before routing it. Button processing flushes that pending position first, so a press does not test the preceding frame's pointer location. Captured movement can reuse the existing layout list instead of forcing a fresh solve for every motion event.

The routing result carries whether the interface consumed the interaction. The application forwards that result with the corresponding game input event. It does not suppress all observation of pointer movement merely because an interface control owns the press. This lets game input distinguish observation from an unhandled gameplay action.

Text focus and composition ownership

Text focus is separate from pointer hover and press state. Focusing a text box clears active script input state, updates keyboard or input-method availability, and emits focus callbacks. Clicking another actionable target can release that focus. Validation also releases a focus target that is no longer usable.

Input method editor (IME) preedit remains a separate composition string and caret, rather than being inserted into the persistent text on every update. An IME commit clears that composition and applies the insertion to the editable field. Disabling composition or changing focus clears the transient preedit. A committed edit then uses the ordinary caret and selection update path.

An offscreen interface host can perform layout and editing without a native window. Native IME availability and clipboard operations depend on the host services supplied to that router. The shared text geometry still supplies caret and selection positions.

The interface guide covers authoring controls and layouts. The input guide covers events and action bindings. XR frames and tracking describes how a headset pointer and interface panel enter the client frame.