Working on messenger

This commit is contained in:
Vladislav Khorev committed 2026-06-05 12:13:02 +03:00
1 parent 9221ab68ac
commit 1fc8120ee0
25 files changed
+445 -259

No files matched your search

+201 -1
View File
@@ -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