Added raylib+rmlui template

This commit is contained in:
2026-08-23 20:57:32 -05:00
commit 1a378aebf0
15 changed files with 685 additions and 0 deletions

51
src/Application.cpp Normal file
View File

@@ -0,0 +1,51 @@
#include "Application.h"
#include "backend/RaylibInput.h"
#include <raylib.h>
#include <RmlUi/Core.h>
#include <RmlUi/Debugger.h>
bool Application::Init(int width, int height, const char* title, const char* documentPath) {
InitWindow(width, height, title);
SetTargetFPS(60);
Rml::SetSystemInterface(&system_interface);
Rml::SetRenderInterface(&render_interface);
Rml::Initialise();
context = Rml::CreateContext("main", Rml::Vector2i(width, height));
Rml::Debugger::Initialise(context);
// TODO: load a real font
if (Rml::ElementDocument* document = context->LoadDocument(documentPath)) {
document->Show();
} else {
TraceLog(LOG_WARNING, "Failed to load %s", documentPath);
}
return true;
}
void Application::Frame() {
if (WindowShouldClose()) {
running = false;
return;
}
ForwardInputToRml(context);
context->Update();
BeginDrawing();
ClearBackground(DARKGRAY);
// --- game world rendering goes here, before the UI ---
context->Render();
EndDrawing();
}
void Application::Shutdown() {
Rml::Shutdown();
CloseWindow();
}

19
src/Application.h Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include <RmlUi/Core/Context.h>
#include "backend/RaylibSystemInterface.h"
#include "backend/RaylibRenderInterface.h"
class Application {
public:
bool Init(int width, int height, const char* title, const char* documentPath);
void Frame();
void Shutdown();
bool IsRunning() const { return running; }
private:
RaylibSystemInterface system_interface;
RaylibRenderInterface render_interface;
Rml::Context* context = nullptr;
bool running = true;
};

View File

@@ -0,0 +1,25 @@
#include "RaylibInput.h"
#include <raylib.h>
namespace {
int GetRmlKeyModifiers() {
int mods = 0;
if (IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT)) mods |= Rml::Input::KM_SHIFT;
if (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)) mods |= Rml::Input::KM_CTRL;
if (IsKeyDown(KEY_LEFT_ALT) || IsKeyDown(KEY_RIGHT_ALT)) mods |= Rml::Input::KM_ALT;
return mods;
}
}
void ForwardInputToRml(Rml::Context* context) {
Vector2 mouse = GetMousePosition();
context->ProcessMouseMove((int)mouse.x, (int)mouse.y, GetRmlKeyModifiers());
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) context->ProcessMouseButtonDown(0, GetRmlKeyModifiers());
if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) context->ProcessMouseButtonUp(0, GetRmlKeyModifiers());
if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) context->ProcessMouseButtonDown(1, GetRmlKeyModifiers());
if (IsMouseButtonReleased(MOUSE_BUTTON_RIGHT)) context->ProcessMouseButtonUp(1, GetRmlKeyModifiers());
float wheel = GetMouseWheelMove();
if (wheel != 0.0f) context->ProcessMouseWheel(-wheel, GetRmlKeyModifiers());
}

View File

@@ -0,0 +1,4 @@
#pragma once
#include <RmlUi/Core/Context.h>
void ForwardInputToRml(Rml::Context* context);

View File

@@ -0,0 +1,109 @@
#include "RaylibRenderInterface.h"
#include <raylib.h>
#include <rlgl.h>
#include <vector>
#include <cstring>
namespace {
struct GeometryData {
std::vector<Rml::Vertex> vertices;
std::vector<int> indices;
};
}
Rml::CompiledGeometryHandle RaylibRenderInterface::CompileGeometry(
Rml::Span<const Rml::Vertex> vertices, Rml::Span<const int> indices) {
auto* geo = new GeometryData();
geo->vertices.assign(vertices.begin(), vertices.end());
geo->indices.assign(indices.begin(), indices.end());
return reinterpret_cast<Rml::CompiledGeometryHandle>(geo);
}
void RaylibRenderInterface::RenderGeometry(Rml::CompiledGeometryHandle handle,
Rml::Vector2f translation,
Rml::TextureHandle texture) {
auto* geo = reinterpret_cast<GeometryData*>(handle);
if (texture) {
Texture2D* tex = reinterpret_cast<Texture2D*>(texture);
rlSetTexture(tex->id);
} else {
rlSetTexture(rlGetTextureIdDefault());
}
rlPushMatrix();
rlTranslatef(translation.x, translation.y, 0.0f);
rlBegin(RL_TRIANGLES);
for (size_t i = 0; i + 2 < geo->indices.size(); i += 3) {
for (int j = 0; j < 3; ++j) {
const Rml::Vertex& v = geo->vertices[geo->indices[i + j]];
rlColor4ub(v.colour.red, v.colour.green, v.colour.blue, v.colour.alpha);
rlTexCoord2f(v.tex_coord.x, v.tex_coord.y);
rlVertex2f(v.position.x, v.position.y);
}
}
rlEnd();
rlPopMatrix();
rlSetTexture(0);
}
void RaylibRenderInterface::ReleaseGeometry(Rml::CompiledGeometryHandle handle) {
delete reinterpret_cast<GeometryData*>(handle);
}
Rml::TextureHandle RaylibRenderInterface::LoadTexture(Rml::Vector2i& texture_dimensions,
const Rml::String& source) {
Texture2D tex = ::LoadTexture(source.c_str());
if (tex.id == 0) {
TraceLog(LOG_WARNING, "[RmlUi] failed to load texture: %s", source.c_str());
return 0;
}
texture_dimensions = Rml::Vector2i(tex.width, tex.height);
return reinterpret_cast<Rml::TextureHandle>(new Texture2D(tex));
}
Rml::TextureHandle RaylibRenderInterface::GenerateTexture(Rml::Span<const Rml::byte> source_data,
Rml::Vector2i source_dimensions) {
Image img{};
img.width = source_dimensions.x;
img.height = source_dimensions.y;
img.mipmaps = 1;
img.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
size_t byte_count = static_cast<size_t>(img.width) * img.height * 4;
img.data = RL_MALLOC(byte_count);
std::memcpy(img.data, source_data.data(), byte_count);
Texture2D tex = LoadTextureFromImage(img);
UnloadImage(img); // frees img.data
return reinterpret_cast<Rml::TextureHandle>(new Texture2D(tex));
}
void RaylibRenderInterface::ReleaseTexture(Rml::TextureHandle handle) {
Texture2D* tex = reinterpret_cast<Texture2D*>(handle);
UnloadTexture(*tex);
delete tex;
}
void RaylibRenderInterface::EnableScissorRegion(bool enable) {
scissor_enabled = enable;
if (enable) ApplyScissor();
else EndScissorMode();
}
void RaylibRenderInterface::SetScissorRegion(Rml::Rectanglei region) {
scissor_region = region;
if (scissor_enabled) ApplyScissor();
}
void RaylibRenderInterface::ApplyScissor() {
BeginScissorMode(scissor_region.Left(), scissor_region.Top(),
scissor_region.Width(), scissor_region.Height());
}

View File

@@ -0,0 +1,37 @@
#pragma once
#include <RmlUi/Core/RenderInterface.h>
// NOTE: RmlUi's RenderInterface signature has shifted across versions
// (raw pointer+count vs. Rml::Span<>). If this fails to compile against
// a different RmlUi commit, check RmlUi/Core/RenderInterface.h and adjust
// CompileGeometry/GenerateTexture's parameter types, internal logic
// stays the same.
class RaylibRenderInterface : public Rml::RenderInterface {
public:
Rml::CompiledGeometryHandle CompileGeometry(Rml::Span<const Rml::Vertex> vertices,
Rml::Span<const int> indices) override;
void RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation,
Rml::TextureHandle texture) override;
void ReleaseGeometry(Rml::CompiledGeometryHandle handle) override;
Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions,
const Rml::String& source) override;
Rml::TextureHandle GenerateTexture(Rml::Span<const Rml::byte> source_data,
Rml::Vector2i source_dimensions) override;
void ReleaseTexture(Rml::TextureHandle handle) override;
void EnableScissorRegion(bool enable) override;
void SetScissorRegion(Rml::Rectanglei region) override;
private:
void ApplyScissor();
bool scissor_enabled = false;
Rml::Rectanglei scissor_region;
};

View File

@@ -0,0 +1,14 @@
#include "RaylibSystemInterface.h"
#include <raylib.h>
double RaylibSystemInterface::GetElapsedTime() {
return GetTime();
}
bool RaylibSystemInterface::LogMessage(Rml::Log::Type type, const Rml::String& message) {
TraceLogLevel level = LOG_INFO;
if (type == Rml::Log::LT_WARNING) level = LOG_WARNING;
if (type == Rml::Log::LT_ERROR || type == Rml::Log::LT_ASSERT) level = LOG_ERROR;
TraceLog(level, "[RmlUi] %s", message.c_str());
return true;
}

View File

@@ -0,0 +1,8 @@
#pragma once
#include <RmlUi/Core/SystemInterface.h>
class RaylibSystemInterface : public Rml::SystemInterface {
public:
double GetElapsedTime() override;
bool LogMessage(Rml::Log::Type type, const Rml::String& message) override;
};

33
src/main.cpp Normal file
View File

@@ -0,0 +1,33 @@
#include "Application.h"
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
namespace {
Application* g_app = nullptr;
void MainLoopStep() {
g_app->Frame();
}
} // namespace
int main() {
static Application app;
g_app = &app;
app.Init(1280, 720, "gamejam", "assets/main.rml");
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop(MainLoopStep, 0, 1);
#else
while (app.IsRunning()) {
app.Frame();
}
#endif
app.Shutdown();
return 0;
}