Skip to content

embedding

Jan Boon edited this page Jul 21, 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 editor: markdown

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, sendClockTickEvent, checkCoords, drawViews. If the application ships no translation files, CI18N::setNoResolution(true) renders literals instead of <NotExist:...> noise.

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. (Keyboard subscription was missing in the extracted listener for years because the client wires its own input path; edit boxes were deaf in standalone apps until 2f6a3f319.)

Under Emscripten the driver feeds the same event server from HTML5 callbacks (CEmscriptenEventEmitter in the opengl3/opengles3 driver, since cc374f902): mouse, wheel and keyboard map to the usual NeL events — coordinates 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 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

Everything here was learned the hard way; details in the linked pages.

  • 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 textline_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