217 lines
5.8 KiB
C++
217 lines
5.8 KiB
C++
#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;
|
|
}
|
|
} |