Appearance
Editor UI Controls
ReGame editor UI is authored as RSX on UISurface so layout, input, accessibility, and automation can share one path.
Use ScrollView for small or custom child content. Use data-driven virtual controls when a panel can grow large enough that creating every row would hurt frame time.
Editor Context
Editor tools can read the current editor dimensions through the Editor singleton. It is available in RGScript, RSX render expressions, and RStyle expressions for registered editor UI surfaces.
Editor.surface is the content area of the surface currently being mounted. Use it for responsive tool layout inside a dock, panel, overlay, modal, or toolbar surface. Named regions describe other editor areas:
Editor.windowEditor.sceneTreeEditor.fileSystemEditor.viewportEditor.bottomDockEditor.inspectorEditor.gameEditor.leftDockEditor.rightDockEditor.centerDock
Each region is an EditorSurfaceMetrics value with x, y, width, height, visible, and active. getWidth() and getHeight() return the same size values for RGScript code that prefers method calls.
rgscript
let viewportWidth = Editor.getViewportWidth()
let inspectorWidth = Editor.inspector.getWidth()
let dockHeight = Editor.bottomDock.heightUse Editor.surface for the current tool's own layout:
rstyle
assetGrid: {
width: {Editor.surface.width},
height: {Editor.surface.height - 42},
overflow: "scroll"
}Use named regions only when the tool needs another editor area's size. For example, a viewport overlay may align to Editor.viewport, and an inspector companion tool may choose a compact layout when Editor.inspector.width is narrow.
UIComponent surfaces can use ready for mount-time initialization. Runtime interaction should use control events such as onClick, onChange, and onScroll instead of polling layout every frame.
RSX Source Mode
Files ending in .rsx use the normal RGScript class parser with one extra UI member: render { ... }. The class must extend UIComponent to use render; ordinary .rg scripts and non-UIComponent classes reject render blocks.
Write fields, methods, imports, helper logic, and lifecycle code as RGScript outside render. This includes normal typed arrays, records, vectors, Math helpers, console output, indexed assignment, helper methods, and lifecycle state changes. Keep render declarative: use RSX markup and expressions that produce values or elements, such as {items.map(item => <Row item={item} />)} or {item.enabled ? "Enabled" : "Disabled"}. Render expressions cannot assign, compound-assign, increment, or decrement state.
Imported .rsx components are component types in RGScript. A parent can keep a typed handle, find a retained child component by semantic id, call its public methods, or write its public fields:
rsx
import CounterPanel from "./CounterPanel.rsx"
class Dashboard extends UIComponent {
private counter: CounterPanel
ready {
counter = root.find<CounterPanel>("main-counter")
}
public addOne(): void {
counter.increment()
}
public rename(nextTitle: string): void {
counter.title = nextTitle
}
render {
<View automationId="dashboard">
<CounterPanel automationId="main-counter" title="Main Counter" />
</View>
}
}Mounting RSX With InterfaceHost
Attach an InterfaceHost component to a GameObject to mount an .rsx source in a scene. One InterfaceHost can render an entire RSX tree, so a single host may contain a label, image, progress bar, and any other elements that belong to the same interface. A scene may also use multiple InterfaceHost GameObjects when interfaces need independent visibility, transforms, or lifecycles.
See the InterfaceHost guide for a complete Inspector walkthrough, floor purchase-pad recipe, moving overhead UI setup, property defaults, events, limitations, and troubleshooting.
The Space property chooses how the host is rendered:
screenmounts the UISurface in the normal screen overlay. This is the default.worldrenders the UISurface as a transparent rectangle in the 3D scene.
Changing Space to world in the component inspector adds a Transform3D when the GameObject does not already have one. Transform3D is the only owner of world position, rotation, and scale; InterfaceHost does not add a second set of X/Y/Z fields. This keeps normal hierarchy behavior intact.
To make an overhead name, health bar, or order indicator follow a moving character, create a child GameObject under the character and attach InterfaceHost plus Transform3D to that child. Its local transform offsets the interface above the character, and the complete parent transform chain moves it with the character. The same approach works for a sign attached to a model or a label attached to another moving GameObject.
For an interface painted onto a floor or wall, use a world host and rotate its Transform3D to match the surface. For example, a floor pad normally uses an X rotation near -90 degrees and a small offset above the floor. depthBias can remove the remaining z-fighting without becoming a second position control.
World Surface Properties
surfaceSizeis the RSX render-target resolution in pixels. Its default is512 x 256.pixelsPerUnitconverts that pixel size into world units. At the default100, a512 x 256surface occupies5.12 x 2.56world units beforeTransform3Dscale is applied.depthTestallows ordinary 3D geometry to occlude the interface and is on by default.receiveShadowsoptionally lets directional scene shadows darken visible interface pixels. It is off by default.doubleSidedcontrols whether the interface is visible from both sides.depthBiasapplies a small render-depth offset for interfaces placed directly against other geometry.
World surfaces remain otherwise unlit. When receiveShadows is enabled, the shared directional shadow map darkens visible surface pixels without changing transparent alpha. This means a worker or prop shadow can fall across floor-pad artwork. It does not make the InterfaceHost cast its own shadow onto the floor. Screen surfaces remain ordinary overlays and are never affected by scene lighting or shadows.
Moving a parent or changing a Transform3D updates the world rectangle's transform; it does not require the RSX content to be rerendered. A source or structural change can replace the UISurface. Ordinary retained state changes use the frame commit described below.
Retained Frame Commits
InterfaceHost mutation is frame-coalesced. Pointer handlers, UIComponent methods, and imperative retained-element setters may perform several writes during one frame; ReGame applies one InterfaceHost commit after frame processing.
The commit tracks four independent kinds of change:
structurechanges replace only the affected surface and run its cancellation and mount lifecycle;layoutchanges update dirty nodes in the existing Yoga tree;paintchanges regenerate cached render items for dirty elements while clean elements and clean hosts reuse their prior output;semanticchanges republish the accessibility tree without requiring a paint rebuild.
Setter return values and retained property reads are authoritative immediately. Geometry, render items, world render-target revisions, and accessibility publication reflect the completed frame commit. Do not force a source reload to display interaction state, and do not split frequently changing labels or controls into separate InterfaceHosts as a performance workaround.
Screen and World hosts share UISurface input. World hosts project the active Camera3D ray into surface pixels, then preserve normal per-pointer capture through Move, Up, or Cancel. Use an Area3D when a floor pad should react to player occupancy; use an RSX Button or Pressable when it should react to a pointer. The full routing order, typed payload hierarchy, Joystick API, and platform notes are documented in Pointer Input And Joystick.
World InterfaceHosts use the shared OpenGL runtime path on desktop and Android. Native iOS currently keeps screen InterfaceHosts on its 2D Metal path, but it does not yet render world InterfaceHosts because that renderer does not have the engine's 3D camera/depth pipeline. Native iOS world-host support requires that 3D Metal path rather than a special InterfaceHost fallback.
Built-In Fonts
RSX text uses ReGame's built-in Inter family when fontFamily is omitted. Regular, semibold, and light atlases are engine resources; a new data-only project does not need to copy them into src/assets/fonts.
Desktop editor bundles must contain the atlases under Resources/fonts. Android, iOS, and web exports stage the same built-in atlases into the exported project payload. If a fresh project displays the pixel/debug fallback font, check the Player log for Failed loading font atlas. That message means the engine or export bundle is missing its shared font resources; copying a font into one game only hides the packaging defect and does not fix other projects.
UI Builder Preview
The InterfaceHost inspector exposes Space as the screen/world selector and keeps world placement in the normal Transform3D fields. In UI Builder, enable Scene (uiBuilder.viewport.showScene) to render the active scene behind the RSX preview. For a world host, this shows the interface at its actual 3D position, rotation, scale, and parent-relative placement instead of treating it as a flat screen overlay.
The UI Builder Components palette includes Image. Inserting it creates a normal RSX Image element with editable source and resizeMode properties, so an interface can combine authored images with Text, Button, and other retained elements in the same host.
Lifecycle Events
InterfaceHost exposes named lifecycle and geometry events through the shared GameObject event lane:
onMountedfires after the UISurface has been built, registered, and laid out.onUnmountedfires before the retained surface is released.onMountSpaceChangedreports a completedscreen/worldchange.onLayoutreports completed UISurface layout.onGeometryChangedreports changes to surface size or pixel-to-world geometry.onRenderSettingsChangedreports changes to depth testing, shadow receiving, sidedness, or depth bias.onRenderTargetReadyreports that a world surface texture is available.onRenderTargetInvalidatedreports that the current world surface texture must be rebuilt.onLoadFailedreports source, RSX build, or world render-target failures.
State is changed before these notifications are emitted, so listeners can read the new authoritative value. Keep the returned event controller when subscribing and cancel long-lived subscriptions during teardown; owner-bound listeners are also cleared when their owner or mounted surface is destroyed.
UIComponent List Rendering
Use map inside a render expression when a component needs to create repeated UI from an array. The callback returns RSX, and the item can be passed into a reusable row component through normal attributes.
rsx
import ComponentRow from "./ComponentRow.rsx"
class ComponentList extends UIComponent {
public components: string[] = ["Camera", "Light", "Audio"]
render {
<View automationId="component.list">
{components.map(component =>
<ComponentRow automationId={"component.row." + component} item={component} />
)}
</View>
}
}rsx
class ComponentRow extends UIComponent {
public item: string = ""
render {
<View>
<Text>{item}</Text>
</View>
}
}Render blocks should describe UI. Put normal RGScript logic in fields, methods, lifecycle blocks, or helpers outside render, then use render expressions for UI values. Event attributes take handler references such as onClick={increment} or handler-valued props such as onClick={props.onAdd}; render does not execute calls like onClick={increment()}. ReGame does not support raw foreach blocks, side-effectful .forEach(...) calls, console.log/warn/error calls, or assignment/update expressions such as {count = count + 1} and {count++} directly inside RSX render markup; use {items.map(item => <Row item={item} />)} for repeated children.
Conditional expressions use the normal RGScript condition ? valueWhenTrue : valueWhenFalse form and work in .rg scripts and .rsx render expressions. They are useful for row labels, state badges, and style selection when the expression stays side-effect free.
VirtualTree
VirtualTree renders hierarchy data with tree branch lines, disclosure state, icons, selection, and depth metadata. It is meant for large tree panels such as File System, Scene Tree, UI hierarchy, and similar editor surfaces.
Instead of creating every expanded row as a UISurface element, VirtualTree flattens the expanded tree, creates only the visible rows plus a small overscan buffer, and inserts spacer elements above and below those rows so the ScrollView keeps the correct total scroll height.
rsx
<ScrollView automationId="fileSystem.scroll" style={styles.scrollViewport} contentContainerStyle={styles.treeContent}>
<VirtualTree rootLabel="Project Root" rootIcon="folder-open" rowHeight="30" itemStyle={styles.treeItem} />
</ScrollView>Props
rowHeight: fixed pixel height used for visible-row math. Match this to the row style height.rootLabel: optional synthetic root row, such asProject Root.rootIcon: icon for the synthetic root row.itemStyle: style applied to each visible tree row.
VirtualList And VirtualGrid
Use VirtualList or VirtualGrid for large flat data arrays where the item UI is still authored by the component. They work like a fixed-size FlatList: the virtualizer owns scroll range math and visible-window expansion, while the child template owns the row or tile contents.
rsx
class AssetGrid extends UIComponent {
public items: record[] = []
public hostWidth: number = 640
public hostHeight: number = 320
render {
<VirtualGrid
automationId="assets.grid"
accessibilityLabel="Assets"
data={items}
itemWidth={104}
itemHeight={132}
viewportWidth={hostWidth}
viewportHeight={hostHeight}
gap={10}
overscan={1}
style={styles.gridViewport}
onScroll={handleScroll}
>
<Pressable automationId={item.automationId} onClick={item.action} style={styles.tile}>
<Text>{item.name}</Text>
</Pressable>
</VirtualGrid>
}
}Props
data: array expression to render.itemHeight: fixed item height in pixels.itemWidth: fixed item width in pixels. Required byVirtualGrid.viewportHeight: visible viewport height used for range math.viewportWidth: visible viewport width used for grid column math. Required byVirtualGrid.gap,rowGap,columnGap: spacing used in the virtual range and item placement.overscan: extra rows to render before and after the viewport.scrollOffsetY: optional explicit vertical scroll offset. When omitted, retained editor and InterfaceHost surfaces preserve scroll offset byautomationId.itemName: optional template local name. Defaults toitem.indexName: optional template local name. Defaults toindex.
onScroll receives (scrollOffsetY, scrollOffsetX, deltaY, deltaX) when the virtualized element is rendered from a UIComponent.
VirtualList and VirtualGrid require fixed item sizes. Variable-height measurement is a separate feature because it needs a measurement cache and reflow strategy. Put viewport sizing in RStyle or literal inline style for now; inline style object expressions such as style={{ width: hostWidth }} are not supported yet.
For editor tools, prefer Editor.surface.width and Editor.surface.height for viewportWidth and viewportHeight. This keeps bottom-dock and side-dock extensions responsive without hard-coded host dimensions.
rsx
<VirtualGrid
automationId="assetBrowser.grid"
data={items}
itemWidth={112}
itemHeight={132}
viewportWidth={Editor.surface.width}
viewportHeight={Editor.surface.height - 42}
gap={10}
overscan={1}
onScroll={handleScroll}
/>Only the visible rows plus overscan are expanded into UISurface elements. Keep item dimensions fixed and stable; changing item size during scroll invalidates the virtual range math and should be modeled as a separate layout mode.
Typed Drag And Drop
Retained RSX elements use one typed drag route across editor panels, inspectors, viewports, semantic automation, and gameplay InterfaceHost surfaces. A source declares dragPayloadType and dragPayloadText; a target declares the comma-separated payload types it accepts.
rsx
<Pressable
automationId="assets.ship"
dragPayloadType="MODEL_ASSET_FILE"
dragPayloadText="file://models/ship.glb"
onDragStart={beginShipDrag}
onDragEnd={finishShipDrag}
/>
<View
automationId="inspector.mesh"
dropPayloadTypes="MODEL_ASSET_FILE"
onDrop={assignMesh}
/>Targets receive onDragEnter, onDragOver, onDragLeave, and onDrop. Sources receive onDragStart, followed by either onDragEnd or onDragCancel. Every callback receives a UIDragEvent containing payloadType, payloadText, bindingKey, coordinates, and semantic target ids. The source state is cleared before its terminal event is emitted.
Disabled sources cannot start a drag, disabled targets cannot accept one, and a shared popup or modal capture cancels the behind-the-overlay drag route. Give every source and target a stable automationId; semantic tools expose startDrag and dropPayload actions using the same event path as physical input.
