-
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
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 original 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. The sample keeps this otherwise-unused skin exercised and doubles 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 gained a -k/--keep-going flag for this: input files that cannot be loaded as bitmaps (e.g. unmaterialized git-lfs pointer files) are skipped with a warning instead of aborting the pack. The font comes from fonts/ingame.ttf in the same checkout — the pixel font whose metrics the interface textures assume (basic.ttf renders ~30% taller at the same sizes).
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)
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), and sample_quit.
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, 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 engine had always stretched the frame over the whole open list — which degenerates the open-section geometry (negative rail height, b_open landing on the ending-strip row) — so the sample introduced the frame_covers_open_list layer option (default true, shipped skins unchanged); the sample's w_ layer blocks set it to false for the original behavior. Lua-scripted UI is exercised by the Minesweeper demo below. Not exercised: group_html/group_table (need the web stack wired), 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, and the sample no longer forces it. Unlike the shipped client — where only layer0 draws a header band (skin_header_*; deeper layers use no_bord) and each layer therefore has its own title size and offsets — the w_ set has a single 24px w_header_* band and a single 14px w_win_close button, so all four sample layer blocks share one set of measured-centered title metrics (13pt at y-4, button at y-5). Importing the client's per-layer offsets into identical band geometry is what title/close-button misalignment looks like.
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.
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.
-
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. - Two NLGUI bugs fixed while building this:
CEventListenernever subscribed keyboard events (edit boxes were deaf outside the client), andCGroupTree::SNode::parsecrashed on a<node>withopened=but noshow=(wrong pointer guarded).
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.
- M1 (landed): skeleton —
w_layer0 container window with header/title/buttons,w_modalmodal viaenter_modal, mouse pointer, frame-capped loop, headless screenshot mode, xdotool-verified event chain under Xvfb. - M2 (landed): comprehensive widget coverage as described above, every interaction verified headless.
- M3 (landed): Emscripten/WebGL build deployed with the other NeL web samples.
- M4 (landed): Minesweeper demo app — pure XML+Lua game riding the widget set; interaction-verified headless (reveal, flood, flags, flag mode, timer, boom, win, reset).