🎯 **PRODUCTION-READY UI ARCHITECTURE** - Data-agnostic IUI interface with type-safe enums for performance - Revolutionary hybrid sizing: percentage targets + absolute pixel constraints - Hierarchical windowing: Parent → Dock → Split → Tab → Window structure - Complete ImGuiUI implementation with all DataType content renderers 🔧 **DEVELOPMENT INFRASTRUCTURE** - AddressSanitizer + GDB debugging workflow for instant crash detection - Cross-platform pipeline: Linux development → Windows .exe automation - Debug mode default with comprehensive sanitizer coverage 📊 **TECHNICAL ACHIEVEMENTS** - Fixed JSON type mixing and buffer overflow crashes with precise debugging - Mathematical hybrid sizing formula: clamp(percentage_target, min_px, max_px) - Professional layout system: economic topbar + companies panel + strategic map - Interactive callback system with request/response architecture 🚀 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
499 lines
17 KiB
C++
499 lines
17 KiB
C++
#pragma once
|
|
|
|
#include "IUI_Enums.h"
|
|
#include <imgui.h>
|
|
#include <imgui_impl_glfw.h>
|
|
#include <imgui_impl_opengl3.h>
|
|
#include <GLFW/glfw3.h>
|
|
#include <GL/gl.h>
|
|
|
|
#include <map>
|
|
#include <vector>
|
|
#include <string>
|
|
#include <functional>
|
|
#include <chrono>
|
|
|
|
namespace warfactory {
|
|
|
|
/**
|
|
* @brief ImGui implementation of IUI interface
|
|
*
|
|
* Provides full windowing system with docking, tabs, splits, and floating windows.
|
|
* Handles hybrid percentage + pixel sizing with automatic constraint enforcement.
|
|
*/
|
|
class ImGuiUI : public IUI {
|
|
private:
|
|
// ========================================
|
|
// CORE STATE
|
|
// ========================================
|
|
|
|
GLFWwindow* window = nullptr;
|
|
bool initialized = false;
|
|
bool should_close = false;
|
|
int frame_count = 0;
|
|
|
|
// Screen/parent sizes for percentage calculations
|
|
ImVec2 screen_size = {1400, 900};
|
|
|
|
// ========================================
|
|
// WINDOW MANAGEMENT
|
|
// ========================================
|
|
|
|
struct WindowInfo {
|
|
std::string id;
|
|
std::string title;
|
|
std::string parent;
|
|
DockPosition dock_position = DockPosition::CENTER;
|
|
bool is_open = true;
|
|
bool is_floating = false;
|
|
bool resizable = true;
|
|
bool closeable = true;
|
|
|
|
// Size system
|
|
ImVec2 size = {400, 300};
|
|
ImVec2 min_size = {100, 100};
|
|
ImVec2 max_size = {2000, 1500};
|
|
ImVec2 position = {0, 0};
|
|
|
|
// Percentage tracking
|
|
std::string size_width_percent = "";
|
|
std::string size_height_percent = "";
|
|
std::string min_width_percent = "";
|
|
std::string min_height_percent = "";
|
|
std::string max_width_percent = "";
|
|
std::string max_height_percent = "";
|
|
|
|
// Content
|
|
DataType data_type = DataType::CUSTOM;
|
|
json content_data;
|
|
};
|
|
|
|
std::map<std::string, WindowInfo> windows;
|
|
|
|
struct DockInfo {
|
|
std::string id;
|
|
DockType type = DockType::DOCK;
|
|
DockPosition position = DockPosition::LEFT;
|
|
std::string parent;
|
|
bool collapsible = true;
|
|
bool resizable = true;
|
|
ImVec2 size = {300, 200};
|
|
ImVec2 min_size = {100, 100};
|
|
ImVec2 max_size = {1000, 800};
|
|
std::vector<std::string> child_windows;
|
|
};
|
|
|
|
std::map<std::string, DockInfo> docks;
|
|
|
|
// ========================================
|
|
// CALLBACKS
|
|
// ========================================
|
|
|
|
std::map<RequestType, std::function<void(const json&)>> request_callbacks;
|
|
std::map<std::string, std::function<void(const json&)>> custom_request_callbacks;
|
|
|
|
// ========================================
|
|
// MESSAGE SYSTEM
|
|
// ========================================
|
|
|
|
struct LogMessage {
|
|
EventLevel level;
|
|
std::string message;
|
|
std::chrono::steady_clock::time_point timestamp;
|
|
};
|
|
|
|
std::vector<LogMessage> log_messages;
|
|
static constexpr size_t MAX_LOG_MESSAGES = 100;
|
|
|
|
public:
|
|
ImGuiUI() = default;
|
|
~ImGuiUI() override { shutdown(); }
|
|
|
|
// ========================================
|
|
// LIFECYCLE IMPLEMENTATION
|
|
// ========================================
|
|
|
|
void initialize(const json& config) override {
|
|
if (initialized) return;
|
|
|
|
// Initialize GLFW
|
|
if (!glfwInit()) {
|
|
throw std::runtime_error("Failed to initialize GLFW");
|
|
}
|
|
|
|
// OpenGL 3.3 Core
|
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
|
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
|
|
|
// Create window
|
|
std::string title = config.value("title", "Warfactory ImGui UI");
|
|
auto window_size = config.value("window_size", json{{"width", 1400}, {"height", 900}});
|
|
if (window_size.is_object()) {
|
|
screen_size.x = window_size.value("width", 1400);
|
|
screen_size.y = window_size.value("height", 900);
|
|
} else {
|
|
screen_size.x = 1400;
|
|
screen_size.y = 900;
|
|
}
|
|
|
|
window = glfwCreateWindow(
|
|
static_cast<int>(screen_size.x),
|
|
static_cast<int>(screen_size.y),
|
|
title.c_str(), nullptr, nullptr
|
|
);
|
|
|
|
if (!window) {
|
|
glfwTerminate();
|
|
throw std::runtime_error("Failed to create GLFW window");
|
|
}
|
|
|
|
glfwMakeContextCurrent(window);
|
|
glfwSwapInterval(1); // VSync
|
|
|
|
// Initialize ImGui
|
|
IMGUI_CHECKVERSION();
|
|
ImGui::CreateContext();
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
|
|
|
// Basic style setup
|
|
ImGui::StyleColorsDark();
|
|
|
|
// Platform/Renderer backends
|
|
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
|
ImGui_ImplOpenGL3_Init("#version 330 core");
|
|
|
|
initialized = true;
|
|
}
|
|
|
|
bool update() override {
|
|
if (!initialized || !window) return false;
|
|
|
|
if (glfwWindowShouldClose(window)) {
|
|
should_close = true;
|
|
return false;
|
|
}
|
|
|
|
// Update screen size for percentage calculations
|
|
int fb_width, fb_height;
|
|
glfwGetFramebufferSize(window, &fb_width, &fb_height);
|
|
screen_size.x = static_cast<float>(fb_width);
|
|
screen_size.y = static_cast<float>(fb_height);
|
|
|
|
frame_count++;
|
|
|
|
// Poll events
|
|
glfwPollEvents();
|
|
|
|
// Start ImGui frame
|
|
ImGui_ImplOpenGL3_NewFrame();
|
|
ImGui_ImplGlfw_NewFrame();
|
|
ImGui::NewFrame();
|
|
|
|
// Render all windows
|
|
renderAllWindows();
|
|
|
|
// Render ImGui
|
|
ImGui::Render();
|
|
|
|
// OpenGL rendering
|
|
glViewport(0, 0, static_cast<int>(screen_size.x), static_cast<int>(screen_size.y));
|
|
glClearColor(0.45f, 0.55f, 0.60f, 1.00f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
|
|
|
glfwSwapBuffers(window);
|
|
|
|
return !should_close;
|
|
}
|
|
|
|
void shutdown() override {
|
|
if (!initialized) return;
|
|
|
|
if (window) {
|
|
ImGui_ImplOpenGL3_Shutdown();
|
|
ImGui_ImplGlfw_Shutdown();
|
|
ImGui::DestroyContext();
|
|
|
|
glfwDestroyWindow(window);
|
|
glfwTerminate();
|
|
window = nullptr;
|
|
}
|
|
|
|
initialized = false;
|
|
}
|
|
|
|
private:
|
|
// ========================================
|
|
// SIZE CALCULATION HELPERS
|
|
// ========================================
|
|
|
|
/**
|
|
* @brief Parse size value - handles both pixels and percentages
|
|
*/
|
|
float parseSize(const json& size_value, float parent_size, float default_size) {
|
|
try {
|
|
if (size_value.is_number()) {
|
|
return size_value.get<float>();
|
|
}
|
|
|
|
if (size_value.is_string()) {
|
|
std::string size_str = size_value.get<std::string>();
|
|
if (!size_str.empty() && size_str.back() == '%') {
|
|
float percent = std::stof(size_str.substr(0, size_str.length() - 1));
|
|
return (percent / 100.0f) * parent_size;
|
|
} else {
|
|
// String but not percentage - try to parse as number
|
|
return std::stof(size_str);
|
|
}
|
|
}
|
|
} catch (...) {
|
|
// Any JSON or parsing error - return default
|
|
}
|
|
|
|
// Neither number nor string or error - return default
|
|
return default_size;
|
|
}
|
|
|
|
/**
|
|
* @brief Calculate effective size with hybrid constraints
|
|
*/
|
|
ImVec2 calculateEffectiveSize(const WindowInfo& win, ImVec2 parent_size) {
|
|
// Use already parsed sizes (converted in showData)
|
|
float target_width = win.size.x;
|
|
float target_height = win.size.y;
|
|
|
|
// Calculate constraint bounds
|
|
float min_width = win.min_size.x;
|
|
float min_height = win.min_size.y;
|
|
float max_width = win.max_size.x;
|
|
float max_height = win.max_size.y;
|
|
|
|
// Apply constraints (clamp)
|
|
float final_width = std::max(min_width, std::min(target_width, max_width));
|
|
float final_height = std::max(min_height, std::min(target_height, max_height));
|
|
|
|
return {final_width, final_height};
|
|
}
|
|
|
|
// ========================================
|
|
// RENDERING IMPLEMENTATION
|
|
// ========================================
|
|
|
|
void renderAllWindows() {
|
|
for (auto& [window_id, win] : windows) {
|
|
if (!win.is_open) continue;
|
|
|
|
// Calculate effective size with constraints
|
|
ImVec2 effective_size = calculateEffectiveSize(win, screen_size);
|
|
|
|
// Set window constraints
|
|
ImGui::SetNextWindowSizeConstraints(win.min_size, win.max_size);
|
|
|
|
if (win.is_floating) {
|
|
// Floating window
|
|
ImGui::SetNextWindowSize(effective_size, ImGuiCond_FirstUseEver);
|
|
if (win.position.x > 0 || win.position.y > 0) {
|
|
ImGui::SetNextWindowPos(win.position, ImGuiCond_FirstUseEver);
|
|
}
|
|
}
|
|
|
|
// Window flags
|
|
ImGuiWindowFlags flags = ImGuiWindowFlags_None;
|
|
if (!win.resizable) flags |= ImGuiWindowFlags_NoResize;
|
|
|
|
// Render window
|
|
if (ImGui::Begin(win.title.c_str(), win.closeable ? &win.is_open : nullptr, flags)) {
|
|
renderWindowContent(win);
|
|
}
|
|
ImGui::End();
|
|
}
|
|
}
|
|
|
|
void renderWindowContent(const WindowInfo& win) {
|
|
switch (win.data_type) {
|
|
case DataType::ECONOMY:
|
|
renderEconomyContent(win.content_data);
|
|
break;
|
|
case DataType::MAP:
|
|
renderMapContent(win.content_data);
|
|
break;
|
|
case DataType::INVENTORY:
|
|
renderInventoryContent(win.content_data);
|
|
break;
|
|
case DataType::CONSOLE:
|
|
renderConsoleContent(win.content_data);
|
|
break;
|
|
case DataType::PERFORMANCE:
|
|
renderPerformanceContent(win.content_data);
|
|
break;
|
|
case DataType::COMPANIES:
|
|
renderCompaniesContent(win.content_data);
|
|
break;
|
|
case DataType::ALERTS:
|
|
renderAlertsContent(win.content_data);
|
|
break;
|
|
case DataType::SETTINGS:
|
|
renderSettingsContent(win.content_data);
|
|
break;
|
|
default:
|
|
renderGenericContent(win.content_data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public:
|
|
// ========================================
|
|
// IUI INTERFACE IMPLEMENTATION - DATA DISPLAY
|
|
// ========================================
|
|
|
|
void showData(DataType dataType, const json& data) override {
|
|
// Extract window configuration
|
|
json window_config = data.value("window", json{});
|
|
json content = data.value("content", data);
|
|
|
|
// Generate ID if not provided
|
|
std::string window_id = window_config.value("id", "window_" + std::to_string(windows.size()));
|
|
|
|
// Create or update window info
|
|
WindowInfo& win = windows[window_id];
|
|
win.id = window_id;
|
|
win.title = window_config.value("title", toString(dataType));
|
|
win.data_type = dataType;
|
|
win.content_data = content;
|
|
win.is_open = true;
|
|
|
|
// Parse size configuration with percentage support
|
|
if (window_config.contains("size")) {
|
|
auto size_config = window_config["size"];
|
|
if (size_config.is_object()) {
|
|
if (size_config.contains("width")) {
|
|
auto width_val = size_config["width"];
|
|
if (width_val.is_string()) {
|
|
win.size_width_percent = width_val.get<std::string>();
|
|
win.size.x = parseSize(width_val, screen_size.x, 400);
|
|
} else if (width_val.is_number()) {
|
|
win.size.x = width_val.get<float>();
|
|
} else {
|
|
win.size.x = 400; // Default fallback
|
|
}
|
|
}
|
|
|
|
if (size_config.contains("height")) {
|
|
auto height_val = size_config["height"];
|
|
if (height_val.is_string()) {
|
|
win.size_height_percent = height_val.get<std::string>();
|
|
win.size.y = parseSize(height_val, screen_size.y, 300);
|
|
} else if (height_val.is_number()) {
|
|
win.size.y = height_val.get<float>();
|
|
} else {
|
|
win.size.y = 300; // Default fallback
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse constraints
|
|
if (window_config.contains("min_size")) {
|
|
auto min_config = window_config["min_size"];
|
|
if (min_config.is_object()) {
|
|
if (min_config.contains("width")) {
|
|
win.min_size.x = parseSize(min_config["width"], screen_size.x, 100);
|
|
} else {
|
|
win.min_size.x = 100;
|
|
}
|
|
if (min_config.contains("height")) {
|
|
win.min_size.y = parseSize(min_config["height"], screen_size.y, 100);
|
|
} else {
|
|
win.min_size.y = 100;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (window_config.contains("max_size")) {
|
|
auto max_config = window_config["max_size"];
|
|
if (max_config.is_object()) {
|
|
if (max_config.contains("width")) {
|
|
win.max_size.x = parseSize(max_config["width"], screen_size.x, 2000);
|
|
} else {
|
|
win.max_size.x = 2000;
|
|
}
|
|
if (max_config.contains("height")) {
|
|
win.max_size.y = parseSize(max_config["height"], screen_size.y, 1500);
|
|
} else {
|
|
win.max_size.y = 1500;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse other properties
|
|
win.is_floating = window_config.value("floating", false);
|
|
win.resizable = window_config.value("resizable", true);
|
|
win.closeable = window_config.value("closeable", true);
|
|
|
|
if (window_config.contains("position")) {
|
|
auto pos = window_config["position"];
|
|
if (pos.is_object()) {
|
|
win.position.x = pos.value("x", 0);
|
|
win.position.y = pos.value("y", 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void showDataCustom(const std::string& customType, const json& data) override {
|
|
// Treat as generic data with custom type in title
|
|
json modified_data = data;
|
|
if (!modified_data.contains("window")) {
|
|
modified_data["window"] = json{};
|
|
}
|
|
if (!modified_data["window"].contains("title")) {
|
|
modified_data["window"]["title"] = customType;
|
|
}
|
|
|
|
showData(DataType::CUSTOM, modified_data);
|
|
}
|
|
|
|
// ========================================
|
|
// IUI INTERFACE IMPLEMENTATION - REQUESTS & EVENTS
|
|
// ========================================
|
|
|
|
void onRequest(RequestType requestType, std::function<void(const json&)> callback) override;
|
|
void onRequestCustom(const std::string& customType, std::function<void(const json&)> callback) override;
|
|
void showEvent(EventLevel level, const std::string& message) override;
|
|
|
|
// ========================================
|
|
// WINDOW MANAGEMENT IMPLEMENTATION
|
|
// ========================================
|
|
|
|
void createDock(const std::string& dockId, DockType type, DockPosition position, const json& config = {}) override;
|
|
void createSplit(const std::string& dockId, Orientation orientation, const json& config = {}) override;
|
|
void closeWindow(const std::string& windowId) override;
|
|
void focusWindow(const std::string& windowId) override;
|
|
|
|
// ========================================
|
|
// STATE MANAGEMENT
|
|
// ========================================
|
|
|
|
json getState() const override;
|
|
void setState(const json& state) override;
|
|
|
|
private:
|
|
// ========================================
|
|
// CONTENT RENDERING IMPLEMENTATIONS
|
|
// ========================================
|
|
|
|
void renderEconomyContent(const json& content);
|
|
void renderMapContent(const json& content);
|
|
void renderInventoryContent(const json& content);
|
|
void renderConsoleContent(const json& content);
|
|
void renderPerformanceContent(const json& content);
|
|
void renderCompaniesContent(const json& content);
|
|
void renderAlertsContent(const json& content);
|
|
void renderSettingsContent(const json& content);
|
|
void renderGenericContent(const json& content);
|
|
void renderLogConsole();
|
|
};
|
|
|
|
} // namespace warfactory
|