249 lines
7.1 KiB
C++
249 lines
7.1 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);
|
|
}
|
|
|
|
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
|