-
Notifications
You must be signed in to change notification settings - Fork 113
widget_sample
title: GUI Widget Sample description: Standalone NeL GUI showcase sample (nl_sample_gui) using the legacy w_ interface skin published: true editor: markdown
This page documents the sample itself. The general interface documentation extracted from this work lives under Interface (authoring: layout, widgets, database, Lua) and Embedding NeL GUI (the host-application contract). {.is-info}
nl_sample_gui (nel/samples/gui/) is a standalone showcase of the NLGUI widget library. It boots the widget manager, view renderer and interface parser outside the Ryzom client, parses a self-contained interface definition, and renders it with the legacy w_ interface skin — the older beveled reference style whose window frames (w_header_*, w_l0_*, w_l1_*, w_modal_*, w_scroll_l0_*) were later replaced name-for-name by the shipped skin_* set (non-frame w_ art — gauges, tab buttons, brick slots and more — still ships alongside). The sample exercises that otherwise-unused frame set and serves as a minimal reference for embedding NLGUI in any NeL application.
-
main.cpp— bootstrap:UDriver+ text context,CViewRenderer::setDriver/setTextContext+init, atlasloadTextures,parseInterface,updateAllLocalisedElements,activateMasterGroup("ui:sample"), then the pump/draw loop (EventServer.pump→sendClockTickEvent→checkCoords→drawViews). ANLGUI::CEventListenerroutes driver mouse events into the widget manager. -
data/config_sample.xml— master<root id="sample">, database defaults, and thew_skinoptionsblocks (layer0,skin_modal,container_move_opt,container_insertion_opt,context_menu_back, …). -
data/widgets_sample.xml— shared widget styles (text_button_16,button_ok,tab_button) on thew_text_button_*/w_tab_*textures. -
data/main_sample.xml— the showcase windows, thegeneric_pointerview, and the<tree>nodes that register windows. -
data/minesweeper_sample.xml+data/minesweeper.lua— the Minesweeper demo app (see below).
No interface texture data lives in the code repository. At build time, CMake runs the build_interface tool over interfaces/v3/ of a ryzomcore_graphics checkout (NL_GRAPHICS_DIR, default sibling of the source tree) and packs gui_sample_atlas.png/.txt into the build directory — the same pattern the WebGL water sample uses for its assets. build_interface --keep-going (-k) skips inputs that cannot be loaded as bitmaps (e.g. unmaterialized git-lfs pointer files) with a warning instead of aborting the pack. The font comes from fonts/ingame.ttf in the same checkout (basic.ttf works equally well).
The Emscripten build preloads the generated atlas, the XML and the font into the .data bundle; when cross-compiling, point NL_BUILD_INTERFACE_TOOL at a host-built build_interface.
Native (needs WITH_GUI=ON, which needs Lua and ryzom/luabind; build both into a local prefix and pass LUA_*/LUABIND_* cache variables if the distribution does not package them):
cmake -DWITH_GUI=ON -DWITH_NEL_SAMPLES=ON ...
ninja nl_sample_gui
bin/nl_sample_gui # interactive, ESC quits
bin/nl_sample_gui --screenshot out.png --frames 10 # headless witness (works under xvfb-run)
bin/nl_sample_gui -d dir --xml extra.xml # parse additional interface XML on top of the showcase
NLGUI ships the widget-level handlers (set, tab_select, ic_* container buttons, combo box, select number, …) but some conventional handlers live in the Ryzom client. The sample registers its own equivalents: proc (run an interface procedure), enter_modal / leave_modal (CWidgetManager::enableModalWindow), push_modal/pop_modal, active_menu, lua, browse/browse_undo/browse_redo (drive a group_html by its name= param — html links emit browse with name=<full id>|url=<href>), a no-op submit_quick_help (CAHManager forwards every handler run to that sink for the client's quick-help system and warns when it is missing), and sample_quit.
proc contract trap: runProcedure must receive the whole split param list, procedure name still at [0] — CProcAction::eval maps @0 to paramList[1] by design. Stripping the name shifts every substitution by one and silently breaks every procedure call.
A launcher window toggles one showcase window per family; root windows all use layer0, and the deeper container layer skins (w_l1_*/w_l2_* frame sets) appear where the engine actually selects them — by popin nesting depth inside the contacts rollout. Covered and interaction-verified under Xvfb with xdotool: text views (sizes, colors, case modes, multi-line), bitmaps (scaled/tiled), text_number/digit/text_quantity, bar/bar3 gauges, push/toggle/radio buttons, text buttons, sliders on both axes bound to UI:TEMP:* database entries, edit boxes (typed input works), scroll_text (which contains the runtime-filled group_list), group_tree with arbo_* fold arrows, menus (checkable, grayed, separator, nested submenu — a submenu is a nested <action> inside an <action>, not a <menu> element), the combo box with its dropdown, select_number, the color picker (palette + a makeRGB(...) link recoloring a swatch), tab groups, the openable container mode (the views window collapses to its titlebar via the layer0 arrow), stacked modals (push_modal/pop_modal), a <link> showing a warning icon above a threshold, and the contacts-style popin rollout with the original anatomy: window contents live in the header groups, the section below is the open rollout where child containers dock via nested <tree> nodes, and a contact entry is a closed container — just its title bar with icons in header_closed. With the child list open, the original layout draws the top block, the m_open left rail (with the list scrollbar riding it), and the floating el/em/er_open ending strip; the frame stops below the header and content, the child bevel bars carry the open section, and the right edge of the rollout is genuinely open (the shipped skin_ layers instead patch a single repeated right-edge texture over it, e.g. skin_l1_r for tx_tr/tx_r/tx_br). The layer option frame_covers_open_list (default true = shipped behavior) selects between the two layouts; the sample's w_ layer blocks set it false. Without it, the frame stretches over the whole open list and degenerates the open-section geometry (negative rail height, b_open landing on the ending-strip row). Lua-scripted UI is exercised by the demo apps below; group_html and group_table by the help viewer (tables in html render through group_table). Not exercised: edit_box_decor, group_frame/header.
Note on container layers: standalone windows can force a deeper skin with options="layer1..3", but the engine normally derives the layer from popin nesting depth. Only layer0 (root windows) renders a visible title band in production use; deeper layers carry rollout children whose collapsed rows are the "titlebar", so the sample's four layer blocks share uniform title metrics (13pt at y-4, w_win_close at y-5) and only header_h varies (16/14/12/10).
The showcase includes a playable Minesweeper window (toggled from the launcher) with no game code in the binary: the 9x9 board is 81 instances of a plain cell template stamped out by <vector> (_firstpos/_nextpos/_firstindex with $i substitution — note posref order is parent-hotspot child-hotspot, so a left-to-right chain is _nextpos="TR TL"), and all rules live in minesweeper.lua, driving the widgets through the reflection system. A cell is a flat floor bitmap + number text underneath, a w_slot_brick cover button on top (w_slot_brick_selected as hover), and a w_warning flag marker above; revealing is just cover.active = false, mines show w_death. First-click-safe mine placement, flood fill, right-click flags (onclick_r/params_r) plus a flag-mode toggle button for environments without right-click, a live timer via setOnDraw + nltime.getPreciseLocalTime, and win/boom end states.
What it demonstrates about the Lua embedding contract:
-
The embedder must call
parser->initLUA()beforeparseInterface— it registers the stock NLGUI Lua API (setOnDraw,addOnDbChange,runAH,runExpr,nltime.*, …) and the parser does not call it itself (the client calls it explicitly too). Without it,<lua file="...">scripts fail on the first stock-API call at parse time. -
getUIis not part of NLGUI — widget lookup from Lua is left to the embedder (the client registers its own richer version). The sample registers a minimalgetUI(id)(CWidgetManager::getElementFromId+CLuaIHM::pushUIOnStack) before parsing; registrations made on theCLuaManagerstate beforeparseInterfacesurvive, sinceinitLUAregisters into the same state without resetting it. - Lua sees widgets through their reflected properties (
active,hardtext,color,texture,pushed,frozen,x/y/w/h, …) and child widgets as fields (cell.cover.active = false), so a game needs no dedicated C++ surface at all. - A group's
setOnDrawscript runs through theluaaction handler — which also lives client-side, so the sample's registered equivalent covers it.
A console window (toggled from the launcher) evaluates Lua against the live widget tree: the log is a <vector> stack of plain text views repainted from a history table, the input a stock edit_box whose onenter runs the evaluator. Set enter_loose_focus="false" on a console-style edit box — the template default drops keyboard focus on Enter and every other command silently goes nowhere. Evaluation tries return <line> first so plain expressions print their values, then falls back to statement form; errors render in red; print is redirected into the log; max_historic gives up-arrow history for free.
The inspector half comes from the __next metamethod NLGUI installs on reflected userdata: redefine global next/pairs to consult getmetatable(t).__next (exactly what the Ring scripts do in r2_misc.lua) and pairs(widget) enumerates children groups/views/ctrls (_Group n keys, values are the child widgets) followed by every exported property (keys stringify to the property names). The sample ships ls() (children by bucket) and props() (property dump) on top, plus ui(path) prefixing ui:sample: for brevity. Reflected x_real/y_real/w_real/h_real are readable, which is what makes Lua-side hit-testing (inventory below) possible.
An armor paper-doll strip (six typed equip slots with labels) over a 6x2 bag, using the w_ar_* armor icons and w_slot_item floors from the atlas. All logic in inventory.lua; the binary only gained two Lua hooks and one listener:
-
getMousePos()/getMouseDown()— registered by the sample with the client's exact signatures (CToolsemantics), reading the widget manager's pointer. Coordinates are interface pixels, the same space as reflected*_realgeometry. -
Nothing in NLGUI ever calls
setPointerDown— the widget manager updates pointer position only; button state is mirrored into the pointer by the client's input handler. Standalone apps must do the same (the sample adds a smallIEventListenerfor mouse down/up/focus-lost on the driver's event server). Note the parser auto-creates a baregeneric_pointerwhen none is parsed; the parser only wires a parsed view as the pointer fortype="pointer", which is the client's registered class, so atype="generic_pointer"view in XML is decorative.
A setOnDraw tick polls the hooks and runs the whole gesture: press-edge hit-tests slots against x_real/y_real/w_real/h_real, a ghost bitmap follows the cursor (ghost.x = mx - content.x_real - 20 with posref="BL BL"), item_selection.tga glows on valid targets, release applies move/swap/reject-with-return. Typed slots just compare a kind tag in Lua — acceptance rules cost nothing when the data layer owns them.
A browser window on local HTML files: pages resolve through the search path (CGroupHTML::lookupLocalFile accepts bare filenames and file: URLs), so no network and no curl involvement. Anatomy that must be present inside the <group type="html">: a backdrop bitmap named by background_bitmap_view (the classic black2 + blank.tga), the text_list list group, and a scroll_bar ctrl (it extends group_scroll_text). Links emit the browse action handler with name=<full id>|url=<href>; Back/Forward buttons run browse_undo/browse_redo, and pointing the group's browse_undo=/browse_redo= attributes at those buttons makes it freeze/unfreeze them with history state automatically. The page <title> becomes the container window title.
browser.css is load-bearing: the legacy per-attribute styling (link_color, h1_font_size, …) is parsed but the modern CSS engine styles the actual views — without a browser.css on the search path links render as plain gray text and headings collapse to body size, with only a one-line warning in the log. The sample ships the client's stylesheet with the link color adjusted to the w_ palette. Pages exercise headings, lists, <b>/<em>, inline <img> from atlas textures, and bordered tables (group_table).
NLGUI expects several things from the embedding application that the Ryzom client normally supplies:
-
CInterfaceLink::CInterfaceLinkUpdater— instantiate one after creating the widget manager; without it,<link>elements never evaluate (database observer flushes are what pump triggered links). CallCInterfaceLink::updateAllLinks()once after parsing for the initial evaluation. -
CDBGroupComboBox::selectMenu/selectMenuOut/measureMenu— static ids the combo dropdown machinery resolves; point them at your master group's menus. -
Action handlers with client-side conventions:
proc,enter_modal/leave_modal,push_modal/pop_modal,active_menu,lua,browse/browse_undo/browse_redo,submit_quick_help. -
Lua environment hooks beyond
initLUA:getUI, and for gesture-driven UIgetMousePos/getMouseDownplus the pointer button-state mirroring described above.
-
Container windows need the global options blocks.
CGroupContainer::setupdereferencescontainer_move_optunchecked — without that options block the firstupdateAllLocalisedElementscrashes. -
Fresh database leaves default to 0.
UI:SAVE:CONTENT_ALPHA,UI:SAVE:CONTAINER_ALPHAand friends must be declared (255) in the XML or every window renders fully transparent. LikewiseUI:SAVE:*_ROLLOVER_FACTORare inverted factors: 255 = no fade-out when the mouse is not over the window, 0 = fade to invisible. -
Root-level plain
<group>s are parsed but never drawn — only windows registered on the master group (containers via<tree>, modals viaenableModalWindow) render. Put HUD-style elements inside a registered window. -
%case_upperetc. are interface defines, not built-ins — declarecase_normal…case_first_word_letter_up(0…5). - The texture lookup normalizes
.png→.tgaon both the atlas-load and lookup sides, so interface XML keeps the canonical.tganames while the atlas is packed from.pngsources. The color picker is the exception: it reads its palette as a loose file (it needs pixel access), so the palette png is staged next to the atlas andCPath::remapExtension("png", "tga", true)is set before the search paths are indexed. - Container windows compute their height from the content group unless
resizer="true"; a fixed-size window withresizer="false"and an empty-sized content collapses to its title bar. -
dbview_quantity's registered class name istext_quantity(notquantity). -
Call
setKeep800x600Ratio(false)on the text context (the client does this inresetTextContext).CTextContextdefaults the ratio ON, which silently scales every font size bywindowHeight/600— at 1600x1200 all text renders at 2×, overflowing the texture bands no matter which font sizes the XML asks for. With true pixel metrics the client's per-layer title sizes (13/11/10) match theirheader_hbands (16/14/12). The sample's--font-probeflag prints the renderer's real per-size string metrics (viaUTextContext::getStringInfo) for checking this. -
w=does not limit multi-line text wrapping. Amulti_line="true"view wraps atline_maxwclamped by the parent's inner width (getCurrentMultiLineMaxW); a plainwattribute is ignored for wrapping. Useline_maxwfor a narrower paragraph column.
The GUI stack builds for the web sample target. Recipe (dev-box; prefix paths are examples):
-
Cross-build the static deps with the emsdk into a local prefix:
- Lua 5.1.5:
make generic CC="emcc -O2" AR="emar rcu" RANLIB=emranlib,make install INSTALL_TOP=<prefix>. - libxml2 (2.12.x):
emcmake cmakewith-DBUILD_SHARED_LIBS=OFF -DLIBXML2_WITH_{PYTHON,ICONV,LZMA,ZLIB,TESTS,PROGRAMS}=OFF. -
ryzom/luabind:
emcmake cmakeagainst the wasm Lua. Boost is headers-only here, but do not pass/usr/includeas the Boost include dir — host headers poison the emscripten sysroot; symlinkboost/into a clean directory instead.
- Lua 5.1.5:
-
curl/openssl are not built —
nel/samples/gui/emscripten/nel_wasm_stubs.c(compiled with emcc, archived aslibcurl.a/libssl.a/libcrypto.ain the prefix, with real curl headers copied alongside) provides symbol-level stubs whose entry points fail the way the real libraries would. NeL's error paths handle that; a JS missing-function import would abort at the first call instead (CCurlHttpClient::connectdereferencescurl_version_info()unconditionally, so that one returns a static struct). SysV IPC stubs cover nelmisc shared memory. -
Configure the emscripten tree with
WITH_GUI=ON WITH_WEB=ON WITH_LUA51=ONplusLUA_*,LUABIND_*,Boost_INCLUDE_DIR,LibXml2_DIR=<prefix>/lib/cmake/libxml2-<ver>,CURL_INCLUDE_DIR/CURL_LIBRARY,OPENSSL_*pointing at the prefix, andNL_BUILD_INTERFACE_TOOL=<host build>/bin/build_interfacefor the atlas pack. The root CMake emscripten branch probes these instead of compiling XML support out (nelmiscgains realCIXmlon wasm this way). - The sample selects
UDriver::OpenGlEs3under emscripten and defaults to the 1280x720 showcase layout; the html page CSS-scales the canvas. Deploys next to the other WebGL samples (nl_sample_gui.html/.js/.wasm/.data).
Verified in headless chromium over SwiftShader WebGL: rendering matches the native build, including evaluated <link>s and the color picker palette.
Input in the browser: the driver's CEmscriptenEventEmitter feeds the event server from HTML5 callbacks (mouse/wheel/keyboard; the first touch maps to the left mouse button), normalizing coordinates against the canvas CSS size — so the CSS-scaled host page and mobile touch both work with no sample-side changes. Ctrl/Cmd combos and F-keys are left to the browser; Tab/Space/Backspace/arrows are consumed so the page doesn't scroll. Details on the embedding page.