space-game001/UiManager.cpp
2025-12-22 15:40:45 +06:00

300 lines
8.3 KiB
C++

#include "UiManager.h"
#include "Utils.h"
#include <fstream>
#include <iostream>
#include <algorithm>
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<Texture>* 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<UiNode> UiManager::parseNode(const json& j, Renderer& renderer, const std::string& zipFile) {
auto node = std::make_shared<UiNode>();
if (j.contains("type") && j["type"].is_string()) node->type = j["type"].get<std::string>();
if (j.contains("name") && j["name"].is_string()) node->name = j["name"].get<std::string>();
if (j.contains("x")) node->rect.x = j["x"].get<float>();
if (j.contains("y")) node->rect.y = j["y"].get<float>();
if (j.contains("width")) node->rect.w = j["width"].get<float>();
if (j.contains("height")) node->rect.h = j["height"].get<float>();
if (j.contains("orientation") && j["orientation"].is_string()) node->orientation = j["orientation"].get<std::string>();
if (j.contains("spacing")) node->spacing = j["spacing"].get<float>();
if (node->type == "Button") {
auto btn = std::make_shared<UiButton>();
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<Texture> {
if (!t.contains(key) || !t[key].is_string()) return nullptr;
std::string path = t[key].get<std::string>();
try {
auto data = CreateTextureDataFromPng(path.c_str(), zipFile.c_str());
return std::make_shared<Texture>(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<UiNode>& 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<UiNode>& 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<void(const std::string&)> 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<UiButton> UiManager::findButton(const std::string& name) {
for (auto& b : buttons) if (b->name == name) return b;
return nullptr;
}
} // namespace ZL