Skip to content

first_window

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

title: Your First Window description: A minimal working NeL interface file, explained line by line published: true editor: markdown

This walkthrough builds a complete little window (a click counter) in pure interface XML: no C++, no Lua. It exercises the four things every interface uses: a window, a view, a control, and the database.

The finished tutorial window: a text label, a live counter bound to the database, and a button that increments it

Running it

The easiest playground is the GUI widget sample. Save the file below as hello.xml anywhere, then:

nl_sample_gui -d /path/to/your/dir --xml hello.xml

The sample boots the whole widget stack (skin textures, styles, fonts, master group) and parses your file on top, so your window appears next to the showcase windows. In the Ryzom client the equivalent iteration loop is /loadui user/hello.xml.

The file

<interface_config>

<variable entry="UI:TEMP:HELLO" type="sint32" value="0" />

<group type="container" id="hello" w="260" pop_min_w="260" pop_max_w="260" h="130" pop_min_h="130" pop_max_h="130"
	title="Hello NeL" resizer="true" global_color="false" header_color="UI:SAVE:WIN:COLORS:SAMPLE"
	title_bar_open="false" open_button="false" right_button="true" lockable="false" openable="false"
	opened="true" movable="true" active="true" x="450" y="560">
	<group id="header_closed" x="0" y="0" h="12" posref="TL TL"></group>
	<group id="header_opened" x="0" y="0" w="0" h="16" posref="TL TL"></group>
	<group id="content" x="0" y="0" posref="TL TL" w="0" h="0">
		<view type="text" id="label" posref="TL TL" x="12" y="-12" fontsize="14" shadow="true"
			global_color="false" color="255 255 255 255" hardtext="You clicked:" />
		<view type="text_number" id="count" posparent="label" posref="MR ML" x="8" fontsize="16" shadow="true"
			global_color="false" color="150 255 150 255" value="UI:TEMP:HELLO" />
		<ctrl style="text_button_16" id="bt_click" posparent="label" posref="BL TL" y="-12"
			onclick_l="set" params_l="dblink=UI:TEMP:HELLO|value=add(@UI:TEMP:HELLO,1)" hardtext="Click me" />
	</group>
</group>
<tree node="hello"></tree>

</interface_config>

Line by line

<interface_config> is the root element every interface file has. All parsed files are merged into one document, so your file can reference templates, styles, options and database entries declared in files parsed before it (here: the sample's own definition files). See Interface XML structure.

<variable entry="UI:TEMP:HELLO" type="sint32" value="0"/> creates a leaf in the interface database and seeds it with 0. The database is the reactive state store: widgets bound to a leaf update when it changes.

<group type="container" id="hello" ...>: a container is the standard window widget, with title bar, close button, dragging and resizing. The attribute pile is normal for containers; the load-bearing ones here:

  • id="hello" becomes ui:sample:hello (the sample's master group is ui:sample). Every element needs an id.
  • x="450" y="560" with the default posref sets the window position. Interface coordinates are y-up: on a 720-high canvas, y="560" puts the window's top edge 160 pixels from the top of the screen.
  • w/h plus pop_min_*/pop_max_* set the size and its resize clamps. A container computes its height from content unless pinned like this; a fixed window without clamps and with empty-sized content collapses to its title bar.
  • title, movable, resizer, active="true" are self-describing. header_color names a database entry, not a literal color.

header_closed / header_opened / content: containers look up these three child groups by id. Content goes in content; the header groups hold widgets shown on the title bar in the collapsed/expanded states (empty here, but they must exist for the layouts that use them). See Windows and containers.

The text view is <view type="text" ...> with posref="TL TL" x="12" y="-12": pin my top-left to the content group's top-left, then offset 12 right and 12 down (negative y moves down in the y-up coordinate system). See Layout.

The counter is <view type="text_number" value="UI:TEMP:HELLO"/>, which displays a database leaf and tracks it automatically: bind once, never repaint by hand. posparent="label" posref="MR ML" chains it to the label instead of the group: "my middle-left on the label's middle-right", i.e. inline to the right.

The button is <ctrl style="text_button_16" ...> using a style defined by the sample (a text_button with the w_text_button_* skin at fontsize 10). The interesting part is the event wiring:

onclick_l="set" params_l="dblink=UI:TEMP:HELLO|value=add(@UI:TEMP:HELLO,1)"

Left-click runs the action handler set, whose params say: evaluate the interface expression add(@UI:TEMP:HELLO,1) (@path reads a database leaf) and write the result back to that leaf. The text_number view sees the change and redraws. That is the whole loop: control → action handler → database → view.

<tree node="hello"></tree> registers the group as a window of the current master group. This is the step everyone forgets: an unregistered group is parsed, laid out and never drawn. Modal groups are the exception (auto-registered).

Where next

Clone this wiki locally