implement data model binding
This commit is contained in:
239
main.cpp
239
main.cpp
@@ -2,10 +2,7 @@
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
|
||||
#include <RmlUi/Core.h>
|
||||
#include <RmlUi/Debugger.h>
|
||||
#include <RmlUi/Lua.h>
|
||||
@@ -14,7 +11,6 @@
|
||||
#include <RmlUi_Include_Windows.h>
|
||||
#include <RmlUi_Include_GL3.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <png.h>
|
||||
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
@@ -22,106 +18,21 @@ extern "C" {
|
||||
#include <lualib.h>
|
||||
}
|
||||
|
||||
// Global app state for Lua access
|
||||
#include "src/data_models.h"
|
||||
#include "src/system_interface.h"
|
||||
#include "src/utils.h"
|
||||
|
||||
// Global app state
|
||||
struct AppState {
|
||||
Rml::Context* context = nullptr;
|
||||
Rml::ElementDocument* document = nullptr;
|
||||
std::filesystem::path assets_path;
|
||||
} g_app;
|
||||
|
||||
// Custom system interface to enable logging
|
||||
class LoggingSystemInterface : public Rml::SystemInterface {
|
||||
public:
|
||||
LoggingSystemInterface(Rml::SystemInterface* backend) : backend_(backend) {}
|
||||
|
||||
double GetElapsedTime() override { return backend_->GetElapsedTime(); }
|
||||
|
||||
bool LogMessage(Rml::Log::Type type, const Rml::String& message) override {
|
||||
const char* type_str = "";
|
||||
switch (type) {
|
||||
case Rml::Log::LT_ERROR: type_str = "ERROR"; break;
|
||||
case Rml::Log::LT_WARNING: type_str = "WARNING"; break;
|
||||
case Rml::Log::LT_INFO: type_str = "INFO"; break;
|
||||
case Rml::Log::LT_DEBUG: type_str = "DEBUG"; break;
|
||||
default: type_str = "LOG"; break;
|
||||
}
|
||||
std::println("[RmlUi {}] {}", type_str, message);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Forward JoinPath to fix Windows path issues
|
||||
void JoinPath(Rml::String& translated_path, const Rml::String& document_path, const Rml::String& path) override {
|
||||
// Fix paths where colon was converted to pipe (D| -> D:)
|
||||
std::string fixed_path = path;
|
||||
if (fixed_path.length() >= 2 && fixed_path[1] == '|') {
|
||||
fixed_path[1] = ':';
|
||||
}
|
||||
|
||||
std::string fixed_doc = document_path;
|
||||
if (fixed_doc.length() >= 2 && fixed_doc[1] == '|') {
|
||||
fixed_doc[1] = ':';
|
||||
}
|
||||
|
||||
// Use std::filesystem to join paths properly and normalize (resolve ..)
|
||||
std::filesystem::path doc_dir = std::filesystem::path(fixed_doc).parent_path();
|
||||
std::filesystem::path result = (doc_dir / fixed_path).lexically_normal();
|
||||
translated_path = result.generic_string();
|
||||
std::println("JoinPath: {} + {} -> {}", fixed_doc, fixed_path, translated_path);
|
||||
}
|
||||
|
||||
private:
|
||||
Rml::SystemInterface* backend_;
|
||||
};
|
||||
|
||||
static LoggingSystemInterface* g_logging_interface = nullptr;
|
||||
|
||||
// Custom file interface to fix Windows path issues (D| -> D:)
|
||||
class WindowsFileInterface : public Rml::FileInterface {
|
||||
public:
|
||||
Rml::FileHandle Open(const Rml::String& path) override {
|
||||
// Fix paths where colon was converted to pipe (D| -> D:)
|
||||
std::string fixed_path = path;
|
||||
if (fixed_path.length() >= 2 && fixed_path[1] == '|') {
|
||||
fixed_path[1] = ':';
|
||||
}
|
||||
std::println("FileInterface::Open: {} -> {}", path, fixed_path);
|
||||
|
||||
FILE* fp = fopen(fixed_path.c_str(), "rb");
|
||||
return reinterpret_cast<Rml::FileHandle>(fp);
|
||||
}
|
||||
|
||||
void Close(Rml::FileHandle file) override {
|
||||
fclose(reinterpret_cast<FILE*>(file));
|
||||
}
|
||||
|
||||
size_t Read(void* buffer, size_t size, Rml::FileHandle file) override {
|
||||
return fread(buffer, 1, size, reinterpret_cast<FILE*>(file));
|
||||
}
|
||||
|
||||
bool Seek(Rml::FileHandle file, long offset, int origin) override {
|
||||
return fseek(reinterpret_cast<FILE*>(file), offset, origin) == 0;
|
||||
}
|
||||
|
||||
size_t Tell(Rml::FileHandle file) override {
|
||||
return ftell(reinterpret_cast<FILE*>(file));
|
||||
}
|
||||
};
|
||||
|
||||
static WindowsFileInterface* g_file_interface = nullptr;
|
||||
|
||||
void load_fonts(const std::filesystem::path& dir)
|
||||
{
|
||||
for (const auto& file : std::filesystem::directory_iterator(dir))
|
||||
{
|
||||
if (file.path().extension() == ".ttf")
|
||||
{
|
||||
Rml::LoadFontFace(file.path().string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lua function: loadScreen(path) - loads a new RML document
|
||||
// Path is relative to assets folder
|
||||
int lua_loadScreen(lua_State* L)
|
||||
{
|
||||
std::println("lua_loadScreen called!");
|
||||
@@ -168,131 +79,12 @@ void registerLuaFunctions()
|
||||
{
|
||||
lua_State* L = Rml::Lua::Interpreter::GetLuaState();
|
||||
|
||||
// Register global function
|
||||
lua_pushcfunction(L, lua_loadScreen);
|
||||
lua_setglobal(L, "loadScreen");
|
||||
|
||||
std::println("Registered Lua functions");
|
||||
}
|
||||
|
||||
void DumpElementTree(Rml::Element* element, std::ofstream& out, int depth = 0)
|
||||
{
|
||||
if (!element) return;
|
||||
|
||||
std::string indent(depth * 2, ' ');
|
||||
std::string id = element->GetId().empty() ? "" : "#" + element->GetId();
|
||||
|
||||
out << indent << element->GetTagName() << id
|
||||
<< " @ (" << element->GetAbsoluteLeft() << ", " << element->GetAbsoluteTop() << ") "
|
||||
<< element->GetOffsetWidth() << "x" << element->GetOffsetHeight() << "\n";
|
||||
|
||||
for (int i = 0; i < element->GetNumChildren(); i++)
|
||||
DumpElementTree(element->GetChild(i), out, depth + 1);
|
||||
}
|
||||
|
||||
// Offscreen framebuffer for screenshot capture
|
||||
struct OffscreenFBO {
|
||||
GLuint fbo = 0;
|
||||
GLuint texture = 0;
|
||||
GLuint depth_rbo = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
bool create(int w, int h)
|
||||
{
|
||||
width = w;
|
||||
height = h;
|
||||
|
||||
// Create framebuffer
|
||||
glGenFramebuffers(1, &fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
|
||||
// Create color texture
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
|
||||
|
||||
// Create depth/stencil renderbuffer
|
||||
glGenRenderbuffers(1, &depth_rbo);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, depth_rbo);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depth_rbo);
|
||||
|
||||
// Check completeness
|
||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
||||
{
|
||||
std::println("Failed to create offscreen framebuffer");
|
||||
destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void destroy()
|
||||
{
|
||||
if (texture) glDeleteTextures(1, &texture);
|
||||
if (depth_rbo) glDeleteRenderbuffers(1, &depth_rbo);
|
||||
if (fbo) glDeleteFramebuffers(1, &fbo);
|
||||
fbo = texture = depth_rbo = 0;
|
||||
}
|
||||
|
||||
void bind()
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
glViewport(0, 0, width, height);
|
||||
}
|
||||
|
||||
std::vector<unsigned char> readPixels()
|
||||
{
|
||||
std::vector<unsigned char> pixels(width * height * 3);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels.data());
|
||||
return pixels;
|
||||
}
|
||||
};
|
||||
|
||||
void SavePNG(const std::filesystem::path& path, const std::vector<unsigned char>& pixels, int width, int height)
|
||||
{
|
||||
// Flip vertically (OpenGL origin is bottom-left)
|
||||
std::vector<unsigned char> flipped(width * height * 3);
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
std::memcpy(&flipped[y * width * 3],
|
||||
&pixels[(height - 1 - y) * width * 3],
|
||||
width * 3);
|
||||
}
|
||||
|
||||
FILE* fp = fopen(path.string().c_str(), "wb");
|
||||
if (!fp)
|
||||
{
|
||||
std::println("Failed to open {} for writing", path.string());
|
||||
return;
|
||||
}
|
||||
|
||||
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
png_infop info = png_create_info_struct(png);
|
||||
png_init_io(png, fp);
|
||||
png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGB,
|
||||
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||||
png_write_info(png, info);
|
||||
|
||||
std::vector<png_bytep> rows(height);
|
||||
for (int y = 0; y < height; y++)
|
||||
rows[y] = const_cast<png_bytep>(&flipped[y * width * 3]);
|
||||
png_write_image(png, rows.data());
|
||||
png_write_end(png, nullptr);
|
||||
|
||||
png_destroy_write_struct(&png, &info);
|
||||
fclose(fp);
|
||||
|
||||
std::println("Screenshot saved to: {}", path.string());
|
||||
}
|
||||
|
||||
int main(const int argc, const char* argv[])
|
||||
{
|
||||
constexpr int window_width = 540;
|
||||
@@ -312,7 +104,7 @@ int main(const int argc, const char* argv[])
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (!Backend::Initialize("Load Document Sample", window_width, window_height, true))
|
||||
if (!Backend::Initialize("Mosis Designer", window_width, window_height, true))
|
||||
{
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
@@ -335,13 +127,16 @@ int main(const int argc, const char* argv[])
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Initialize sample data and data models
|
||||
initializeSampleData();
|
||||
setupDataModels(g_app.context);
|
||||
|
||||
// Register custom Lua functions
|
||||
registerLuaFunctions();
|
||||
|
||||
// Find the assets folder by checking for fonts/ subdirectory
|
||||
g_app.assets_path = std::filesystem::absolute(file.parent_path());
|
||||
|
||||
// Walk up the directory tree to find a folder containing a fonts/ subdirectory with .ttf files
|
||||
std::filesystem::path fonts_path;
|
||||
while (!g_app.assets_path.empty() && g_app.assets_path.has_parent_path())
|
||||
{
|
||||
@@ -383,7 +178,6 @@ int main(const int argc, const char* argv[])
|
||||
// Dump mode: render and capture screenshot
|
||||
if (dump_mode)
|
||||
{
|
||||
// Create offscreen FBO at window dimensions
|
||||
OffscreenFBO fbo;
|
||||
if (!fbo.create(window_width, window_height))
|
||||
{
|
||||
@@ -414,26 +208,21 @@ int main(const int argc, const char* argv[])
|
||||
dump_file.close();
|
||||
}
|
||||
|
||||
// Bind FBO and set up OpenGL state for rendering
|
||||
// Bind FBO and render
|
||||
fbo.bind();
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||
|
||||
// Enable blending for proper text rendering
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// Render to FBO
|
||||
g_app.context->Update();
|
||||
g_app.context->Render();
|
||||
|
||||
// Read pixels
|
||||
auto pixels = fbo.readPixels();
|
||||
fbo.destroy();
|
||||
|
||||
SavePNG(dump_folder / "screenshot.png", pixels, window_width, window_height);
|
||||
|
||||
// Restore default framebuffer
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
||||
Rml::Shutdown();
|
||||
@@ -441,6 +230,7 @@ int main(const int argc, const char* argv[])
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
// Watch for file changes
|
||||
const HANDLE hNotif = FindFirstChangeNotification(g_app.assets_path.c_str(),
|
||||
TRUE, FILE_NOTIFY_CHANGE_LAST_WRITE);
|
||||
|
||||
@@ -469,6 +259,9 @@ int main(const int argc, const char* argv[])
|
||||
Backend::PresentFrame();
|
||||
}
|
||||
|
||||
delete g_logging_interface;
|
||||
delete g_file_interface;
|
||||
|
||||
Rml::Shutdown();
|
||||
Backend::Shutdown();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user