Working on messenger
This commit is contained in:
1 parent
9221ab68ac
commit
1fc8120ee0
25 files changed
+445
-259
No files matched your search
+201
-1
@@ -824,6 +824,107 @@ namespace ZL {
|
||||
return root;
|
||||
}
|
||||
|
||||
// ---- UiChatBubble helpers ----
|
||||
|
||||
static void buildQuadMesh(VertexRenderStruct& mesh, float x0, float y0, float x1, float y1) {
|
||||
mesh.data.PositionData.clear();
|
||||
mesh.data.TexCoordData.clear();
|
||||
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();
|
||||
}
|
||||
|
||||
float UiChatBubble::computeHeight() const {
|
||||
if (!textRenderer) return CORNER_SIZE * 2 + PAD_Y * 2 + static_cast<float>(fontSize);
|
||||
const float textW = MAX_WIDTH - CORNER_SIZE * 2.0f - PAD_X * 2.0f;
|
||||
const std::string wrapped = wrapTextByPixels(text, *textRenderer, textW, 1.0f);
|
||||
int lines = 1;
|
||||
for (char c : wrapped) if (c == '\n') ++lines;
|
||||
return CORNER_SIZE * 2.0f + PAD_Y * 2.0f + lines * static_cast<float>(fontSize) * 1.25f;
|
||||
}
|
||||
|
||||
void UiChatBubble::buildMeshes() {
|
||||
const float x = rect.x;
|
||||
const float y = rect.y;
|
||||
const float w = rect.w;
|
||||
const float h = rect.h;
|
||||
const float cs = CORNER_SIZE;
|
||||
|
||||
const float OFFSET = 0.5f;
|
||||
|
||||
// 1. Middle center (full width, between top and bottom corner rows)
|
||||
buildQuadMesh(meshMiddle, x, y + cs- OFFSET, x + w, y + h - cs +OFFSET);
|
||||
// 2. Top strip (between corners)
|
||||
buildQuadMesh(meshTopStrip, x + cs- OFFSET, y + h - cs, x + w - cs+ OFFSET, y + h);
|
||||
// 3. Bottom strip
|
||||
buildQuadMesh(meshBotStrip, x + cs- OFFSET, y, x + w - cs+ OFFSET, y + cs);
|
||||
// Corners
|
||||
buildQuadMesh(meshCornerLT, x, y + h - cs-OFFSET, x + cs, y + h);
|
||||
buildQuadMesh(meshCornerRT, x + w - cs, y + h - cs - OFFSET, x + w, y + h);
|
||||
buildQuadMesh(meshCornerLB, x, y, x + cs, y + cs);
|
||||
buildQuadMesh(meshCornerRB, x + w - cs, y, x + w, y + cs);
|
||||
}
|
||||
|
||||
void UiChatBubble::draw(Renderer& renderer, float nodeScaleX, float nodeScaleY) const {
|
||||
renderer.RenderUniform1i(textureUniformName, 0);
|
||||
|
||||
|
||||
// Corners with blending (semi-transparent rounded corners)
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
auto drawCorner = [&](const std::shared_ptr<Texture>& tex, const VertexRenderStruct& mesh) {
|
||||
if (tex) {
|
||||
glBindTexture(GL_TEXTURE_2D, tex->getTexID());
|
||||
renderer.DrawVertexRenderStruct(mesh);
|
||||
}
|
||||
};
|
||||
drawCorner(texCornerLT, meshCornerLT);
|
||||
drawCorner(texCornerRT, meshCornerRT);
|
||||
drawCorner(texCornerLB, meshCornerLB);
|
||||
drawCorner(texCornerRB, meshCornerRB);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
// Center parts (no blending needed — opaque fill)
|
||||
if (texCenter) {
|
||||
glBindTexture(GL_TEXTURE_2D, texCenter->getTexID());
|
||||
renderer.DrawVertexRenderStruct(meshMiddle);
|
||||
renderer.DrawVertexRenderStruct(meshTopStrip);
|
||||
renderer.DrawVertexRenderStruct(meshBotStrip);
|
||||
}
|
||||
|
||||
// Text — position is transformed around the bubble centre to match the renderer's
|
||||
// scale matrix (which scales meshes around the same centre point).
|
||||
if (textRenderer && !text.empty()) {
|
||||
const float textW = rect.w - CORNER_SIZE * 2.0f - PAD_X * 2.0f;
|
||||
const std::string wrapped = wrapTextByPixels(text, *textRenderer, textW, 1.0f);
|
||||
|
||||
const float tx0 = rect.x + CORNER_SIZE + PAD_X;
|
||||
const float ty0 = rect.y + rect.h - CORNER_SIZE - PAD_Y - static_cast<float>(fontSize);
|
||||
|
||||
// Scale around bubble centre, matching how the renderer's ScaleMatrix works
|
||||
const float cx = rect.x + rect.w * 0.5f;
|
||||
const float cy = rect.y + rect.h * 0.5f;
|
||||
const float tx = cx + (tx0 - cx) * nodeScaleX;
|
||||
const float ty = cy + (ty0 - cy) * nodeScaleY;
|
||||
|
||||
textRenderer->drawText(wrapped, tx, ty, nodeScaleX, false, { 1.f, 1.f, 1.f, 1.f });
|
||||
}
|
||||
}
|
||||
|
||||
// ---- end UiChatBubble helpers ----
|
||||
|
||||
void UiManager::replaceRoot(std::shared_ptr<UiNode> newRoot) {
|
||||
root = newRoot;
|
||||
layoutNode(
|
||||
@@ -841,6 +942,7 @@ namespace ZL {
|
||||
textViews.clear();
|
||||
textFields.clear();
|
||||
staticImages.clear();
|
||||
chatBubbles.clear();
|
||||
pulsingNodes.clear();
|
||||
popInNodes.clear();
|
||||
collectButtonsAndSliders(root);
|
||||
@@ -1052,6 +1154,12 @@ namespace ZL {
|
||||
node->staticImage->rect = node->screenRect;
|
||||
node->staticImage->buildMesh();
|
||||
}
|
||||
|
||||
// 6. Chat bubble
|
||||
if (node->chatBubble) {
|
||||
node->chatBubble->rect = node->screenRect;
|
||||
node->chatBubble->buildMeshes();
|
||||
}
|
||||
}
|
||||
|
||||
void UiManager::updateAllLayouts() {
|
||||
@@ -1106,6 +1214,9 @@ namespace ZL {
|
||||
if (node->pulseEnabled && node->staticImage) {
|
||||
pulsingNodes.push_back(node);
|
||||
}
|
||||
if (node->chatBubble) {
|
||||
chatBubbles.push_back(node->chatBubble);
|
||||
}
|
||||
for (auto& c : node->children) collectButtonsAndSliders(c);
|
||||
}
|
||||
|
||||
@@ -1215,6 +1326,7 @@ namespace ZL {
|
||||
prev.textViews = textViews;
|
||||
prev.textFields = textFields;
|
||||
prev.staticImages = staticImages;
|
||||
prev.chatBubbles = chatBubbles;
|
||||
prev.pulsingNodes = pulsingNodes;
|
||||
prev.pressedButtons = pressedButtons;
|
||||
prev.pressedTextButtons = pressedTextButtons;
|
||||
@@ -1280,6 +1392,7 @@ namespace ZL {
|
||||
textViews = s.textViews;
|
||||
textFields = s.textFields;
|
||||
staticImages = s.staticImages;
|
||||
chatBubbles = s.chatBubbles;
|
||||
pulsingNodes = s.pulsingNodes;
|
||||
popInNodes = s.popInNodes;
|
||||
pressedButtons = s.pressedButtons;
|
||||
@@ -1347,7 +1460,12 @@ namespace ZL {
|
||||
if (node->staticImage) {
|
||||
node->staticImage->draw(renderer);
|
||||
}
|
||||
|
||||
|
||||
// 1b. Chat bubble (composite widget)
|
||||
if (node->chatBubble) {
|
||||
node->chatBubble->draw(renderer, node->scaleX, node->scaleY);
|
||||
}
|
||||
|
||||
// 2. Потом кнопки
|
||||
if (node->button) {
|
||||
node->button->draw(renderer);
|
||||
@@ -1994,4 +2112,86 @@ namespace ZL {
|
||||
if (!node) return false;
|
||||
return node->visible;
|
||||
}
|
||||
|
||||
std::string UiManager::addChatBubble(
|
||||
const std::string& parentNodeName,
|
||||
const std::string& text, bool incoming,
|
||||
std::shared_ptr<Texture> texCenter,
|
||||
std::shared_ptr<Texture> texLT, std::shared_ptr<Texture> texLB,
|
||||
std::shared_ptr<Texture> texRT, std::shared_ptr<Texture> texRB,
|
||||
Renderer& renderer, const std::string& fontPath, int fontSize,
|
||||
const std::string& zipFile)
|
||||
{
|
||||
if (!root) return "";
|
||||
auto parent = findNodeByName(root, parentNodeName);
|
||||
if (!parent) {
|
||||
std::cerr << "UiManager::addChatBubble: parent '" << parentNodeName << "' not found\n";
|
||||
return "";
|
||||
}
|
||||
|
||||
static int bubbleCounter = 0;
|
||||
const std::string nodeName = "chat_bubble_" + std::to_string(bubbleCounter++);
|
||||
|
||||
// Build the chat bubble component
|
||||
auto bubble = std::make_shared<UiChatBubble>();
|
||||
bubble->name = nodeName;
|
||||
bubble->text = text;
|
||||
bubble->incoming = incoming;
|
||||
bubble->fontSize = fontSize;
|
||||
bubble->texCenter = texCenter;
|
||||
bubble->texCornerLT = texLT;
|
||||
bubble->texCornerLB = texLB;
|
||||
bubble->texCornerRT = texRT;
|
||||
bubble->texCornerRB = texRB;
|
||||
|
||||
bubble->textRenderer = std::make_unique<TextRenderer>();
|
||||
if (!bubble->textRenderer->init(renderer, fontPath, fontSize, zipFile)) {
|
||||
std::cerr << "UiManager::addChatBubble: TextRenderer init failed\n";
|
||||
}
|
||||
|
||||
// Build the UiNode
|
||||
auto node = std::make_shared<UiNode>();
|
||||
node->name = nodeName;
|
||||
node->width = UiChatBubble::MAX_WIDTH;
|
||||
node->height = bubble->computeHeight();
|
||||
node->localX = 430.0f;
|
||||
node->localY = 0.0f;
|
||||
node->visible = true;
|
||||
node->chatBubble = bubble;
|
||||
|
||||
if (incoming) {
|
||||
node->layoutSettings.hGravity = HorizontalGravity::Left;
|
||||
} else {
|
||||
node->layoutSettings.hGravity = HorizontalGravity::Right;
|
||||
}
|
||||
// Bottom gravity means localY is used as-is for screenRect.y (no parentH transform).
|
||||
// The original static chat nodes all had "vertical_gravity": "bottom" for the same reason.
|
||||
node->layoutSettings.vGravity = VerticalGravity::Bottom;
|
||||
|
||||
parent->children.push_back(node);
|
||||
|
||||
// Re-layout and collect the new node
|
||||
updateAllLayouts();
|
||||
bubble->rect = node->screenRect;
|
||||
bubble->buildMeshes();
|
||||
chatBubbles.push_back(bubble);
|
||||
|
||||
return nodeName;
|
||||
}
|
||||
|
||||
void UiManager::clearChatBubbles(const std::string& parentNodeName) {
|
||||
if (!root) return;
|
||||
auto parent = findNodeByName(root, parentNodeName);
|
||||
if (!parent) return;
|
||||
|
||||
// Remove children that have a chatBubble component
|
||||
auto& ch = parent->children;
|
||||
ch.erase(std::remove_if(ch.begin(), ch.end(),
|
||||
[](const std::shared_ptr<UiNode>& n) { return n && n->chatBubble != nullptr; }),
|
||||
ch.end());
|
||||
|
||||
// Remove from chatBubbles collection
|
||||
chatBubbles.clear();
|
||||
}
|
||||
|
||||
} // namespace ZL
|
||||
Reference in new issue
Block a user