add quest journal (QuestJournal + split UI view)
This commit is contained in:
1 parent
dbf0af2cb2
commit
d78286a9c2
13 files changed
+1148
-32
No files matched your search
+169
-3
@@ -4,12 +4,90 @@
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include "GameConstants.h"
|
||||
|
||||
namespace ZL {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
|
||||
static int countWrappedLines(const std::string& text) {
|
||||
if (text.empty()) return 0;
|
||||
int lines = 1;
|
||||
for (char c : text) {
|
||||
if (c == '\n') ++lines;
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
static std::string limitLines(const std::string& text, int maxLines) {
|
||||
if (maxLines <= 0) return text;
|
||||
std::string out;
|
||||
int lines = 1;
|
||||
for (char c : text) {
|
||||
if (c == '\n') {
|
||||
if (lines >= maxLines) {
|
||||
out += "...";
|
||||
return out;
|
||||
}
|
||||
++lines;
|
||||
}
|
||||
out.push_back(c);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::string wrapTextByPixels(const std::string& input, const TextRenderer& textRenderer, float maxWidthPx, float scale, int maxLines = 0) {
|
||||
if (input.empty() || maxWidthPx <= 1.0f) return input;
|
||||
|
||||
std::string output;
|
||||
std::string currentLine;
|
||||
std::string currentWord;
|
||||
auto flushLine = [&]() {
|
||||
if (!currentLine.empty()) {
|
||||
if (!output.empty()) output.push_back('\n');
|
||||
output += currentLine;
|
||||
currentLine.clear();
|
||||
}
|
||||
};
|
||||
auto pushWord = [&](const std::string& word) {
|
||||
if (word.empty()) return;
|
||||
if (currentLine.empty()) {
|
||||
currentLine = word;
|
||||
return;
|
||||
}
|
||||
const std::string candidate = currentLine + " " + word;
|
||||
if (textRenderer.measureTextWidth(candidate, scale) <= maxWidthPx) {
|
||||
currentLine = candidate;
|
||||
} else {
|
||||
flushLine();
|
||||
currentLine = word;
|
||||
}
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < input.size(); ++i) {
|
||||
const char ch = input[i];
|
||||
if (ch == '\n') {
|
||||
pushWord(currentWord);
|
||||
currentWord.clear();
|
||||
flushLine();
|
||||
continue;
|
||||
}
|
||||
if (ch == ' ' || ch == '\t' || ch == '\r') {
|
||||
pushWord(currentWord);
|
||||
currentWord.clear();
|
||||
continue;
|
||||
}
|
||||
currentWord.push_back(ch);
|
||||
}
|
||||
|
||||
pushWord(currentWord);
|
||||
flushLine();
|
||||
|
||||
return limitLines(output, maxLines);
|
||||
}
|
||||
|
||||
static float applyEasing(const std::string& easing, float t) {
|
||||
if (easing == "easein") {
|
||||
return t * t;
|
||||
@@ -130,14 +208,60 @@ namespace ZL {
|
||||
|
||||
|
||||
// Draw text on top (uses absolute coords, add anim offset manually)
|
||||
// use left padding, which is required for inventory/quest lists.
|
||||
if (textRenderer && !text.empty()) {
|
||||
float cx = rect.x + rect.w / 2.0f + animOffsetX;
|
||||
float cy = rect.y + rect.h / 2.0f + animOffsetY;
|
||||
textRenderer->drawText(text, cx, cy, 1.0f, textCentered, color);
|
||||
float tx = rect.x + rect.w / 2.0f + animOffsetX;
|
||||
if (!textCentered) {
|
||||
tx = rect.x + textPaddingX + animOffsetX;
|
||||
}
|
||||
const float ty = rect.y + rect.h * 0.5f + textPaddingY + animOffsetY;
|
||||
textRenderer->drawText(text, tx, ty, 1.0f, textCentered, color);
|
||||
}
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
}
|
||||
|
||||
void UiTextView::draw(Renderer& renderer) const {
|
||||
(void)renderer;
|
||||
if (!textRenderer || text.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float scale = 1.0f;
|
||||
|
||||
// Backward compatibility:
|
||||
// Old UI files, including the original inventory panel, positioned TextView
|
||||
// around the rect center. If a TextView does not explicitly request wrapping,
|
||||
// top alignment, padding or line limiting, keep that old behavior.
|
||||
const bool usesModernRectText = wrap || topAligned || paddingX != 0.0f || paddingY != 0.0f || maxLines > 0;
|
||||
if (!usesModernRectText) {
|
||||
textRenderer->drawText(
|
||||
text,
|
||||
rect.x + rect.w * 0.5f,
|
||||
rect.y + rect.h * 0.5f,
|
||||
scale,
|
||||
centered,
|
||||
color
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const float availableWidth = max(1.0f, rect.w - paddingX * 2.0f);
|
||||
const std::string finalText = wrap
|
||||
? wrapTextByPixels(text, *textRenderer, availableWidth, scale, maxLines)
|
||||
: limitLines(text, maxLines);
|
||||
|
||||
float tx = centered ? rect.x + rect.w * 0.5f : rect.x + paddingX;
|
||||
float ty = rect.y + rect.h * 0.5f;
|
||||
|
||||
if (topAligned) {
|
||||
// TextRenderer expects a baseline position. This offset places the first
|
||||
// visible line close to the top inside the TextView rectangle.
|
||||
ty = rect.y + rect.h - paddingY - static_cast<float>(fontSize);
|
||||
}
|
||||
|
||||
textRenderer->drawText(finalText, tx, ty, scale, centered, color);
|
||||
}
|
||||
|
||||
void UiSlider::buildTrackMesh() {
|
||||
trackMesh.data.PositionData.clear();
|
||||
trackMesh.data.TexCoordData.clear();
|
||||
@@ -490,6 +614,8 @@ namespace ZL {
|
||||
if (j.contains("fontPath")) tb->fontPath = j["fontPath"].get<std::string>();
|
||||
if (j.contains("fontSize")) tb->fontSize = j["fontSize"].get<int>();
|
||||
if (j.contains("textCentered")) tb->textCentered = j["textCentered"].get<bool>();
|
||||
if (j.contains("textPaddingX")) tb->textPaddingX = j["textPaddingX"].get<float>();
|
||||
if (j.contains("textPaddingY")) tb->textPaddingY = j["textPaddingY"].get<float>();
|
||||
if (j.contains("color") && j["color"].is_array() && j["color"].size() == 4) {
|
||||
for (int i = 0; i < 4; ++i) tb->color[i] = j["color"][i].get<float>();
|
||||
}
|
||||
@@ -573,6 +699,11 @@ namespace ZL {
|
||||
}
|
||||
}
|
||||
if (j.contains("centered")) tv->centered = j["centered"].get<bool>();
|
||||
if (j.contains("wrap")) tv->wrap = j["wrap"].get<bool>();
|
||||
if (j.contains("topAligned")) tv->topAligned = j["topAligned"].get<bool>();
|
||||
if (j.contains("paddingX")) tv->paddingX = j["paddingX"].get<float>();
|
||||
if (j.contains("paddingY")) tv->paddingY = j["paddingY"].get<float>();
|
||||
if (j.contains("maxLines")) tv->maxLines = j["maxLines"].get<int>();
|
||||
|
||||
tv->textRenderer = std::make_unique<TextRenderer>();
|
||||
if (!tv->textRenderer->init(renderer, tv->fontPath, tv->fontSize, zipFile)) {
|
||||
@@ -675,6 +806,25 @@ namespace ZL {
|
||||
replaceRoot(newRoot);
|
||||
}
|
||||
|
||||
void UiManager::appendFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile) {
|
||||
std::shared_ptr<UiNode> extraRoot = loadUiFromFile(path, renderer, zipFile);
|
||||
if (!extraRoot) {
|
||||
std::cerr << "UiManager: appendFromFile failed: " << path << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!root) {
|
||||
replaceRoot(extraRoot);
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& child : extraRoot->children) {
|
||||
root->children.push_back(child);
|
||||
}
|
||||
|
||||
replaceRoot(root);
|
||||
}
|
||||
|
||||
|
||||
void UiManager::layoutNode(const std::shared_ptr<UiNode>& node, float parentX, float parentY, float parentW, float parentH, float finalLocalX, float finalLocalY) {
|
||||
|
||||
@@ -1640,6 +1790,15 @@ namespace ZL {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UiManager::setTextColor(const std::string& name, const std::array<float, 4>& color) {
|
||||
auto tv = findTextView(name);
|
||||
if (!tv) {
|
||||
return false;
|
||||
}
|
||||
tv->color = color;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<UiTextButton> UiManager::findTextButton(const std::string& name) {
|
||||
for (auto& tb : textButtons) if (tb->name == name) return tb;
|
||||
return nullptr;
|
||||
@@ -1672,6 +1831,13 @@ namespace ZL {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UiManager::setTextButtonColor(const std::string& name, const std::array<float, 4>& color) {
|
||||
auto tb = findTextButton(name);
|
||||
if (!tb) return false;
|
||||
tb->color = color;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<UiNode> UiManager::findNode(const std::string& name) {
|
||||
if (!root) return nullptr;
|
||||
return findNodeByName(root, name);
|
||||
|
||||
Reference in new issue
Block a user