Skip to content

embedding

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

title: Embedding NeL GUI description: What a host application must provide to run NLGUI standalone: bootstrap order, handlers, hooks and pitfalls published: true date: 2026-07-24T16:27:44.137Z tags: editor: markdown dateCreated: 2026-07-23T22:41:55.951Z

NLGUI was extracted from the Ryzom client, and several things the library expects are still supplied by the embedding application: conventional action handlers, Lua entry points, the link updater, pointer button state. This page is the complete contract, distilled from building the standalone widget sample (nel/samples/gui/main.cpp is the reference implementation; everything below exists there in working form).

Bootstrap order

// driver + text
UDriver *driver = UDriver::createDriver(0, false, 0);
driver->setDisplay(UDriver::CMode(width, height, 32, true));
UTextContext *tc = driver->createTextContext(fontPath);
tc->setKeep800x600Ratio(false);                    // fontsize means pixels; see pitfalls

// link evaluation pipeline (before any parsing)
new CInterfaceLink::CInterfaceLinkUpdater();       // leaked once, process lifetime

// renderer
CViewRenderer::setDriver(driver);
CViewRenderer::setTextContext(tc);
CViewRenderer::getInstance()->init();
CViewRenderer::getInstance()->loadTextures("atlas.png", "atlas.txt", false);

// Lua before parsing: initLUA registers the stock API; the parser does NOT call it
IParser *parser = CWidgetManager::getInstance()->getParser();
parser->initLUA();
CLuaManager::getInstance().getLuaState()->registerFunc("getUI", luaGetUI);   // yours

// parse, evaluate, activate
parser->parseInterface(xmlFiles, false);
CWidgetManager::getInstance()->updateAllLocalisedElements();
CInterfaceLink::updateAllLinks();
CWidgetManager::getInstance()->activateMasterGroup("ui:sample", true);

Main loop each frame: pump the driver's event server, feed the frame clock (below), sendClockTickEvent, checkCoords, drawViews. If the application ships no translation files, CI18N::setNoResolution(true) renders literals instead of <NotExist:...> noise.

Feed the frame clock every frame — NLGUI never writes CWidgetManager::SInterfaceTimes itself, only reads it. Skip the call and frameDiffMs stays 0 forever, silently stalling every time-driven behavior: edit-box caret blink (stuck in its initial-off phase, so the caret looks missing), rollover fades, group_html refresh/timeout, sendClockTickEvent-driven scroll, interface animations.

CWidgetManager::SInterfaceTimes times = wm->getInterfaceTimes();
sint64 nowMs = (sint64)NLMISC::CTime::getLocalTime();
times.lastFrameMs = times.thisFrameMs;
times.thisFrameMs = nowMs;
times.frameDiffMs = times.lastFrameMs ? (nowMs - times.lastFrameMs) : 0;
wm->updateInterfaceTimes(times);

The Ryzom client does this in time_client.cpp:392-396. The sample and zone_painter now do the same.

Input events

Instantiate an NLGUI::CEventListener and add it to the driver's event server; it adapts mouse and keyboard events into the widget manager.

Under Emscripten the driver feeds the same event server from HTML5 callbacks (CEmscriptenEventEmitter in the opengl3/opengles3 driver): mouse, wheel and keyboard map to the usual NeL events, coordinates are normalized against the canvas CSS size so a CSS-scaled canvas just works, and the first touch is mapped to the left mouse button. The listener and pointer-mirroring path described here works unmodified in the browser, including touch drag on mobile.

Mouse pointer and buttons

The widget manager updates the pointer view's position only; nothing in the library ever calls setPointerDown. The client mirrors button state into the pointer in its own input handler, and a standalone app must do the same. Without it, anything reading the pointer's button state (drag logic, getMouseDown-style hooks) sees buttons that never press. A small IEventListener on mouse down/up/focus-lost suffices:

// on EventMouseDownId:  pointer->setPointerDown(button == leftButton);
//                       pointer->setPointerMiddleDown / setPointerRightDown likewise
// on EventMouseUpId:    clear the pressed states
// on EventSetFocusId:   reset everything (focus loss mid-drag)

The parser auto-creates a bare generic_pointer view when none is declared. Only a type="pointer" view (a class name the client registers) is wired as the pointer by the parser, so a type="generic_pointer" element in XML is decorative; declare cursor textures on it only if your application registers and wires it.

Action handlers

The library and standard interface data invoke these by name; implementations live in the client, so register your own (a few lines each; see the sample's main.cpp):

name minimal implementation
proc split params on `
lua execute params via CLuaManager's state. Without it, setOnDraw, addOnDbChange, lua_class and every onclick="lua:..." are dead: the library calls this handler internally
enter_modal / push_modal / leave_modal enableModalWindow / pushModalWindow / popModalWindow with the group= param
active_menu getParam(params, "menu")enableModalWindow(caller, menu)
browse / browse_undo / browse_redo resolve the name= param to a CGroupHTML*, call browse(url) / browseUndo() / browseRedo(); required for clickable HTML links
submit_quick_help no-op: every single handler invocation is forwarded to this name and warns when it is missing
set_keyboard_focus / reset_keyboard_focus, launch_help optional; focus convenience and the container help button

Lua entry points

initLUA provides the stock API (catalog) but not getUI: widget lookup from Lua is the embedder's. Minimal version: CWidgetManager::getElementFromId + CLuaIHM::pushUIOnStack. Register extra functions before parseInterface; registrations survive since initLUA reuses the same state.

For gesture-driven UI (drag and drop), the sample registers the client-compatible getMousePos() / getMouseDown() reading the widget manager's pointer. Coordinates are in interface pixels, the same space as reflected *_real geometry. They depend on the button mirroring above.

Combo boxes

The drop-down machinery resolves three static ids; point them at a menu group in your XML before combos are used:

CDBGroupComboBox::selectMenu    = "ui:sample:combo_menu";
CDBGroupComboBox::selectMenuOut = "ui:sample:combo_menu";
CDBGroupComboBox::measureMenu   = "ui:sample:combo_menu";

Links

<link> evaluation is driven by database observer flushes, closed by the CInterfaceLink::CInterfaceLinkUpdater instance, which nothing in the library creates. Without it, links evaluate once at updateAllLinks() and never again. Instantiate it before parsing and call CInterfaceLink::updateAllLinks() once after (mechanics).

Pitfalls checklist

Details in the linked pages.

  • updateInterfaceTimes skipped → caret and every timed behavior stalled (frameDiffMs stays 0); call it every frame before sendClockTickEvent.
  • container_move_opt options block missing → crash in the first layout pass of any container (options blocks).
  • Fresh database = all zeros → invisible UI: declare the UI:SAVE:*ALPHA entries, mind the inverted *_ROLLOVER_FACTOR (database).
  • Unregistered groups never draw: windows need <tree> registration; modals are automatic (xml structure).
  • %case_upper etc. are defines, not built-ins; declare them (0–5).
  • setKeep800x600Ratio(false) or every font size scales with window height and overflows the skin's header bands (skinning).
  • .png.tga remap before indexing search paths when sources are png but XML uses canonical tga names; the color picker additionally needs its palette as a loose file (skinning).
  • w= does not wrap text; line_maxw does (views).
  • enter_loose_focus defaults true on edit boxes (controls).
  • Container height comes from content unless resizer-clamped; an empty fixed window collapses to its title bar.
  • dbview_quantity's registered type name is text_quantity, and the digit view's is digit.

Dependencies and platforms

WITH_GUI requires Lua (5.1 classic; 5.2+ supported) and ryzom/luabind, plus libxml2 and libpng/jpeg via NLMISC. CGroupHTML uses CURL when WITH_WEB/curl are present; local pages need none of it. The stack builds for Emscripten/WebGL: cross-compiled deps, curl symbol stubs and the recipe are documented in the widget sample, which also serves as the wasm reference embedding.

Clone this wiki locally