Adding template project and correct name

This commit is contained in:
2026-07-19 22:15:13 -05:00
commit 718156d516
90 changed files with 71513 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
#include <RmlUi/Core/Event.h>
#include <RmlUi/Core/EventListener.h>
class ButtonClickListener : public Rml::EventListener {
public:
using ClickCallback = std::function<void(Rml::Event &event)>;
ButtonClickListener(ClickCallback callback) : callback(callback) {}
void ProcessEvent(Rml::Event &event) override {
if (event.GetId() == Rml::EventId::Click) {
if (callback) callback(event);
}
}
private:
ClickCallback callback;
};

217
src/app.cpp Normal file
View File

@@ -0,0 +1,217 @@
#include "app.hpp"
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/Factory.h>
#include <RmlUi/Lua/Interpreter.h>
#include <RmlUi/Core/StyleSheetContainer.h>
#include <RmlUi/Lua.h>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <lauxlib.h>
#include <lua.h>
#include <sstream>
App::App(Rml::Context *ctx, ApplicationData *appData)
: ctx(ctx), appData(appData), doc(nullptr) {}
App::~App() {
Shutdown();
}
void App::SetupListeners(std::function<void(Rml::ElementDocument*)> bindCallback) {
onBindListeners = bindCallback;
}
void App::RegisterListener(Rml::Element* element, std::unique_ptr<Rml::EventListener> listener) {
if (element && listener) {
element->AddEventListener(Rml::EventId::Click, listener.get());
listeners.push_back(std::move(listener));
}
}
PageBase* App::GetOrCreatePage(const std::string& pageName) {
if (auto it = pages.find(pageName); it != pages.end()) {
return it->second.get();
}
std::string path = GetAssetsPath() + "/pages/" + pageName + ".rml";
if (!std::filesystem::exists(path)) {
return nullptr;
}
auto page = std::make_unique<PageBase>(pageName, this);
PageBase* raw = page.get();
pages.emplace(pageName, std::move(page));
return raw;
}
bool App::ReadFile(const std::string& path, std::string& outContent) {
std::ifstream file(path, std::ios::in | std::ios::binary);
if (!file) return false;
std::ostringstream ss;
ss << file.rdbuf();
outContent = ss.str();
return true;
}
bool App::NavigateTo(const std::string &pageName) {
if (!pageOutlet) {
std::cout << "[ERROR] no page outlet - is main.rml missing id=\"page-content\"?\n";
return false;
}
PageBase* page = GetOrCreatePage(pageName);
if (!page) {
std::cout << "[ERROR] no page registered or found on disk for '" << pageName << "'\n";
return false;
}
TeardownCurrentPage();
std::string luaPath = GetAssetsPath() + "/pages/" + pageName + ".lua";
if (std::filesystem::exists(luaPath)) {
std::string luaContent;
if (ReadFile(luaPath, luaContent)) {
lua_State *L = Rml::Lua::Interpreter::GetLuaState();
if (luaL_dostring(L, luaContent.c_str()) != LUA_OK) {
std::cout << "[LUA ERROR] Failed to run page script " << pageName << ".lua: "
<< lua_tostring(L, -1) << "\n";
lua_pop(L, 1);
}
}
}
std::string path = GetAssetsPath() + "/pages/" + pageName + ".rml";
std::string content;
if (!ReadFile(path, content)) {
std::cout << "[ERROR] could not read page file: " << path << "\n";
return false;
}
if (Rml::DataModelConstructor constructor = ctx->CreateDataModel(pageName)) {
page->SetupDataModel(constructor);
page->model = constructor.GetModelHandle();
page->hasModel = true;
} else {
std::cout << "[WARN] failed to create data model for page '" << pageName << "'\n";
}
std::string rcssPath = GetAssetsPath() + "/pages/" + pageName + ".rcss";
if (std::filesystem::exists(rcssPath)) {
auto pageStyleSheet = Rml::Factory::InstanceStyleSheetFile(rcssPath);
if (pageStyleSheet && baseStyleSheet) {
auto combined = baseStyleSheet->CombineStyleSheetContainer(*pageStyleSheet);
doc->SetStyleSheetContainer(combined);
}
} else if (doc && baseStyleSheet) {
doc->SetStyleSheetContainer(baseStyleSheet);
}
pageOutlet->SetInnerRML(content);
page->root = pageOutlet->GetElementById("page-root");
if (!page->root) {
std::cout << "[WARN] page '" << pageName << "' has no element with id=\"page-root\"\n";
}
currentPage = page;
page->OnEnter(page->root);
return true;
}
bool App::LoadUi() {
if (!ctx) return false;
doc = ctx->LoadDocument(GetAssetsPath() + "/main.rml");
if (!doc) return false;
doc->Show();
pageOutlet = doc->GetElementById("page-content");
if (!pageOutlet) {
std::cout << "[WARN] main.rml has no element with id=\"page-content\"\n";
}
baseStyleSheet = Rml::Factory::InstanceStyleSheetFile(GetAssetsPath() + "/styles/main.rcss");
listeners.clear();
if (onBindListeners) {
onBindListeners(doc);
}
return true;
}
void App::Reload() {
Rml::Factory::ClearStyleSheetCache();
Rml::Factory::ClearTemplateCache();
std::string previousPage = currentPage ? currentPage->GetName() : "";
TeardownCurrentPage();
if (doc) {
doc->Close();
doc = nullptr;
pageOutlet = nullptr;
}
LoadUi();
if (!previousPage.empty()) {
NavigateTo(previousPage);
}
}
std::string App::GetAssetsPath() {
std::string install_path = std::string(ASSETS_DIR) + "/main.rml";
if (std::filesystem::exists(install_path)) {
return std::string(ASSETS_DIR);
}
if (std::filesystem::exists("./assets/main.rml")) {
return "./assets";
}
return ".";
}
void App::TeardownCurrentPage() {
if (!currentPage) return;
lua_State* L = Rml::Lua::Interpreter::GetLuaState();
lua_pushnil(L);
lua_setglobal(L, currentPage->GetName().c_str());
currentPage->OnExit();
if (currentPage->hasModel) {
ctx->RemoveDataModel(currentPage->GetName());
currentPage->hasModel = false;
}
currentPage->root = nullptr;
if (pageOutlet) {
pageOutlet->SetInnerRML("");
}
currentPage->pageListeners.clear();
currentPage = nullptr;
}
void App::Shutdown() {
if (hasShutdown) return;
hasShutdown = true;
TeardownCurrentPage(); // uses ctx->RemoveDataModel - must run while ctx is still valid
if (doc) {
doc->Close(); // deferred destruction, but marks it for cleanup on next Update/Shutdown
doc = nullptr;
pageOutlet = nullptr;
}
}

58
src/app.hpp Normal file
View File

@@ -0,0 +1,58 @@
#pragma once
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/ElementDocument.h>
#include <RmlUi/Core/EventListener.h>
#include <string>
#include <type_traits>
#include <vector>
#include <memory>
#include <functional>
#include <unordered_map>
#include "page.hpp"
struct ApplicationData {};
class App {
public:
App(Rml::Context* ctx, ApplicationData* appData);
~App();
void SetupListeners(std::function<void(Rml::ElementDocument*)> bindCallback);
void RegisterListener(Rml::Element* element, std::unique_ptr<Rml::EventListener> listener);
template <typename T = PageBase>
void RegisterPage(const std::string &pageName) {
static_assert(std::is_base_of<PageBase, T>::value, "T must derive from PageBase");
pages[pageName] = std::make_unique<T>(pageName, this);
}
bool NavigateTo(const std::string &pageName);
bool LoadUi();
void Reload();
static std::string GetAssetsPath();
void Shutdown();
private:
ApplicationData *appData;
Rml::Context* ctx;
Rml::ElementDocument* doc;
Rml::Element* pageOutlet = nullptr;
Rml::SharedPtr<Rml::StyleSheetContainer> baseStyleSheet;
std::vector<std::unique_ptr<Rml::EventListener>> listeners;
std::function<void(Rml::ElementDocument*)> onBindListeners;
std::unordered_map<std::string, std::unique_ptr<PageBase>> pages;
PageBase* currentPage = nullptr;
PageBase* GetOrCreatePage(const std::string& pageName);
static bool ReadFile(const std::string& path, std::string& outContent);
void TeardownCurrentPage();
bool hasShutdown = false;
};

22
src/element_registry.hpp Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include <RmlUi/Core/ElementInstancer.h>
#include <RmlUi/Core/Factory.h>
#include <memory>
#include <string>
#include <type_traits>
#include <vector>
class ElementRegistry {
public:
template <typename T>
void Register(const std::string& tag) {
static_assert(std::is_base_of<Rml::Element, T>::value, "T must derive from Rml::Element");
auto instancer = std::make_unique<Rml::ElementInstancerGeneric<T>>();
Rml::Factory::RegisterElementInstancer(tag, instancer.get());
instancers.push_back(std::move(instancer));
}
private:
std::vector<std::unique_ptr<Rml::ElementInstancer>> instancers;
};

35
src/lua_app_api.cpp Normal file
View File

@@ -0,0 +1,35 @@
#include "lui_app_api.hpp"
#include <RmlUi/Lua/Interpreter.h>
#include <RmlUi/Lua/Lua.h>
#include <iostream>
extern "C" {
#include "lua.h"
}
int AppLuaApi::Lua_NavigateTo(lua_State *L) {
AppLuaApi* apiInstance = static_cast<AppLuaApi*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!apiInstance || !apiInstance->app) {
std::cout << "[LUA ERROR] API context or App pointer is invalid.\n";
return 0;
}
if (lua_isstring(L, 1)) {
std::string pageName = lua_tostring(L, 1);
apiInstance->app->NavigateTo(pageName);
} else {
std::cout << "[LUA ERROR] NavigateTo expects a string argument for pageName.\n";
}
return 0;
}
void AppLuaApi::RegisterLuaBindings() {
if (!L) return;
lua_pushlightuserdata(L, this);
lua_pushcclosure(L, Lua_NavigateTo, 1);
lua_setglobal(L, "NavigateTo");
}

16
src/lui_app_api.hpp Normal file
View File

@@ -0,0 +1,16 @@
#include <RmlUi/Lua/Lua.h>
#include "app.hpp"
class AppLuaApi {
public:
AppLuaApi(App *app, lua_State *L) : app(app), L(L) {}
void RegisterLuaBindings();
static int Lua_NavigateTo(lua_State *L);
private:
App *app;
lua_State *L;
};

130
src/main.cpp Normal file
View File

@@ -0,0 +1,130 @@
#include <GLFW/glfw3.h>
#include <RmlUi/Core/ElementDocument.h>
#include <RmlUi/Core/Event.h>
#include <RmlUi/Debugger/Debugger.h>
#include <RmlUi/Lua/Interpreter.h>
#include <iostream>
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/Math.h>
#include <RmlUi/Core.h>
#include <RmlUi_Backend.h>
#include <RmlUi/Debugger.h>
#include <RmlUi/Lua/Lua.h>
#include "app.hpp"
#include "element_registry.hpp"
#include "ButtonClickListener.hpp"
#include "lui_app_api.hpp"
static bool is_initalized = false;
static bool is_running = false;
static Rml::Context *ctx;
static App *app;
static ElementRegistry elementRegistry;
void RegisterElements() {
}
bool InitalizeApplication() {
if (!Backend::Initialize("RmlUi App Template", 800, 600, true)) {
std::cout << "[ERROR] backend was not initalized successfully.\n";
return false;
}
Rml::SetSystemInterface(Backend::GetSystemInterface());
Rml::SetRenderInterface(Backend::GetRenderInterface());
Rml::Initialise();
Rml::Lua::Initialise();
ctx = Rml::CreateContext("main", Rml::Vector2i(800,600));
if (!ctx) {
std::cout << "[ERROR] context was null.\n";
Rml::Shutdown();
Backend::Shutdown();
return false;
}
Rml::Debugger::Initialise(ctx);
app = new App(ctx, nullptr);
if (!app) {
std::cout << "[ERROR] app was null.\n";
Rml::Shutdown();
Backend::Shutdown();
return false;
}
lua_State* L = Rml::Lua::Interpreter::GetLuaState();
AppLuaApi *lua_app_api = new AppLuaApi(app, L);
lua_app_api->RegisterLuaBindings();
RegisterElements();
std::cout << "Application initalized...\n";
return true;
}
bool f5_was_pressed = false;
bool f8_was_pressed = false;
void handle_input() {
GLFWwindow *window = glfwGetCurrentContext();
if(window) {
// Reload ui
bool f5_state = glfwGetKey(window, GLFW_KEY_F5);
if (f5_state == GLFW_PRESS && !f5_was_pressed) {
f5_was_pressed = true;
app->Reload();
} else if (f5_state == GLFW_RELEASE) {
f5_was_pressed = false;
}
// Toggle debugger window
bool f8_state = glfwGetKey(window, GLFW_KEY_F8);
if (f8_state == GLFW_PRESS && !f8_was_pressed) {
f8_was_pressed = true;
bool is_visible = Rml::Debugger::IsVisible();
Rml::Debugger::SetVisible(!is_visible);
} else if (f8_state == GLFW_RELEASE) {
f8_was_pressed = false;
}
}
}
int main() {
std::cout << "Application starting...\n";
is_initalized = InitalizeApplication();
if (!is_initalized) return 1;
is_running = true;
std::cout << "Loading UI...\n";
app->SetupListeners([](Rml::ElementDocument *doc) {
});
app->LoadUi();
app->NavigateTo("home");
std::cout << "UI Loaded\n";
while (is_running) {
is_running = Backend::ProcessEvents(ctx);
handle_input();
ctx->Update();
Backend::BeginFrame();
ctx->Render();
Backend::PresentFrame();
}
app->Shutdown();
Rml::Shutdown();
delete app;
Backend::Shutdown();
return 0;
}

0
src/page.cpp Normal file
View File

49
src/page.hpp Normal file
View File

@@ -0,0 +1,49 @@
#pragma once
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/DataModelHandle.h>
#include <RmlUi/Core/Element.h>
#include <RmlUi/Core/ElementDocument.h>
#include <RmlUi/Core/Factory.h>
#include <string>
class App;
class PageBase
{
public:
PageBase(std::string pageName, App* app) : pageName(std::move(pageName)), app(app) {}
virtual ~PageBase() = default;
const std::string& GetName() const { return pageName; }
bool TryLoadRmlPage(std::string path = "");
virtual void SetupDataModel(Rml::DataModelConstructor constructor) {}
virtual void OnEnter(Rml::Element *root) {}
virtual void OnExit() {}
protected:
App* GetApp() const {return app;}
void RegisterListener(Rml::Element* element, std::unique_ptr<Rml::EventListener> listener) {
if (element && listener) {
element->AddEventListener(Rml::EventId::Click, listener.get());
pageListeners.push_back(std::move(listener));
}
}
void DirtyVariable(const std::string& name) {
if (hasModel) model.DirtyVariable(name);
}
private:
friend class App;
std::string pageName;
App *app;
std::vector<std::unique_ptr<Rml::EventListener>> pageListeners;
Rml::Element *root = nullptr;
Rml::DataModelHandle model;
bool hasModel = false;
};