Files
EVEngine/src/modules/ui/UISystem.cpp
T
西风逍遥游 f0b0527f9f feat: enhance map module with multi-layer support and object management
- Implemented multi-layer tilemap functionality, allowing for the reading and saving of Tiled JSON formats.
- Added support for various tile projections (orthogonal, isometric, staggered, hexagonal) and depth calculations.
- Introduced object layer parsing and caching for improved map object management.
- Updated the rendering system to unify 2D depth queues and enhance sprite handling.
- Added new methods for accessing object properties, including position and dimensions.

This update significantly improves the map module's capabilities and integration with the rendering system.
2026-08-08 00:26:58 +08:00

284 lines
9.6 KiB
C++

#include "ui/UISystem.h"
#include "ui/Theme.h"
#include <imgui.h>
#include <algorithm>
#include <cstring>
#include <vector>
namespace eve::ui {
namespace {
std::vector<UIEvent> g_pending;
std::vector<UIClick> g_clicks;
std::vector<UIChange> g_changes;
void pushPending(UIHost *host, const UINode &n, const char *kind, uint32_t handlerIndex,
bool toggleValue = false, float floatValue = 0.f, std::string textValue = {}) {
UIEvent ev;
ev.host = host;
ev.hostName = host ? host->meta()->name : "";
ev.nodeId = n.id;
ev.kind = kind;
ev.handlerIndex = handlerIndex;
ev.toggleValue = toggleValue;
ev.floatValue = floatValue;
ev.textValue = std::move(textValue);
g_pending.push_back(std::move(ev));
}
void walk(UIHost *host, UIHost::Tree *tree, int index) {
if (index < 0 || index >= int(tree->nodes.size())) return;
UINode &n = tree->nodes[size_t(index)];
if (!n.visible) {
if (n.nextSibling >= 0) walk(host, tree, n.nextSibling);
return;
}
switch (n.type) {
case NodeType::Window: {
std::string title = n.text.empty() ? "Window" : n.text;
if (host && !host->meta()->name.empty()) title = host->meta()->name + "/" + title;
const bool modal = host && host->meta()->modal;
ImGuiWindowFlags flags = 0;
if (host && host->meta()->hasPos) {
ImGui::SetNextWindowPos(ImVec2(host->meta()->posX, host->meta()->posY), ImGuiCond_Always,
ImVec2(host->meta()->pivotX, host->meta()->pivotY));
flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize;
}
if (host && host->meta()->overlay) {
flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_AlwaysAutoResize;
ImGui::SetNextWindowBgAlpha(0.4f);
}
if (modal) {
ImGui::OpenPopup(title.c_str());
if (ImGui::BeginPopupModal(title.c_str(), nullptr,
ImGuiWindowFlags_AlwaysAutoResize | flags)) {
if (n.firstChild >= 0) walk(host, tree, n.firstChild);
ImGui::EndPopup();
}
} else {
ImGui::Begin(title.c_str(), nullptr, flags);
if (n.firstChild >= 0) walk(host, tree, n.firstChild);
ImGui::End();
}
break;
}
case NodeType::Text:
ImGui::TextUnformatted(n.text.c_str());
break;
case NodeType::Button:
if (ImGui::Button(n.text.empty() ? "Button" : n.text.c_str())) {
pushPending(host, n, "click", n.handlerClick);
}
break;
case NodeType::SameLine:
ImGui::SameLine();
break;
case NodeType::Group:
ImGui::BeginGroup();
if (n.firstChild >= 0) walk(host, tree, n.firstChild);
ImGui::EndGroup();
break;
case NodeType::Separator:
ImGui::Separator();
break;
case NodeType::Checkbox: {
bool checked = n.checked;
if (ImGui::Checkbox(n.text.empty() ? "Check" : n.text.c_str(), &checked)) {
n.checked = checked;
pushPending(host, n, "toggle", n.handlerToggle, checked);
}
break;
}
case NodeType::Slider: {
float v = n.value;
const char *label = n.text.empty() ? "Slider" : n.text.c_str();
if (ImGui::SliderFloat(label, &v, n.minValue, n.maxValue)) {
n.value = v;
pushPending(host, n, "value", n.handlerValue, false, v);
}
break;
}
case NodeType::Progress: {
const char *overlay = n.text.empty() ? nullptr : n.text.c_str();
ImGui::ProgressBar(n.value, ImVec2(-1.f, 0.f), overlay);
break;
}
case NodeType::InputText: {
char buf[256];
std::memset(buf, 0, sizeof(buf));
if (!n.valueText.empty()) {
std::strncpy(buf, n.valueText.c_str(), sizeof(buf) - 1);
}
const char *label = n.text.empty() ? "Input" : n.text.c_str();
if (ImGui::InputText(label, buf, sizeof(buf))) {
n.valueText = buf;
pushPending(host, n, "text", n.handlerText, false, 0.f, n.valueText);
}
break;
}
case NodeType::CollapsingHeader: {
ImGuiTreeNodeFlags flags = n.open ? ImGuiTreeNodeFlags_DefaultOpen : 0;
const char *label = n.text.empty() ? "Section" : n.text.c_str();
if (ImGui::CollapsingHeader(label, flags)) {
if (n.firstChild >= 0) walk(host, tree, n.firstChild);
}
break;
}
case NodeType::Child: {
ImVec2 size(n.sizeX, n.sizeY);
const char *cid = n.id.empty() ? "child" : n.id.c_str();
if (ImGui::BeginChild(cid, size, true)) {
if (n.firstChild >= 0) walk(host, tree, n.firstChild);
}
ImGui::EndChild();
break;
}
}
if (n.nextSibling >= 0) walk(host, tree, n.nextSibling);
}
} // namespace
std::vector<UIEvent> &UISystem::pendingEvents() { return g_pending; }
std::vector<UIClick> &UISystem::clickQueue() { return g_clicks; }
std::vector<UIChange> &UISystem::changeQueue() { return g_changes; }
UIHost *UISystem::findHost(const std::string &name) {
if (name.empty()) return nullptr;
if (ecs::current()->getManager<UIHost>() == nullptr) return nullptr;
auto view = ecs::View<UIHost, UIHost::Meta, UIHost::Tree>();
for (auto it = view.begin(); it != view.end(); ++it) {
auto [meta, tree] = *it;
(void)tree;
if (meta->name == name) return meta->entity;
}
return nullptr;
}
UIHost *UISystem::findHostByOwner(uint32_t ownerId) {
if (ownerId == 0) return nullptr;
if (ecs::current()->getManager<UIHost>() == nullptr) return nullptr;
auto view = ecs::View<UIHost, UIHost::Meta, UIHost::Tree>();
for (auto it = view.begin(); it != view.end(); ++it) {
auto [meta, tree] = *it;
(void)tree;
if (meta->ownerId == ownerId) return meta->entity;
}
return nullptr;
}
void UISystem::render() {
if (ecs::current()->getManager<UIHost>() == nullptr) return;
applyThemeToImGui(globalTheme());
struct Item {
UIHost *host;
UIHost::Meta *meta;
UIHost::Tree *tree;
};
std::vector<Item> items;
auto view = ecs::View<UIHost, UIHost::Meta, UIHost::Tree>();
for (auto it = view.begin(); it != view.end(); ++it) {
auto [meta, tree] = *it;
if (!meta->visible) continue;
UIHost *host = meta->entity;
if (!host) continue;
items.push_back(Item{host, meta, tree});
}
std::stable_sort(items.begin(), items.end(),
[](const Item &a, const Item &b) { return a.meta->layer < b.meta->layer; });
for (auto &item : items) {
item.tree->dirty = false;
if (item.tree->root >= 0) walk(item.host, item.tree, item.tree->root);
}
}
void UISystem::dispatchEvents() {
auto events = std::move(g_pending);
g_pending.clear();
for (auto &ev : events) {
if (!ev.host) continue;
if (ev.kind == "click" && !ev.nodeId.empty()) {
UIClick c;
c.hostName = ev.hostName.empty() ? ev.host->meta()->name : ev.hostName;
c.nodeId = ev.nodeId;
g_clicks.push_back(std::move(c));
}
if ((ev.kind == "toggle" || ev.kind == "value" || ev.kind == "text") && !ev.nodeId.empty()) {
UIChange ch;
ch.hostName = ev.hostName.empty() ? ev.host->meta()->name : ev.hostName;
ch.nodeId = ev.nodeId;
ch.kind = ev.kind;
g_changes.push_back(std::move(ch));
}
if (ev.kind == "toggle" && ev.handlerIndex != 0) {
auto t = ev.host->tree();
size_t idx = size_t(ev.handlerIndex - 1);
if (idx < t->toggleHandlers.size() && t->toggleHandlers[idx])
t->toggleHandlers[idx](ev.toggleValue);
continue;
}
if (ev.kind == "value" && ev.handlerIndex != 0) {
auto t = ev.host->tree();
size_t idx = size_t(ev.handlerIndex - 1);
if (idx < t->valueHandlers.size() && t->valueHandlers[idx])
t->valueHandlers[idx](ev.floatValue);
continue;
}
if (ev.kind == "text" && ev.handlerIndex != 0) {
auto t = ev.host->tree();
size_t idx = size_t(ev.handlerIndex - 1);
if (idx < t->textHandlers.size() && t->textHandlers[idx])
t->textHandlers[idx](ev.textValue);
continue;
}
if (ev.handlerIndex == 0) continue;
auto t = ev.host->tree();
size_t idx = size_t(ev.handlerIndex - 1);
if (idx >= t->clickHandlers.size()) continue;
if (t->clickHandlers[idx]) t->clickHandlers[idx]();
}
}
std::string UISystem::consumeClick() {
if (g_clicks.empty()) return {};
UIClick c = std::move(g_clicks.front());
g_clicks.erase(g_clicks.begin());
if (c.hostName.empty()) return c.nodeId;
return c.hostName + "/" + c.nodeId;
}
std::string UISystem::consumeClickFor(const std::string &hostName) {
for (auto it = g_clicks.begin(); it != g_clicks.end(); ++it) {
if (it->hostName != hostName) continue;
std::string id = std::move(it->nodeId);
g_clicks.erase(it);
return id;
}
return {};
}
std::string UISystem::consumeChange() {
if (g_changes.empty()) return {};
UIChange c = std::move(g_changes.front());
g_changes.erase(g_changes.begin());
if (c.hostName.empty()) return c.nodeId;
return c.hostName + "/" + c.nodeId;
}
} // namespace eve::ui