Packages

@kama/rmlui 0.1.0

RmlUi 6.3 vendored and bound: HTML/CSS-like documents laid out natively, drawn by your own renderer

Install

kama pkg add kama.json @kama/rmlui --version ^0.1.0

or in kama.json: "@kama/rmlui": { "version": "^0.1.0" }

License
MIT OR Apache-2.0
Repository
github.com/cosmic-canopy/kama-rmlui
Keywords
ui rmlui html css gui
Dependencies
@kama/freetype ^0.1.0

Versions

VersionPublishedRevisionIntegrity
0.1.0278f717c4d7fsha256-34be5ba37f2b…

@kama/rmlui

RmlUi 6.3 for kama: HTML/CSS-like documents (RML and RCSS), laid out and styled by RmlUi, driven from kama. A general binding with no opinion about the program using it. It provides contexts, documents from a file or from text, fonts, provided images, injected input, elements found by id or selector and then read or written, and an event queue that kama polls.

RmlUi draws nothing. Its render interface here records. Context.render() fills a list of draw commands (vertices, indices, a texture, a translation, a scissor rectangle, a transform, clip-mask operations) that the program's own renderer draws. There is no OpenGL, Vulkan or WebGPU backend inside, so it works with any renderer and also headless.

Status: new, not yet published. Native only (@compileFor(!ARCH_WASM32)). In a browser, @kama/ui puts the same documents into the DOM instead. Tested on Linux x86_64 (debug, release, AddressSanitizer). macOS and Windows are not yet run here. Needs kama ≥ 0.9.577.

Setting it up

RmlUi is vendored (third_party/rmlui) and compiled by kama into your program. FreeType, its font engine, comes from @kama/freetype on the registry, which is also vendored. There is nothing to fetch, nothing to build with CMake, and no -L path to set.

"dependencies": { "@kama/rmlui": { "version": "^0.1.0" } }

To build against a checkout instead (working on the package itself), override it in your gitignored kama.local.json rather than editing kama.json:

{ "overrides": { "@kama/rmlui": { "path": "../kama-rmlui" } } }

What that costs and buys:

Using it

import { rmlui::RmlUi, rmlui::RmlUiError, rmlui::Context, rmlui::Document, rmlui::Element, rmlui::Event,
         rmlui::Modal, rmlui::Focus, rmlui::BoxArea, rmlui::Command, rmlui::LISTEN_BUBBLE, rmlui::MOD_NONE };

fn void demo() {
    Result<RmlUi, RmlUiError> started = RmlUi.start();
    RmlUi ui = match (started) { case Ok(value: u): u; case Err(error: e): { return; } };
    ui.loadFontFace(path: "assets/fonts/Inter-Regular.ttf", fallback: true);
    Result<Context, RmlUiError> made = Context.create(ui: ui, name: "menu", width: 1920, height: 1080);
    Context ctx = match (made) { case Ok(value: c): c; case Err(error: e): { return; } };

    Optional<Document> loaded = ctx.loadDocument(path: "assets/ui/menu.rml");
    Document doc = match (loaded) { case Some(value: d): d; case None: { return; } };
    doc.show(modal: Modal::None, focus: Focus::Auto);          // focuses the `autofocus` element
    Optional<Element> play = doc.byId(id: "play");
    match (play) { case Some(value: p): { p.listen(event: "click", flags: LISTEN_BUBBLE); } case None: { } };

    // each frame: input in, update, render, draw, then drain the events
    ctx.mouseMove(x: 960.0, y: 540.0, mods: MOD_NONE);
    ctx.update();
    ctx.render();
    int32 i = 0;
    while (i < ctx.commandCount()) {
        Command c = ctx.command(index: i);    // c.kind, c.vertices (8 floats a vertex), c.indices, c.texture, …
        i = i + 1;
    }
    bool more = true;
    while (more) {
        Optional<Event> e = ctx.pollEvent();
        match (e) { case None: { more = false; } case Some(value: ev): { /* ev.type, ev.element, ev.target */ } };
    }
    ctx.destroy();
    ui.shutdown();
}
type what
RmlUi The library: start / shutdown (reference counted). Also loadFontFace, loadFontFaceBytes, provideImage, setTime, pollLog / setLogEcho, textureCount / texture, and the last event's eventName / eventInt / eventFloat / eventString.
Context One surface in framebuffer pixels: create / destroy, setDimensions, setDpRatio, update, render, commandCount / command. Input: mouseMove, mouseButton, mouseWheel, mouseLeave, key, text, textUtf8; each returns true when the UI consumed it. Also loadDocument / loadDocumentRml, focusElement / hoverElement / elementAtPoint, pollEvent, clearEvents.
Document show(modal:, focus:), hide, close, title, byId, query, focused (None when nothing in it is), nextTab, bind, bindingOf, root.
Element Reading: tag, id, attribute, hasClass, property (computed), value (form controls), innerRml, text, rect(area:), isVisible, parent / childCount / child, byId / query. Writing: setInnerRml, setText (escaped), setAttribute / removeAttribute, setClass, setProperty / removeProperty (inline style), setValue(value:, quiet:). Actions: focus, blur, click, scrollIntoView, listen(event:, flags:).
Binding An id held across rewrites: element, listen, id (see below).
Event listener, binding, type (EventType), element (where it was heard), target (where it happened).
Command, Texture, Vertex, Rect The recording: kind (Draw, ClipEnable, ClipDisable, ClipSet, ClipSetInverse, ClipIntersect), raw pointers for an upload plus bounds-checked vertex(i) / index(i) / byteAt(i).
KEY_*, MOD_* RmlUi's own key identifiers and modifier bits. The shim static_asserts every one.

The rules it keeps

What is vendored, and why

version pin licence
RmlUi: Source/Core and Include (Core and Config) only 6.3 ba95ffe8bfb6370efb2cdcca927eaad4710c5413 MIT
robin_hood and itlib (inside RmlUi's Include/RmlUi/Core/Containers/) as in RmlUi 6.3 same tree MIT, separate notices
FreeType, through the @kama/freetype dependency 2.14.3 that package's VENDORED FTL (taken over GPLv2)

tools/vendor-rmlui.sh is the only thing that writes third_party/rmlui/. It works like this:

Samples, Backends, Tests, Utilities, Dependencies and the build files are not vendored, and neither are the Debugger, Lua, SVG and Lottie plugins.

The licence gate: MIT only. RmlUi 6 keeps its licence in LICENSE.txt, not in each file. So:

At 6.3 the gate checks 442 files and keeps 442. Three of them name MIT themselves: robin_hood and the two itlib headers. It refuses none.

How it compiles without leaking into your build. RmlUi's CMake gives rmlui_core three definitions: RMLUI_VERSION, RMLUI_FONT_ENGINE_FREETYPE, and, for a static library, RMLUI_STATIC_LIB. A cflags entry would reach every translation unit of every consumer. Instead:

The switches carried over from both games' builds, with their reasons:

What a program that ships this must carry

licenses/manifest.tsv lists the texts. All of them ship in every native build that links this package:

  1. RmlUi's MIT notice: third_party/rmlui/LICENSE.txt.
  2. robin_hood's and itlib's MIT notices: third_party/rmlui/Include/RmlUi/Core/Containers/LICENSE.txt. This is a separate notice from separate copyright holders. friendly-fire-department's FR-28 audit found 472 of their symbols in a shipped binary whose notice named neither.
  3. FreeType's, from @kama/freetype (its README and licenses/manifest.tsv):
    • the FTL, with its credit line in the documentation (a credits screen counts):

      Portions of this software are copyright © 2026 The FreeType Project (https://freetype.org). All rights reserved.

    • the MIT notice of FreeType's hashing code.

The test font (Lato, SIL OFL 1.1, tests/fonts/) is never part of a program, and it is not published.

Tests

KAMA=/path/to/kama tools/test.sh      # tests/: debug, release, and debug under AddressSanitizer (Linux)

tests/ is one headless program, 91 checks. It passes in debug, in release, and under AddressSanitizer with leak checking. The ASan leg wraps cc/c++ with -fsanitize=address, so RmlUi, FreeType and the shim are all instrumented. Its font is Lato, committed under tests/fonts/ with its OFL text. It covers:

There is no wasm leg: this package is native by construction. @kama/ui's web build depends on it, and that build is what shows the gates hold. The shim's #error tripwire never fires there.

Where it came from

The shim is the-mallow-man's ui/src/tmm_ui_native.cpp and tmm_ui.h (E7b/E7c, 2026-10-07). Those were friendly-fire-department's ffd_ui_native.cpp and ffd_ui.h (a676ca3, last changed 2026-09-29), renamed and extended by the-mallow-man for text entry, focus, a real clock and inline properties. The Mallow Man's copy was the base. After the rename, friendly-fire-department's copy held nothing the newer one lacked; the only lines unique to it were a comment giving its stage size.

What changed on the way from a game's shim to a general binding:

the games' shim here why
one file-static context, one document contexts are handles, any number of documents general
bind(id): the only way to reach an element, repaired after rewrites by a write counter element handles for any element, tracked by ObserverPtr; bindings re-find their element by id no stale pointer can be read; works for elements found by selector or tree walk
listeners attached by bind only, detached by hand before a close listeners on any element and any event, retired by RmlUi's OnDetach removes the close-ordering use-after-free rule
one global event queue: click and change only a queue per context, any event, its target, its parameters two UIs in one program do not see each other's events
keys: GLFW codes, a fixed subset RmlUi's KeyIdentifiers and modifiers the subset and the translation are a seam's policy (@kama/ui keeps them)
draws and scissors recorded also transforms and clip masks RCSS transform and rounded overflow: hidden now reach the renderer
uint8_t* / float* out const pointers, UnsafeConstPtr in kama the games' headers said this was owed once kama could spell it
printf of RmlUi's log a queue plus stdout tests can assert on it
built into libtmm_ui.a by the game's Makefile a csources entry the package compiles its own glue

Not here yet

Publishing

kama publish ships exactly what git tracks, minus publish.exclude (tools/, tests/, the agent notes). The three packages publish in order: @kama/freetype, then @kama/rmlui, then @kama/ui. A published package cannot carry a path dependency, so just before this one publishes, change its @kama/freetype dependency from the path to { "version": "^0.1.0" }, once @kama/freetype 0.1.0 is on the registry.

License

@kama/rmlui is licensed under either of

at your option. RmlUi (and the containers it vendors), in third_party/rmlui, is MIT. FreeType comes from @kama/freetype under the FTL. "What a program that ships this must carry" lists the notices.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in @kama/rmlui by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.