#pragma once #include "IUI_Enums.h" #include #include #include #include #include #include #include #include #include #include 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 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 child_windows; }; std::map docks; // ======================================== // CALLBACKS // ======================================== std::map> request_callbacks; std::map> custom_request_callbacks; // ======================================== // MESSAGE SYSTEM // ======================================== struct LogMessage { EventLevel level; std::string message; std::chrono::steady_clock::time_point timestamp; }; std::vector 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(screen_size.x), static_cast(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(fb_width); screen_size.y = static_cast(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(screen_size.x), static_cast(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(); } if (size_value.is_string()) { std::string size_str = size_value.get(); 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(); win.size.x = parseSize(width_val, screen_size.x, 400); } else if (width_val.is_number()) { win.size.x = width_val.get(); } 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(); win.size.y = parseSize(height_val, screen_size.y, 300); } else if (height_val.is_number()) { win.size.y = height_val.get(); } 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 callback) override; void onRequestCustom(const std::string& customType, std::function 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