Skip to content

xml_structure

Jan Boon edited this page Jul 21, 2026 · 2 revisions

title: Interface XML Structure description: Files, parse order, defines, templates, vectors, styles, options blocks and window registration published: true editor: markdown

An interface is a set of XML files, each rooted at <interface_config>, parsed as one merged document: the children of every file are appended in order and then processed together. Declaration order matters across files exactly as it does within one file — which is why skins and shared styles are parsed first (config_sample.xml, widgets_sample.xml before main_sample.xml in the sample; config.xml and widgets.xml first in the client).

The parse pipeline

Before each top-level element is handled, three text transforms run in order:

  1. Feature flags<flag name=.../> and flat <if flags=...>/<elseif>/<else>/<endif> markers conditionally strip siblings (a preprocessor for product flavors).
  2. Define expansion — every %name in every attribute is substituted (see below).
  3. Style mergingstyle="name" attributes pull in style properties (element attributes win).

Then two passes over the top-level elements:

  • Pass 1: <template>, <options>, <define>, <style> — declarations.
  • Pass 2: <root>, <group>, <instance>, <view>, <ctrl>, <vector>, <link>, <variable>, <tree>, <proc>, <anim>, <sheet_selection>, <lua> — content.

Unknown elements are offered to embedder-registered parser modules (this is where client-only elements like <macro> and <action_category> live — they are not part of the library).

A failed element normally warns and is skipped; the exception is <lua>, whose failure is fatal — scripts run at parse time and the interface is assumed to depend on them.

Defines

<define id="case_upper" value="2" />
<define id="menu_bg" value="w_modal.tga" />
<view type="text" case_mode="%case_upper" ... />

<define> registers a textual macro; %name is replaced in every attribute of every later element. Names use [A-Za-z0-9_]; %% escapes a literal percent. An unknown %name fails the whole element that used it. value_from_code="expr" evaluates an interface expression at parse time and stores its string result.

The client's familiar %case_upper, %case_normal etc. are defines from its config files, not built-ins. A standalone interface must declare them (0–5, see views) before use. {.is-info}

Templates and instances

<template name="inv_slot" keep="true" id="" posparent="parent" posref="TL TL" x="0" y="0">
	<group id="#id" posparent="#posparent" posref="#posref" x="#x" y="#y" w="44" h="44">
		<view type="bitmap" id="back" posref="MM MM" texture="w_slot_item.tga" global_color="false" />
	</group>
</template>

<instance template="inv_slot" id="b0" x="12" y="-70" />
  • Attributes on the <template> element are parameter defaults; #param inside any attribute of the template body is replaced by the instance's attribute of that name, falling back to the default. Longest name wins when parameters share prefixes; there is no escape for a literal #.
  • The template body is expanded inline where the <instance> stands, then parsed as normal elements.
  • keep="true" retains the template after parsing so it can be instantiated at runtime (createGroupInstance from code or client Lua).

<vector> is the repeated form of <instance> with $i index substitution and position chaining — syntax and semantics in Layout.

Styles

<style style="text_button_16" type="text_button" tx_normal="w_text_button" ...
       fontsize="10" shadow="true" />

<ctrl style="text_button_16" id="bt_ok" hardtext="OK" ... />

A <style> records a named bag of attributes; any element carrying style="name" receives each style attribute unless it already defines it — element attributes override the style. Styles are how the sample and client keep one skin definition per button family.

Options blocks

<options type="layer" name="layer0">
	<param name="tx_tl" value="w_l0_tl.tga" />
	<param name="header_h" value="24" />
	...
</options>

<options> stores a named parameter map that widgets look up by name at setup: container windows read their entire skin from a layer options block (see Skinning), modal frames from a frame block (skin_modal in the shipped data), plus the fixed-name blocks container_move_opt and container_insertion_opt. herit="other" copies an existing block as the base.

Containers dereference container_move_opt unchecked. An interface with container windows but without that options block crashes on the first layout pass. Ship the global options blocks before any window XML. {.is-warning}

Master groups, windows and

<root id="sample" x="0" y="0" w="800" h="600" active="true" />
...
<group type="container" id="hello" ... />
<tree node="hello"></tree>
  • <root> creates (or switches to) a master group; its id gets an automatic ui: prefix. Every subsequent top-level group/view/ctrl/link/tree is parented to the current master group.
  • <tree node="shortname"/> registers a direct child group of the master group as a window. Only windows draw; a plain group at the root level parses fine and never renders. The name match is case-insensitive on the last id segment.
  • Nested <tree><node node="child"/>...</tree> entries attach containers inside other containers — this is what builds docked rollout lists (see Windows and containers), not XML nesting of the container groups.
  • Modal groups are auto-registered — no <tree> needed.
  • Window stacking respects win_priority (0–7, default 3) declared on the group.

The embedding application decides which master group is active (the sample activates ui:sample; the client runs ui:interface plus separate master groups for login and outgame).

Database seeds:

<variable entry="UI:SAVE:CONTENT_ALPHA" type="sint32" value="255" />
<variable entry="UI:TEMP:MATRIX:$i:VAL" type="sint32" value="0" size="16" />

Creates database leaves with initial values — the interface equivalent of declaring your state. Types: sint64, sint32, float/double, bool, rgba, hotspot. With size="N", $i in the path expands to N entries. If value is not a literal (doesn't start with a digit/-), it is treated as another database path to link to. Details and the fresh-database pitfalls in Database.

Procedures and Lua hooks

<proc> defines named action-handler sequences (see Action handlers and procedures); <lua file="script.lua"/> executes a script at parse time (see Lua scripting). Both are top-level elements.

Element instantiation

<group type="...">, <view type="..."> and <ctrl type="..."> all draw from one class factory keyed by the type name; the tag only constrains what the result must be (a ctrl must be interactive, a group must be a group — a <group> with an unknown or missing type silently falls back to a plain interface_group, while views and ctrls fail). The full type-name table lives in NeL GUI; the per-widget attributes are documented in views, controls and windows.

Clone this wiki locally