#include "app.hpp" #include #include #include #include #include #include App::App(Rml::Context *ctx, ApplicationData *appData) : ctx(ctx), appData(appData), doc(nullptr) {} App::~App() { Shutdown(); } void App::SetupListeners(std::function bindCallback) { onBindListeners = bindCallback; } void App::RegisterListener(Rml::Element* element, std::unique_ptr 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(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; } 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; } TeardownCurrentPage(); 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"; } 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"; } 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; 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; } }