Adding template project and correct name
This commit is contained in:
18
src/ButtonClickListener.hpp
Normal file
18
src/ButtonClickListener.hpp
Normal 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
217
src/app.cpp
Normal 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
58
src/app.hpp
Normal 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
22
src/element_registry.hpp
Normal 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
35
src/lua_app_api.cpp
Normal 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
16
src/lui_app_api.hpp
Normal 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
130
src/main.cpp
Normal 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
0
src/page.cpp
Normal file
49
src/page.hpp
Normal file
49
src/page.hpp
Normal 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;
|
||||
};
|
||||
Reference in New Issue
Block a user