#include "UiManager.h" #include "Utils.h" #include #include #include namespace ZL { using json = nlohmann::json; void UiButton::buildMesh() { mesh.data.PositionData.clear(); mesh.data.TexCoordData.clear(); float x0 = rect.x; float y0 = rect.y; float x1 = rect.x + rect.w; float y1 = rect.y + rect.h; mesh.data.PositionData.push_back({ x0, y0, 0 }); mesh.data.TexCoordData.push_back({ 0, 0 }); mesh.data.PositionData.push_back({ x0, y1, 0 }); mesh.data.TexCoordData.push_back({ 0, 1 }); mesh.data.PositionData.push_back({ x1, y1, 0 }); mesh.data.TexCoordData.push_back({ 1, 1 }); mesh.data.PositionData.push_back({ x0, y0, 0 }); mesh.data.TexCoordData.push_back({ 0, 0 }); mesh.data.PositionData.push_back({ x1, y1, 0 }); mesh.data.TexCoordData.push_back({ 1, 1 }); mesh.data.PositionData.push_back({ x1, y0, 0 }); mesh.data.TexCoordData.push_back({ 1, 0 }); mesh.RefreshVBO(); } void UiButton::draw(Renderer& renderer) const { if (!texNormal) return; const std::shared_ptr* tex = &texNormal; switch (state) { case ButtonState::Normal: tex = &texNormal; break; case ButtonState::Hover: tex = &texHover; break; case ButtonState::Pressed: tex = &texPressed; break; } if (!(*tex)) return; static const std::string vPositionName = "vPosition"; static const std::string vTexCoordName = "vTexCoord"; static const std::string textureUniformName = "Texture"; renderer.RenderUniform1i(textureUniformName, 0); renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vTexCoordName); glBindTexture(GL_TEXTURE_2D, (*tex)->getTexID()); renderer.DrawVertexRenderStruct(mesh); renderer.DisableVertexAttribArray(vPositionName); renderer.DisableVertexAttribArray(vTexCoordName); } // UiManager implementation void UiManager::loadFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile) { std::ifstream in(path); if (!in.is_open()) { std::cerr << "UiManager: failed to open " << path << std::endl; throw std::runtime_error("Failed to load UI file: " + path); } json j; try { in >> j; } catch (const std::exception& e) { std::cerr << "UiManager: json parse error: " << e.what() << std::endl; throw std::runtime_error("Failed to load UI file: " + path); } if (!j.contains("root") || !j["root"].is_object()) { std::cerr << "UiManager: root node missing or invalid" << std::endl; throw std::runtime_error("Failed to load UI file: " + path); } root = parseNode(j["root"], renderer, zipFile); layoutNode(root); buttons.clear(); collectButtons(root); for (auto& b : buttons) { b->buildMesh(); } } std::shared_ptr UiManager::parseNode(const json& j, Renderer& renderer, const std::string& zipFile) { auto node = std::make_shared(); if (j.contains("type") && j["type"].is_string()) node->type = j["type"].get(); if (j.contains("name") && j["name"].is_string()) node->name = j["name"].get(); if (j.contains("x")) node->rect.x = j["x"].get(); if (j.contains("y")) node->rect.y = j["y"].get(); if (j.contains("width")) node->rect.w = j["width"].get(); if (j.contains("height")) node->rect.h = j["height"].get(); if (j.contains("orientation") && j["orientation"].is_string()) node->orientation = j["orientation"].get(); if (j.contains("spacing")) node->spacing = j["spacing"].get(); if (node->type == "Button") { auto btn = std::make_shared(); btn->name = node->name; btn->rect = node->rect; if (!j.contains("textures") || !j["textures"].is_object()) { std::cerr << "UiManager: Button '" << btn->name << "' missing textures" << std::endl; throw std::runtime_error("UI button textures missing"); } auto t = j["textures"]; auto loadTex = [&](const std::string& key)->std::shared_ptr { if (!t.contains(key) || !t[key].is_string()) return nullptr; std::string path = t[key].get(); try { auto data = CreateTextureDataFromPng(path.c_str(), zipFile.c_str()); return std::make_shared(data); } catch (const std::exception& e) { std::cerr << "UiManager: failed load texture " << path << " : " << e.what() << std::endl; throw std::runtime_error("UI texture load failed: " + path); } }; btn->texNormal = loadTex("normal"); btn->texHover = loadTex("hover"); btn->texPressed = loadTex("pressed"); node->button = btn; } // parse children if (j.contains("children") && j["children"].is_array()) { for (const auto& ch : j["children"]) { node->children.push_back(parseNode(ch, renderer, zipFile)); } } return node; } void UiManager::layoutNode(const std::shared_ptr& node) { for (auto& child : node->children) { child->rect.x += node->rect.x; child->rect.y += node->rect.y; } if (node->type == "LinearLayout") { std::string orient = node->orientation; std::transform(orient.begin(), orient.end(), orient.begin(), ::tolower); float cursorX = node->rect.x; float cursorY = node->rect.y; for (auto& child : node->children) { if (orient == "horizontal") { child->rect.x = cursorX; child->rect.y = node->rect.y; cursorX += child->rect.w + node->spacing; } else { child->rect.x = node->rect.x; child->rect.y = cursorY; cursorY += child->rect.h + node->spacing; } layoutNode(child); } } else { for (auto& child : node->children) { layoutNode(child); } } } void UiManager::collectButtons(const std::shared_ptr& node) { if (node->button) { buttons.push_back(node->button); } for (auto& c : node->children) collectButtons(c); } bool UiManager::setButtonCallback(const std::string& name, std::function cb) { auto b = findButton(name); if (!b) { std::cerr << "UiManager: setButtonCallback failed, button not found: " << name << std::endl; return false; } b->onClick = std::move(cb); return true; } bool UiManager::pushMenuFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile) { MenuState prev; prev.root = root; prev.buttons = buttons; prev.pressedButton = pressedButton; prev.path = ""; try { loadFromFile(path, renderer, zipFile); menuStack.push_back(std::move(prev)); return true; } catch (const std::exception& e) { std::cerr << "UiManager: pushMenuFromFile failed to load " << path << " : " << e.what() << std::endl; return false; } } bool UiManager::popMenu() { if (menuStack.empty()) { std::cerr << "UiManager: popMenu called but menu stack is empty" << std::endl; return false; } auto s = menuStack.back(); menuStack.pop_back(); root = s.root; buttons = s.buttons; pressedButton = s.pressedButton; for (auto& b : buttons) { if (b) b->buildMesh(); } return true; } void UiManager::clearMenuStack() { menuStack.clear(); } void UiManager::draw(Renderer& renderer) { if (!root) return; renderer.PushProjectionMatrix(Environment::width, Environment::height, -1, 1); renderer.PushMatrix(); renderer.LoadIdentity(); for (const auto& b : buttons) { b->draw(renderer); } renderer.PopMatrix(); renderer.PopProjectionMatrix(); } void UiManager::onMouseMove(int x, int y) { for (auto& b : buttons) { if (b->rect.contains((float)x, (float)y)) { if (b->state != ButtonState::Pressed) b->state = ButtonState::Hover; } else { if (b->state != ButtonState::Pressed) b->state = ButtonState::Normal; } } } void UiManager::onMouseDown(int x, int y) { for (auto& b : buttons) { if (b->rect.contains((float)x, (float)y)) { b->state = ButtonState::Pressed; pressedButton = b; } else { // leave others } } } void UiManager::onMouseUp(int x, int y) { for (auto& b : buttons) { bool contains = b->rect.contains((float)x, (float)y); if (b->state == ButtonState::Pressed) { if (contains && pressedButton == b) { if (b->onClick) { b->onClick(b->name); } } b->state = contains ? ButtonState::Hover : ButtonState::Normal; } } pressedButton.reset(); } std::shared_ptr UiManager::findButton(const std::string& name) { for (auto& b : buttons) if (b->name == name) return b; return nullptr; } } // namespace ZL