Minimal RmlUi + Lua 5.4 desktop sample using SDL3 and OpenGL.
The sample expects RmlUi with Lua 5.4 bindings, SDL3, and OpenGL development files. On macOS with Homebrew:
cmake -S . -B build -G Ninja \
-DCMAKE_PREFIX_PATH=/opt/homebrew/opt/rmlui-lua54-brew
cmake --build build
./build/solid_rmlThe window displays assets/hello.rml. The UI is rendered by the small Solid-style
reactive runtime in assets/solid.lua and the example component in assets/hello.lua.
Press Escape or close the window to exit.
The portable Solid-style reactivity and renderer tests run with the system Lua 5.4 interpreter and do not require SDL or an RmlUi window:
lua tests/solid_spec.luaThe suite currently covers signals, effects, dependency switching, memos, cleanup,
components, fragments, fine-grained mocked RmlUi rendering, and the official SolidJS
For, Show, and Switch control-flow behaviors adapted to Lua.
The reactive runtime is also packaged as the solid-rml LuaRocks package. From
the repository root:
luarocks make solid-rml-0.1.0-1.rockspecInstalled Lua code can then load the same runtime with:
local Solid = require("solid_rml")The RmlUi type definitions are available as solid_rml.rmlui for Lua language
servers. The desktop sample keeps loading assets/solid.lua through RmlUi's file
interface, so it remains runnable from a fresh checkout without installing the rock.
For follows the SolidJS list control-flow tutorial
and renders a list by item identity. Pass a list accessor as each when the list should
update reactively. The child callback receives the item and a zero-based index accessor.
Existing item nodes and their reactive owners are retained when items move:
local items, set_items = Solid.createSignal({ "Alpha", "Beta", "Gamma" })
local view = Solid.h(Solid.For, {
each = items,
fallback = Solid.h("span", nil, "No items"),
}, function(item, index)
return Solid.h("div", nil, function()
return tostring(index()) .. ": " .. item
end)
end)each can also be a static Lua array. Use nil, false, or an empty array for the
empty state; fallback is mounted only while the list is empty. A child callback can
return a single view, a Solid.Fragment, an array of views, or nil:
local view = Solid.h(Solid.For, {
each = { "One", "Two" },
}, function(item)
return Solid.h(Solid.Fragment, nil,
Solid.h("strong", nil, item),
Solid.h("br")
)
end)Show renders its children while when is truthy and otherwise renders fallback:
local visible, set_visible = Solid.createSignal(false)
local view = Solid.h(Solid.Show, {
when = visible,
fallback = Solid.h("span", nil, "Hidden"),
}, Solid.h("span", nil, "Visible"))Switch selects the first Match whose condition is truthy:
local page, set_page = Solid.createSignal("home")
local view = Solid.h(Solid.Switch, {
fallback = Solid.h("span", nil, "Not found"),
},
Solid.h(Solid.Match, {
when = function()
return page() == "home"
end,
}, Solid.h("div", nil, "Home")),
Solid.h(Solid.Match, {
when = function()
return page() == "settings"
end,
}, Solid.h("div", nil, "Settings"))
)Use keyed = true when a truthy value change should recreate the active branch:
local selected, set_selected = Solid.createSignal(false)
local view = Solid.h(Solid.Show, {
keyed = true,
when = selected,
}, function(value)
return Solid.h("span", nil, function()
return tostring(value())
end)
end)Lua treats 0 as truthy. Use an explicit comparison such as
when = function() return count() > 0 end when zero should mean false.
While the sample is running, edits to assets/hello.rml, assets/hello.rcss,
assets/solid.lua, and assets/hello.lua are detected and reloaded automatically.
To use another font:
cmake -S . -B build -G Ninja \
-DCMAKE_PREFIX_PATH=/opt/homebrew/opt/rmlui-lua54-brew \
-DSOLID_RML_FONT_PATH=/path/to/font.ttf