Files
rmlui-app-template/src/app.cpp
chris bell 7c6e3a0d89 Shell + page loading
And:
- some basic stylesheet i found for basic html elements
- a global lua method for navigating pages
2026-07-11 21:51:33 -05:00

181 lines
4.4 KiB
C++

#include "app.hpp"
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/Factory.h>
#include <filesystem>
#include <fstream>
#include <iostream>
#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;
}
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;
}
}