RmlUi reference
Use Cheryl::UI::RmlUi to author native RML documents and RCSS styles, receive
Engine input and publish retained scenes. The application owns its session and
chooses the pass order. RmlUi owns document layout and editing;
Engine does not acquire a common widget API or a toolkit dependency.
Start with selection and the editable-document example. Use fonts, recording/publication and materials for application contracts; use dependency configuration, the placeholder contract and focused checks when integrating a supplied toolkit.
Selection and dependency
Set CHERYL_BUILD_UI_RMLUI=ON and reload CMake to expose module_ui_rmlui,
tests-ui-rmlui and all-ui-rmlui when tests are selected. The root option defaults
to ON; TGUI and RmlUi can be selected separately or together. The module library’s
output name is cheryl-module-ui-rmlui.
target_link_libraries(game PRIVATE Cheryl::UI::RmlUi)
Create an editable document
Create an interactive GUI with RmlUi provides the full First Game extension: module selection, inherited Engine fonts, RML and RCSS files, native event listeners, focus routing, retained scene publication and partial-safe cleanup.
The maintained RmlUi demo also demonstrates document loading, hit testing and recoverable upload errors. For a C++ widget HUD using the other integration, follow Create a HUD with TGUI. The contracts below describe RmlUi session ownership and supported behavior.
Native documents and input
Create Session(engine_context, nonzero_focus_id, options) on the simulation/UI owner and call context() for native RML
document loading, RCSS styling, elements and listeners. The adapter contains no
layout string parser or common widget API. The session imports the Engine font
catalogue and establishes its configured default for document inheritance. The
session owns one global Core initialization; a second session or
host-installed interface rejects instead of replacing the host’s SDK state.
Its focus ID identifies the entire session as an Engine keyboard/text recipient. Choose any nonzero value and keep independent recipients distinct; its magnitude gives no priority. Documents/elements share that ID and use native focus within the session. See session routing and widget focus.
For a manual session, initialize copied logical/framebuffer dimensions with set_view, then refresh
them whenever either pair changes. Repeating the call each update is also valid. Context dimensions
are logical units and its density-independent ratio is one; framebuffer scaling
belongs to Cheryl’s clip/projection playback. update_time advances the supplied
simulation duration and updates native layout/animation. record updates pending
authoring/input changes before rendering without advancing the clock again.
The session holds Events capture. request_keyboard_focus acquires Text capture
and a keyboard routing lease for its target.
handle_input observes modifier snapshots but delivers keyboard/text only for its
target and requested epoch saved at call entry. Pass the complete immutable tick
record batch once: poll-latched records drain before external focus preemption
clears native editing, including when the batch is empty.
The three-argument overload calls before_record(record) before each delivery
and uses its return value as that record’s pointer selection. Application controls
can request focus or show/hide a view in that callback without changing the entry
epoch used to select keyboard/text for the rest of the batch. Future polls observe
the new routing lease; existing records retain their original targets/epochs.
The two-argument overload keeps a constant pointer selection. Unselected pointer
delivery sends mouse-leave when necessary. Mouse buttons and fractional wheel
input apply their observation-time coordinates before delivery. Physical keys,
repeats and releases stay independent of committed Unicode text.
Capabilities report keyboard/text support and rectangular clipping. Clipboard, cursor changes, IME and advanced effects are unavailable. Clipboard shortcuts are suppressed before they change a selection; explicit clipboard interface calls reject. The adapter never runs a native toolkit backend or window event loop.
Release external element, event-listener and toolkit-resource references before
session destruction. After the runtime joins simulation, close_after_quiescence
permits final platform-owner teardown. Native contexts and font globals shut down
while interface/font storage is still live. CPU recordings and uploaded Cheryl
frames retain their own geometry/pixels/resources independently.
Automatic view and time
All constructors remain manual by default. A Context-aware session can opt in
when constructed on its initialized simulation owner, normally during
init_simulation(). Include <cheryl/ui/rmlui/session.h> and <memory>; with engine as
the live Context:
auto session = std::make_unique<CE::UI::RmlUi::Session>(
engine, 3,
CE::UI::RmlUi::SessionOptions{.automatic_view = true, .automatic_time = true}
);
Retain the session between updates. automatic_view applies the current
SimulationView seed before construction returns and follows later logical/pixel
size changes, including zero drawable extents. It must not be enabled during
platform init_platform() before the simulation source exists. The tutorial and demo
choose automatic view following while keeping their existing manual time/input
order.
automatic_time advances toolkit time on future SimulationStepStarting
phases, before the game update. A session first created inside an update does not
replay that step; a session created during init_simulation() participates from
the first step. While attached, every manual update_time() call rejects,
including zero; call detach_time() on the UI owner before returning to manual
advancement. Choose manual time when the application needs it after input or
other game work. Neither option delivers input, adopts scenes or chooses pass
order.
Existing sessions can call follow_view(engine.simulation_view()) and
follow_time(engine.services()) explicitly. Standalone input/fonts constructors
reject automatic flags because they have no Context, but can use those explicit
methods with initialized sources on the same owner. This keeps manual standalone
use independent of Runtime.
A valid public set_view() first detaches automatic sizing, then applies the
manual view. Invalid dimensions leave the existing attachment intact; a native
application failure after detach leaves manual mode selected. follow_view()
can reattach with a fresh coherent seed. Session closure acknowledges both
attachments before releasing toolkit state. Required sizing/time failures
propagate to the runtime before dependent work; optional bus observers are
separate subscriptions.
Fonts
Configure engine.fonts() before creating a context-aware session; see the
Engine font service. Documents inherit that
default unless native RCSS overrides it. Registered references are native family
names, so font-family: handwriting selects that registered family, while
font-weight and font-style select its actual faces. When a family supplies only
normal or only italic faces, the adapter registers those available faces for the
missing style too; weight selection still uses the closest available face.
RmlUi compares family names
without case: references differing only by case reject session import. Use simple
names such as book-print in RCSS, and quote names that require native RCSS quoting.
An explicit random/restored Engine font uses the same handle as direct text.
This uses the books group from the catalogue configuration example:
auto selected = engine.fonts().choose_random("books");
session.set_font(*book_element, selected);
// session.font_family(selected) returns its registered native family name.
The property inherits through the element’s descendants; a child can override it.
Save selected.reference() in application data to retain the choice. The adapter
loads retained bytes with their collection face indices and styles, and keeps them
alive through Core shutdown. It does not reopen source paths. Only the configured
common glyph fallback families and embedded safety font become global native
fallbacks; registering a specialty font does not make it a fallback for other text.
RmlUi searches glyph fallback faces in registration order. That native fallback
search does not match the originating text’s weight or italic style; an available
fallback glyph may therefore use a different style.
Session(input, fonts, target, options) injects an independent Engine font service.
The standalone Session(input, target, options) and explicit load_font(file, family)/load_font(bytes, family) paths remain available. Explicit memory loading
copies its bytes; file loading uses a Core-owned buffer. Both survive through Core
shutdown. Explicit registration cannot replace a family imported from Engine, and
an Engine handle cannot replace an explicitly registered family. Service
reconfiguration does not rebind an existing session or retained font.
Recording and publication
RenderTarget implements the Core render interface without graphics calls. It
copies indexed triangles during compilation and translated draws during recording.
Texture handles retain immutable decoded generations; releasing a toolkit handle
does not invalidate a completed recording. Generated premultiplied pixels remain
unchanged; decoded file RGB is multiplied by alpha once. Texture V coordinates
map RmlUi’s downward-positive convention to Cheryl’s upward-positive image UVs.
Rectangular scissor edges and logical dimensions travel with every draw. Zero dimensions or empty clips produce no draws. Masks, nonidentity transforms, layers, filters, custom shaders and repeating texture coordinates reject publication. The failure stays latched for that renderer because the toolkit may cache an unsupported compiled property without offering it again. Recreate the session after removing such a property. SDK callbacks allow render-stack cleanup to finish before the recording reports the error.
Session texture bounds are at least 1024, matching the retained font atlas’s
maximum dimension; the default bound is 4096. File/generated images must fit the
configured bound. Standalone CPU RenderTarget checks can use smaller bounds.
Construct SceneUploader(provider) on the platform owner during initialization.
submit(platform_submission, recording) transfers owned recording data through
Cheryl’s dispatcher and selects automatic materials. upload(provider, recording)
uses the same defaults when already on that owner. The uploader belongs to one
provider/domain and platform owner. adopt_scene never waits and replaces the
retained scene only after a complete successful upload. Failure or cancellation
preserves its predecessor.
Input identities
keyboard_key maps portable physical keyboard identities to RmlUi keys; unknown
keys remain KI_UNKNOWN. Committed characters come from TextEvent, not from
this mapping. modifiers preserves Control, Shift, Alt, Super, Caps Lock and Num
Lock flags in RmlUi’s modifier representation.
Default and custom materials
The first nonempty upload creates defaults through the provider’s generic shader builder on the platform owner. OpenGL embeds the required shaders and reuses live matching materials; applications need no UI shader files or native builder. Unsupported providers reject the default request.
The optional third argument to submit or upload supplies explicit Materials
handles, including materials loaded from JSON. They require colored triangles,
PremultipliedAlpha, disabled depth/write/culling, a projection semantic and a
custom image sampler for textured draws. Shaders multiply image RGBA by vertex
RGBA without an additional premultiplication. Other custom values can come from
material defaults or Materials::pass_parameters. Passing Materials{} explicitly
selects an empty override; it does not request defaults for nonempty draws.
See custom UI materials
for handle selection and the separate manual construction API.
Dependency configuration
It links shared RmlUi Core and Cheryl::Engine. Session installs a font engine
whose reviewed upstream provider, atlas, effects and fallback logic live in Core;
the Cheryl adapter supplies Engine rasterization through private callbacks.
Core itself does not link Engine, preserving that boundary with static Cheryl too.
The module provides native document sessions, portable input translation, CPU
recording and retained scene upload. The
adapter-author guide describes the
engine boundaries; RmlUi keeps its native context, RML and RCSS authoring API
exported from the shared toolkit library.
This core integration is sufficient for the selected Linux root-composition scope. Further rendering effects are planned extensions, including any required graphics-backend support. OS services remain deferred. Dedicated regression and supplied-Core acceptance gaps remain in the startup/UI follow-up.
The module reuses an existing RmlUi::Core. Otherwise it uses an explicit
CHERYL_RMLUI_SOURCE, an explicit package location in RmlUi_DIR, the pinned
extern/rmlui submodule, or the exact package, in that order. Initialize the bundled
dependency with git submodule update --init extern/rmlui; CMake does not download
it. Core is configured with RMLUI_FONT_ENGINE=none; the module installs its
Engine-backed implementation explicitly. Supplied shared targets declare
CHERYL_RMLUI_ENGINE_SERVICES=TRUE and CHERYL_RMLUI_RASTERIZER_ABI=1 after
implementing the reviewed private rasterizer bridge and preserving native API exports. Metadata does
not make a stock incompatible binary satisfy those contracts.
The adapter requires the matching reviewed font integration sources even when
Core comes from a package; supply CHERYL_RMLUI_SOURCE or retain the pinned
extern/rmlui checkout. The private callback signatures use those upstream font
headers. Session initialization binds the callback table only after acquiring
toolkit ownership, and shutdown releases font faces before clearing it. Keep Core,
the adapter and Engine loaded while their objects or callbacks remain in use.
An owned source configuration selects Core without a separate FreeType engine and excludes samples, toolkit tests, native backends, Lua and optional plugins. Conflicting settings reject instead of rewriting a host’s choices. The module links Core directly, leaving the upstream debugger outside the adapter dependency graph.
The standalone module and its tests/consumer/ entry point locate their enclosing
checkout; set CHERYL_REPOSITORY_ROOT to select another absolute checkout path.
The module reuses a supplied
Cheryl::Engine or bootstraps Engine from that root, suppressing other
module/demo/test selection in its local scope. Consumer/header checks are selected
through CHERYL_BUILD_CONSUMER_TESTS at the root or by configuring the consumer
entry point independently.
Placeholder dependency contract
The selected RmlUi 6.3 Core must include the empty-value correction in
WidgetTextInput::CalculateCharacterIndex. In the pinned source,
GetValue()
returns an empty string while the placeholder is shown, but
FormatText()
retains offsets and lengths from the displayed hint. The
hit-test
uses those offsets and lengths to form an iterator range into GetValue().
That requires:
0 <= line.value_offset
0 <= line.editable_length
line.value_offset + line.editable_length <= GetValue().size()
A nonempty hint can violate this invariant against the empty editable value. Later cursor clamping cannot undo an invalid read. Returning zero before forming the range preserves the only valid insertion position without treating the hint as editable content.
When Cheryl creates Core from CHERYL_RMLUI_SOURCE or the bundled source, its
helper compiles a generated copy of WidgetTextInput.cpp with
an early GetValue().empty() return. It excludes the original translation unit,
preserves its include directory and leaves the dependency checkout unchanged.
An already guarded source is reused; an unrecognized entry/source layout rejects
configuration. The helper then marks the concrete Core target with
CHERYL_RMLUI_PLACEHOLDER_FIX=TRUE.
Existing targets and installed packages bypass that helper. Their concrete Core target must declare the same property after the provider or consumer verifies that the actual library contains the correction. The declaration does not patch a supplied binary or prove its runtime behavior. Missing declarations reject configuration; use Cheryl-owned source or a corrected dependency with the property.
For a verified, corrected external package, place this after finding the package and before adding Cheryl. Resolve an alias before setting the property:
find_package(RmlUi 6.3 EXACT CONFIG REQUIRED)
get_target_property(rmlui_core_target RmlUi::Core ALIASED_TARGET)
if(NOT rmlui_core_target)
set(rmlui_core_target RmlUi::Core)
endif()
# Declare an existing correction; this does not modify the supplied library.
set_property(TARGET "${rmlui_core_target}" PROPERTY CHERYL_RMLUI_PLACEHOLDER_FIX TRUE)
Shared Cheryl SDK consumers use the integrated owner binary and require no separate exported Core dependency or manual declaration. Supplied source compositions still verify the Core correction and Engine-service configuration.
The method-entry guard also returns before resetting
ideal_cursor_position_to_the_right_of_cursor. Its effect on type/clear/reclick,
double-click and textarea vertical movement remains a dedicated regression and
upstream-review requirement. The
upstream follow-up
owns the unmodified reproduction, regression cases and submission prerequisites.
The Cheryl-owned correction has accepted placeholder hit-testing observations in the Linux/X11/OpenGL demo; existing session tests do not directly exercise that correction. Supplied targets/packages still require independent host acceptance and correction declarations. Downstream acceptance does not establish an unmodified-upstream reproduction or upstream acceptance.
Focused checks
Target |
Output / kind |
Selection / coverage |
|---|---|---|
|
|
|
|
|
All owner GoogleTests; |
|
|
|
|
Object library |
Consumer’s first-include header probes; built with the consumer. |
ui_rmlui_input.* covers representative keys and combined modifiers.
ui_rmlui_recording.*, ui_rmlui_texture.* and ui_rmlui_scene.* cover owned
geometry/pixels, alpha/orientation, clipping, retained frames, provider ownership,
material validation and failure-preserving adoption. A checked-in two-row image
distinguishes file premultiplication and row order from generated texture handling.
ui_rmlui_session.* uses real documents, input elements and Engine-rasterized fonts.
Font catalogue cases cover inherited defaults, per-element families, real style
faces and collection indices, retained source bytes, and alias collision rejection.
ui_rmlui_runtime.sequential and .concurrent exercise queued old/new image
publication, real runtime playback, owner threads and retained frames through
toolkit/provider teardown. They use controlled engine adapters without a display.
Their in-memory document has a source URL in the fixtures directory; relative image
names resolve against it, avoiding Core’s removal of a leading slash from image URLs.
The independent consumer loads a document, edits its field and retains CPU draws
after session destruction; header probes use public headers as the first include.
Checks use the selected SDK’s Samples/assets/LatoLatin-Regular.ttf fixture or
an explicit CHERYL_RMLUI_TEST_FONT file. Missing fonts reject configuration;
these cases do not silently skip font coverage or search the host’s system fonts.
Acceptance procedure
For changes affecting RmlUi, reuse the existing native/OpenGL profile with tests
and both UI adapters selected. demo also requires native input. From the repository
root:
(
set -e
cd "$(git rev-parse --show-toplevel)"
git submodule update --init extern/rmlui
)
(
set -e
cd "$(git rev-parse --show-toplevel)"
cmake -S . -B build/debug -DCMAKE_BUILD_TYPE=Debug -DCHERYL_BUILD_UI_RMLUI=ON -G Ninja \
-DCHERYL_BUILD_UI_TGUI=ON -DCHERYL_BUILD_TESTS=ON -DCHERYL_BUILD_DEMO=ON
)
(
set -e
cd "$(git rev-parse --show-toplevel)"
cmake --build build/debug --parallel \
--target demo all-ui-rmlui tests-ui-coexist tests-engine tests-native-glfw
)
Run the focused checks:
(
set -e
cd "$(git rev-parse --show-toplevel)"
./build/debug/tests-all-ui-rmlui
./build/debug/tests-ui-coexist
./build/debug/tests-engine \
--gtest_filter='runtime_contract.*:input_capture.portable_buttons:tick_context.unbound_stop'
./build/debug/tests-native-glfw --gtest_filter='glfw_bindings.portable_*'
)
tests-ui-coexist belongs to projects/tests/, and also joins the combined
all-tests runner. It checks poll-latched text delivery, focus preemption and
stale-lease release across both native toolkits, distinct alpha passes, and frames
retained through independent toolkit/provider teardown. Do not run both runners
for the same cases. Run the affected TGUI session checks as well when shared
routing/session paths change. The accepted root selections and remaining dedicated
regression limits are in the
UI validation guide.
Native interaction is accepted in the selected Linux root composition; the
demo procedure preserves
its scope and reusable observations.
An independently configured consumer establishes standalone source composition and first-include headers without selecting TGUI, Native GLFW or OpenGL. Run it after the root checks settle, so any fixes precede a separate compilation:
(
set -e
cd "$(git rev-parse --show-toplevel)"
cmake -S projects/modules/ui/rmlui/tests/consumer -B build/rmlui-consumer -G Ninja \
-DCMAKE_BUILD_TYPE=Release -DCHERYL_REPOSITORY_ROOT="$PWD" \
-DCHERYL_RMLUI_SOURCE="$PWD/extern/rmlui"
)
(
set -e
cd "$(git rev-parse --show-toplevel)"
cmake --build build/rmlui-consumer --parallel \
--target consumer-module-ui-rmlui
)
(
set -e
cd "$(git rev-parse --show-toplevel)"
./build/rmlui-consumer/cheryl-ui-rmlui-consumer
)
Verify that consumer-module-ui-rmlui depends on
consumer-module-headers--ui-rmlui and compiles its input, rendering, scene and
session probes. The independent graph must exclude TGUI, Native GLFW and OpenGL.
Root consumers alone do not prove isolation; supplied-target/package composition
needs its own check when used. TGUI’s independent procedure stays in its
module guide.
Follow the demo procedure in
normal and --concurrent modes for native alpha/orientation, fonts, focus,
scrolling, bounded resize/DPI, image replacement and shutdown. Controlled cases
do not establish compositor behavior, physical input or GPU resource retirement.
The demo guide retains selected
platform limits.
For a toolkit upgrade, recheck Core/Engine font-service metadata, premultiplied file and generated images, font-byte lifetime, placeholder correction/declarations and retained frames through teardown. Accept changed behavior independently before extending native consumers or the cross-toolkit coexistence suite.