Working on messenger in portrait mode
This commit is contained in:
parent
0b4df8ab2d
commit
8c42179576
83
resources/w/ui/portrait_screen_phone_chat1.json
Normal file
83
resources/w/ui/portrait_screen_phone_chat1.json
Normal file
@ -0,0 +1,83 @@
|
||||
{
|
||||
"root": {
|
||||
"type": "FrameLayout",
|
||||
"name": "hud_root",
|
||||
"width": "match_parent",
|
||||
"height": "match_parent",
|
||||
"vertical_align": "center",
|
||||
"horizontal_align": "center",
|
||||
"children": [
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "phoneExitButton",
|
||||
"horizontal_gravity": "center",
|
||||
"vertical_gravity": "center",
|
||||
"y": 0,
|
||||
"width": "match_parent",
|
||||
"height": "match_parent",
|
||||
"textures": {
|
||||
"normal": "resources/transparent.png",
|
||||
"hover": "resources/transparent.png",
|
||||
"pressed": "resources/transparent.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "phoneMain",
|
||||
"horizontal_gravity": "center",
|
||||
"vertical_gravity": "center",
|
||||
"y": -60,
|
||||
"width": 882,
|
||||
"height": 1416,
|
||||
"textures": {
|
||||
"normal": "resources/w/ui/img/phone/PhoneChat001.png",
|
||||
"hover": "resources/w/ui/img/phone/PhoneChat001.png",
|
||||
"pressed": "resources/w/ui/img/phone/PhoneChat001.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "FrameLayout",
|
||||
"name": "chatMessagesContainer",
|
||||
"horizontal_gravity": "center",
|
||||
"vertical_gravity": "center",
|
||||
"width": "match_parent",
|
||||
"height": 1000
|
||||
},
|
||||
{
|
||||
"type": "StaticImage",
|
||||
"name": "chatTopCover",
|
||||
"width": 638,
|
||||
"height": 101.25,
|
||||
"x": 0,
|
||||
"y": 536,
|
||||
"horizontal_gravity": "center",
|
||||
"vertical_gravity": "center",
|
||||
"texture": "resources/w/ui/img/phone/chat_top_cover001.png"
|
||||
},
|
||||
{
|
||||
"type": "TextButton",
|
||||
"name": "chatTitleButton",
|
||||
"horizontal_gravity": "center",
|
||||
"vertical_gravity": "center",
|
||||
"x": 0.0,
|
||||
"y": 424,
|
||||
"width": 638,
|
||||
"height": 112,
|
||||
"text": "Айпери",
|
||||
"textPaddingY": 28.0,
|
||||
"textPaddingX": 216.0,
|
||||
"fontSize": 40,
|
||||
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||
"textCentered": false,
|
||||
"topAligned": true,
|
||||
"wrap": true,
|
||||
"color": [1.0, 1.0, 1.0, 1.0],
|
||||
"textures": {
|
||||
"normal": "resources/w/ui/img/phone/CharHeader001.png",
|
||||
"hover": "resources/w/ui/img/phone/CharHeader001.png",
|
||||
"pressed": "resources/w/ui/img/phone/CharHeader001.png"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
83
resources/w/ui/portrait_screen_phone_chat2.json
Normal file
83
resources/w/ui/portrait_screen_phone_chat2.json
Normal file
@ -0,0 +1,83 @@
|
||||
{
|
||||
"root": {
|
||||
"type": "FrameLayout",
|
||||
"name": "hud_root",
|
||||
"width": "match_parent",
|
||||
"height": "match_parent",
|
||||
"vertical_align": "center",
|
||||
"horizontal_align": "center",
|
||||
"children": [
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "phoneExitButton",
|
||||
"horizontal_gravity": "center",
|
||||
"vertical_gravity": "center",
|
||||
"y": 0,
|
||||
"width": "match_parent",
|
||||
"height": "match_parent",
|
||||
"textures": {
|
||||
"normal": "resources/transparent.png",
|
||||
"hover": "resources/transparent.png",
|
||||
"pressed": "resources/transparent.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "phoneMain",
|
||||
"horizontal_gravity": "center",
|
||||
"vertical_gravity": "center",
|
||||
"y": -60,
|
||||
"width": 882,
|
||||
"height": 1416,
|
||||
"textures": {
|
||||
"normal": "resources/w/ui/img/phone/PhoneChat001.png",
|
||||
"hover": "resources/w/ui/img/phone/PhoneChat001.png",
|
||||
"pressed": "resources/w/ui/img/phone/PhoneChat001.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "FrameLayout",
|
||||
"name": "chatMessagesContainer",
|
||||
"horizontal_gravity": "center",
|
||||
"vertical_gravity": "center",
|
||||
"width": "match_parent",
|
||||
"height": 1000
|
||||
},
|
||||
{
|
||||
"type": "StaticImage",
|
||||
"name": "chatTopCover",
|
||||
"width": 638,
|
||||
"height": 101.25,
|
||||
"x": 0,
|
||||
"y": 536,
|
||||
"horizontal_gravity": "center",
|
||||
"vertical_gravity": "center",
|
||||
"texture": "resources/w/ui/img/phone/chat_top_cover001.png"
|
||||
},
|
||||
{
|
||||
"type": "TextButton",
|
||||
"name": "chatTitleButton",
|
||||
"horizontal_gravity": "center",
|
||||
"vertical_gravity": "center",
|
||||
"x": 0.0,
|
||||
"y": 424,
|
||||
"width": 638,
|
||||
"height": 112,
|
||||
"text": "Атам",
|
||||
"textPaddingY": 28.0,
|
||||
"textPaddingX": 216.0,
|
||||
"fontSize": 40,
|
||||
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||
"textCentered": false,
|
||||
"topAligned": true,
|
||||
"wrap": true,
|
||||
"color": [1.0, 1.0, 1.0, 1.0],
|
||||
"textures": {
|
||||
"normal": "resources/w/ui/img/phone/CharHeader002.png",
|
||||
"hover": "resources/w/ui/img/phone/CharHeader002.png",
|
||||
"pressed": "resources/w/ui/img/phone/CharHeader002.png"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
83
resources/w/ui/portrait_screen_phone_chat3.json
Normal file
83
resources/w/ui/portrait_screen_phone_chat3.json
Normal file
@ -0,0 +1,83 @@
|
||||
{
|
||||
"root": {
|
||||
"type": "FrameLayout",
|
||||
"name": "hud_root",
|
||||
"width": "match_parent",
|
||||
"height": "match_parent",
|
||||
"vertical_align": "center",
|
||||
"horizontal_align": "center",
|
||||
"children": [
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "phoneExitButton",
|
||||
"horizontal_gravity": "center",
|
||||
"vertical_gravity": "center",
|
||||
"y": 0,
|
||||
"width": "match_parent",
|
||||
"height": "match_parent",
|
||||
"textures": {
|
||||
"normal": "resources/transparent.png",
|
||||
"hover": "resources/transparent.png",
|
||||
"pressed": "resources/transparent.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "phoneMain",
|
||||
"horizontal_gravity": "center",
|
||||
"vertical_gravity": "center",
|
||||
"y": -60,
|
||||
"width": 882,
|
||||
"height": 1416,
|
||||
"textures": {
|
||||
"normal": "resources/w/ui/img/phone/PhoneChat001.png",
|
||||
"hover": "resources/w/ui/img/phone/PhoneChat001.png",
|
||||
"pressed": "resources/w/ui/img/phone/PhoneChat001.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "FrameLayout",
|
||||
"name": "chatMessagesContainer",
|
||||
"horizontal_gravity": "center",
|
||||
"vertical_gravity": "center",
|
||||
"width": "match_parent",
|
||||
"height": 1000
|
||||
},
|
||||
{
|
||||
"type": "StaticImage",
|
||||
"name": "chatTopCover",
|
||||
"width": 638,
|
||||
"height": 101.25,
|
||||
"x": 0,
|
||||
"y": 536,
|
||||
"horizontal_gravity": "center",
|
||||
"vertical_gravity": "center",
|
||||
"texture": "resources/w/ui/img/phone/chat_top_cover001.png"
|
||||
},
|
||||
{
|
||||
"type": "TextButton",
|
||||
"name": "chatTitleButton",
|
||||
"horizontal_gravity": "center",
|
||||
"vertical_gravity": "center",
|
||||
"x": 0.0,
|
||||
"y": 424,
|
||||
"width": 638,
|
||||
"height": 112,
|
||||
"text": "БишкекНовости",
|
||||
"textPaddingY": 28.0,
|
||||
"textPaddingX": 216.0,
|
||||
"fontSize": 40,
|
||||
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||
"textCentered": false,
|
||||
"topAligned": true,
|
||||
"wrap": true,
|
||||
"color": [1.0, 1.0, 1.0, 1.0],
|
||||
"textures": {
|
||||
"normal": "resources/w/ui/img/phone/CharHeader003.png",
|
||||
"hover": "resources/w/ui/img/phone/CharHeader003.png",
|
||||
"pressed": "resources/w/ui/img/phone/CharHeader003.png"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -672,6 +672,7 @@ namespace ZL
|
||||
|
||||
if (!menuManager.cutsceneHudActive_)
|
||||
menuManager.uiManager.draw(renderer);
|
||||
menuManager.drawChatFbo(renderer);
|
||||
if (currentLocation())
|
||||
{
|
||||
currentLocation()->dialogueSystem.draw(renderer);
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace ZL {
|
||||
|
||||
static int questStatusPriority(Quest::QuestStatus status) {
|
||||
@ -149,9 +150,12 @@ namespace ZL {
|
||||
phoneChatListHintRoot = loadUiFromFile("resources/w/ui/screen_phone_chat_list_hint001.json", renderer, zipFile);
|
||||
verticalPhoneChatListRoot = loadUiFromFile("resources/w/ui/portrait_screen_phone_chat_list.json", renderer, zipFile);
|
||||
verticalPhoneChatListHintRoot = loadUiFromFile("resources/w/ui/portrait_screen_phone_chat_list_hint001.json", renderer, zipFile);
|
||||
phoneChat1Root = loadUiFromFile("resources/w/ui/screen_phone_chat1.json", renderer, zipFile);
|
||||
phoneChat2Root = loadUiFromFile("resources/w/ui/screen_phone_chat2.json", renderer, zipFile);
|
||||
phoneChat3Root = loadUiFromFile("resources/w/ui/screen_phone_chat3.json", renderer, zipFile);
|
||||
phoneChat1Root = loadUiFromFile("resources/w/ui/screen_phone_chat1.json", renderer, zipFile);
|
||||
phoneChat2Root = loadUiFromFile("resources/w/ui/screen_phone_chat2.json", renderer, zipFile);
|
||||
phoneChat3Root = loadUiFromFile("resources/w/ui/screen_phone_chat3.json", renderer, zipFile);
|
||||
verticalPhoneChat1Root = loadUiFromFile("resources/w/ui/portrait_screen_phone_chat1.json", renderer, zipFile);
|
||||
verticalPhoneChat2Root = loadUiFromFile("resources/w/ui/portrait_screen_phone_chat2.json", renderer, zipFile);
|
||||
verticalPhoneChat3Root = loadUiFromFile("resources/w/ui/portrait_screen_phone_chat3.json", renderer, zipFile);
|
||||
|
||||
modalMenuRoot = loadUiFromFile("resources/w/ui/screen_modal_menu.json", renderer, zipFile);
|
||||
newInventoryRoot = loadUiFromFile("resources/w/ui/screen_inventory.json", renderer, zipFile);
|
||||
@ -657,15 +661,15 @@ namespace ZL {
|
||||
uiManager.setButtonCallback("phoneMain", [this](const std::string&) {});
|
||||
uiManager.setTextButtonCallback("chat1button", [this](const std::string&) {
|
||||
gameState_.chatUnread[0] = false;
|
||||
openPhoneChatFromList(0, phoneChat1Root);
|
||||
openPhoneChatFromList(0, isPortraitMode() ? verticalPhoneChat1Root : phoneChat1Root);
|
||||
});
|
||||
uiManager.setTextButtonCallback("chat2button", [this](const std::string&) {
|
||||
gameState_.chatUnread[1] = false;
|
||||
openPhoneChatFromList(1, phoneChat2Root);
|
||||
openPhoneChatFromList(1, isPortraitMode() ? verticalPhoneChat2Root : phoneChat2Root);
|
||||
});
|
||||
uiManager.setTextButtonCallback("chat3button", [this](const std::string&) {
|
||||
gameState_.chatUnread[2] = false;
|
||||
openPhoneChatFromList(2, phoneChat3Root);
|
||||
openPhoneChatFromList(2, isPortraitMode() ? verticalPhoneChat3Root : phoneChat3Root);
|
||||
});
|
||||
}
|
||||
|
||||
@ -784,7 +788,33 @@ namespace ZL {
|
||||
gameState_.tutorialPhoneChatScreenOpened = true;
|
||||
uiManager.pushMenuFromSavedRoot(chatRoot);
|
||||
|
||||
rebuildChatBubblesFromHistory(chatIndex);
|
||||
// Build a dedicated chatUiManager root sized to the FBO chat area
|
||||
{
|
||||
const bool portrait = isPortraitMode();
|
||||
const float fboW = portrait ? CHAT_FBO_WIDTH_P : CHAT_FBO_WIDTH_L;
|
||||
const float fboH = portrait ? (CHAT_FBO_OFFSET_UP_P + CHAT_FBO_OFFSET_DOWN_P)
|
||||
: (CHAT_FBO_OFFSET_UP_L + CHAT_FBO_OFFSET_DOWN_L);
|
||||
|
||||
auto fboRoot = std::make_shared<UiNode>();
|
||||
fboRoot->name = "chatFboRoot";
|
||||
fboRoot->width = fboW;
|
||||
fboRoot->height = fboH;
|
||||
fboRoot->screenRect = { 0.0f, 0.0f, fboW, fboH };
|
||||
|
||||
auto fboContainer = std::make_shared<UiNode>();
|
||||
fboContainer->name = "chatMessagesContainer";
|
||||
fboContainer->width = fboW;
|
||||
fboContainer->height = fboH;
|
||||
fboContainer->screenRect = { 0.0f, 0.0f, fboW, fboH };
|
||||
fboRoot->children.push_back(fboContainer);
|
||||
|
||||
chatUiManager.replaceRoot(fboRoot);
|
||||
chatFrameBuffer_ = std::make_unique<FrameBuffer>(
|
||||
static_cast<int>(fboW), static_cast<int>(fboH), false);
|
||||
chatFboQuadLastProjW_ = -1.0f;
|
||||
}
|
||||
|
||||
rebuildChatBubblesFromHistory(chatIndex, isPortraitMode());
|
||||
|
||||
uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) {
|
||||
closePhoneScreenFromChat();
|
||||
@ -808,6 +838,8 @@ namespace ZL {
|
||||
activeChatIndex_ = -1;
|
||||
currentPhoneSubScreen_ = PhoneSubScreen::ChatList;
|
||||
phoneChatVisibleBubbles_.clear();
|
||||
chatUiManager.replaceRoot(nullptr);
|
||||
chatFrameBuffer_.reset();
|
||||
uiManager.popMenu();
|
||||
uiManager.updateAllLayouts();
|
||||
if (gameState_.tutorialPhoneChatScreenOpened)
|
||||
@ -832,6 +864,8 @@ namespace ZL {
|
||||
currentPhoneSubScreen_ = PhoneSubScreen::None;
|
||||
uiState_ = GameUiState::Gameplay;
|
||||
phoneChatVisibleBubbles_.clear();
|
||||
chatUiManager.replaceRoot(nullptr);
|
||||
chatFrameBuffer_.reset();
|
||||
const int depth = uiManager.menuStackSize();
|
||||
for (int i = 0; i < depth; ++i) uiManager.popMenu();
|
||||
uiManager.updateAllLayouts();
|
||||
@ -1261,42 +1295,69 @@ namespace ZL {
|
||||
if (i > 0) totalHeight += CHAT_SPACING;
|
||||
}
|
||||
|
||||
const float available = CHAT_TOP_Y - CHAT_BOTTOM_Y;
|
||||
const float topY = (totalHeight <= available)
|
||||
? CHAT_TOP_Y
|
||||
: CHAT_BOTTOM_Y + totalHeight;
|
||||
const float fboH = CHAT_FBO_OFFSET_UP_L + CHAT_FBO_OFFSET_DOWN_L;
|
||||
const float topY = (totalHeight <= fboH) ? fboH : totalHeight;
|
||||
|
||||
float cursor = topY;
|
||||
for (auto& bubble : phoneChatVisibleBubbles_) {
|
||||
auto node = uiManager.findNode(bubble.nodeName);
|
||||
auto node = chatUiManager.findNode(bubble.nodeName);
|
||||
if (!node) continue;
|
||||
node->localY = cursor - bubble.height;
|
||||
cursor -= bubble.height + CHAT_SPACING;
|
||||
}
|
||||
uiManager.updateAllLayouts();
|
||||
chatUiManager.updateAllLayouts();
|
||||
}
|
||||
|
||||
void MenuManager::rebuildChatBubblesFromHistory(int chatIndex) {
|
||||
uiManager.clearChatBubbles("chatMessagesContainer");
|
||||
void MenuManager::recomputePhoneChatPositionsPortrait() {
|
||||
float totalHeight = 0.0f;
|
||||
for (size_t i = 0; i < phoneChatVisibleBubbles_.size(); ++i) {
|
||||
totalHeight += phoneChatVisibleBubbles_[i].height;
|
||||
if (i > 0) totalHeight += CHAT_SPACING;
|
||||
}
|
||||
|
||||
const float fboH = CHAT_FBO_OFFSET_UP_P + CHAT_FBO_OFFSET_DOWN_P;
|
||||
const float topY = (totalHeight <= fboH) ? fboH : totalHeight;
|
||||
|
||||
float cursor = topY;
|
||||
for (auto& bubble : phoneChatVisibleBubbles_) {
|
||||
auto node = chatUiManager.findNode(bubble.nodeName);
|
||||
if (!node) continue;
|
||||
node->localY = cursor - bubble.height;
|
||||
cursor -= bubble.height + CHAT_SPACING;
|
||||
}
|
||||
chatUiManager.updateAllLayouts();
|
||||
}
|
||||
|
||||
void MenuManager::rebuildChatBubblesFromHistory(int chatIndex, bool isPortrait) {
|
||||
chatUiManager.clearChatBubbles("chatMessagesContainer");
|
||||
phoneChatVisibleBubbles_.clear();
|
||||
|
||||
if (chatIndex < 0 || chatIndex > 2) return;
|
||||
for (const auto& msg : gameState_.chatHistory[chatIndex]) {
|
||||
const bool inc = msg.incoming;
|
||||
const std::string nodeName = uiManager.addChatBubble(
|
||||
const std::string nodeName = chatUiManager.addChatBubble(
|
||||
"chatMessagesContainer", msg.text, inc,
|
||||
inc ? texBubbleInCenter_ : texBubbleOutCenter_,
|
||||
inc ? texBubbleInLT_ : texBubbleOutLT_,
|
||||
inc ? texBubbleInLB_ : texBubbleOutLB_,
|
||||
inc ? texBubbleInRT_ : texBubbleOutRT_,
|
||||
inc ? texBubbleInRB_ : texBubbleOutRB_,
|
||||
renderer, "resources/fonts/DroidSans.ttf", 20, CONST_ZIP_FILE);
|
||||
renderer, "resources/fonts/DroidSans.ttf", isPortrait ? 32 : 20, isPortrait, CONST_ZIP_FILE);
|
||||
if (!nodeName.empty()) {
|
||||
auto n = uiManager.findNode(nodeName);
|
||||
auto n = chatUiManager.findNode(nodeName);
|
||||
phoneChatVisibleBubbles_.push_back({ nodeName, n ? n->height : 60.0f });
|
||||
}
|
||||
}
|
||||
recomputePhoneChatPositions();
|
||||
|
||||
if (isPortrait)
|
||||
{
|
||||
recomputePhoneChatPositionsPortrait();
|
||||
}
|
||||
else
|
||||
{
|
||||
recomputePhoneChatPositions();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MenuManager::onChatBubbleReady(const std::string& text, bool incoming) {
|
||||
@ -1310,25 +1371,34 @@ namespace ZL {
|
||||
|
||||
if (uiState_ != GameUiState::PhoneScreen) return;
|
||||
|
||||
const std::string nodeName = uiManager.addChatBubble(
|
||||
bool isPortrait = isPortraitMode();
|
||||
|
||||
const std::string nodeName = chatUiManager.addChatBubble(
|
||||
"chatMessagesContainer", text, incoming,
|
||||
incoming ? texBubbleInCenter_ : texBubbleOutCenter_,
|
||||
incoming ? texBubbleInLT_ : texBubbleOutLT_,
|
||||
incoming ? texBubbleInLB_ : texBubbleOutLB_,
|
||||
incoming ? texBubbleInRT_ : texBubbleOutRT_,
|
||||
incoming ? texBubbleInRB_ : texBubbleOutRB_,
|
||||
renderer, "resources/fonts/DroidSans.ttf", 20, CONST_ZIP_FILE);
|
||||
renderer, "resources/fonts/DroidSans.ttf", isPortrait ? 32 : 20, isPortrait, CONST_ZIP_FILE);
|
||||
|
||||
if (nodeName.empty()) return;
|
||||
|
||||
auto node = uiManager.findNode(nodeName);
|
||||
auto node = chatUiManager.findNode(nodeName);
|
||||
if (node) {
|
||||
node->scaleX = 0.0f;
|
||||
node->scaleY = 0.0f;
|
||||
phoneChatVisibleBubbles_.push_back({ nodeName, node->height });
|
||||
}
|
||||
recomputePhoneChatPositions();
|
||||
uiManager.startPopIn(nodeName, 300.0f);
|
||||
if (isPortrait)
|
||||
{
|
||||
recomputePhoneChatPositionsPortrait();
|
||||
}
|
||||
else
|
||||
{
|
||||
recomputePhoneChatPositions();
|
||||
}
|
||||
chatUiManager.startPopIn(nodeName, 300.0f);
|
||||
}
|
||||
|
||||
void MenuManager::setDarklandsMode(bool enabled)
|
||||
@ -1537,6 +1607,57 @@ namespace ZL {
|
||||
|
||||
void MenuManager::update(float deltaMs) {
|
||||
updateToasts(deltaMs);
|
||||
chatUiManager.update(deltaMs);
|
||||
}
|
||||
|
||||
void MenuManager::drawChatFbo(Renderer& renderer) {
|
||||
if (!chatFrameBuffer_) return;
|
||||
|
||||
const bool portrait = isPortraitMode();
|
||||
const float fboW = portrait ? CHAT_FBO_WIDTH_P : CHAT_FBO_WIDTH_L;
|
||||
const float offsetUp = portrait ? CHAT_FBO_OFFSET_UP_P : CHAT_FBO_OFFSET_UP_L;
|
||||
const float offsetDown= portrait ? CHAT_FBO_OFFSET_DOWN_P : CHAT_FBO_OFFSET_DOWN_L;
|
||||
const float fboH = offsetUp + offsetDown;
|
||||
const float projW = Environment::projectionWidth;
|
||||
const float projH = Environment::projectionHeight;
|
||||
|
||||
// Pass 1: render chatUiManager into FBO
|
||||
chatFrameBuffer_->Bind();
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
chatUiManager.draw(renderer, fboW, fboH);
|
||||
chatFrameBuffer_->Unbind();
|
||||
|
||||
// Pass 2: blit FBO texture onto screen, centered with up/down offsets from screen center
|
||||
if (chatFboQuadLastProjW_ != projW || chatFboQuadLastPortrait_ != portrait ||
|
||||
chatFboQuadLastProjH_ != projH) {
|
||||
const float x0 = (projW - fboW) * 0.5f;
|
||||
const float y0 = projH * 0.5f - offsetDown;
|
||||
const float x1 = x0 + fboW;
|
||||
const float y1 = y0 + fboH;
|
||||
VertexDataStruct vd;
|
||||
vd.PositionData = { {x0,y0,0},{x0,y1,0},{x1,y1,0},
|
||||
{x0,y0,0},{x1,y1,0},{x1,y0,0} };
|
||||
vd.TexCoordData = { {0,0},{0,1},{1,1},
|
||||
{0,0},{1,1},{1,0} };
|
||||
chatFboQuad_.AssignFrom(vd);
|
||||
chatFboQuadLastProjW_ = projW;
|
||||
chatFboQuadLastProjH_ = projH;
|
||||
chatFboQuadLastPortrait_ = portrait;
|
||||
}
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
renderer.PushProjectionMatrix(projW, projH, -1, 1);
|
||||
renderer.PushMatrix();
|
||||
renderer.LoadIdentity();
|
||||
renderer.RenderUniform1f("uAlpha", 1.0f);
|
||||
glBindTexture(GL_TEXTURE_2D, chatFrameBuffer_->getTextureID());
|
||||
renderer.DrawVertexRenderStruct(chatFboQuad_);
|
||||
renderer.PopMatrix();
|
||||
renderer.PopProjectionMatrix();
|
||||
}
|
||||
|
||||
void MenuManager::saveSettings() {
|
||||
@ -1609,10 +1730,19 @@ namespace ZL {
|
||||
currentPhoneSubScreen_ = PhoneSubScreen::MapUni;
|
||||
break;
|
||||
case PhoneSubScreen::ChatList:
|
||||
case PhoneSubScreen::Chat:
|
||||
openPhoneScreen();
|
||||
openPhoneMessenger();
|
||||
break;
|
||||
case PhoneSubScreen::Chat: {
|
||||
openPhoneScreen();
|
||||
openPhoneMessenger();
|
||||
std::shared_ptr<UiNode> chatRoot;
|
||||
if (savedChatIndex == 0) chatRoot = isPortraitMode() ? verticalPhoneChat1Root : phoneChat1Root;
|
||||
else if (savedChatIndex == 1) chatRoot = isPortraitMode() ? verticalPhoneChat2Root : phoneChat2Root;
|
||||
else if (savedChatIndex == 2) chatRoot = isPortraitMode() ? verticalPhoneChat3Root : phoneChat3Root;
|
||||
if (chatRoot) openPhoneChatFromList(savedChatIndex, chatRoot);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
openPhoneScreen();
|
||||
break;
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "AudioPlayerAsync.h"
|
||||
#include "render/FrameBuffer.h"
|
||||
|
||||
// Forward-declared here to avoid pulling Game.h into MenuManager.h.
|
||||
namespace ZL { struct SaveSlotInfo; }
|
||||
@ -31,6 +32,7 @@ namespace ZL {
|
||||
public:
|
||||
UiManager uiManager;
|
||||
UiManager topUiManager;
|
||||
UiManager chatUiManager;
|
||||
|
||||
MenuManager(Renderer& iRenderer, GameState& gameState, AudioPlayerAsync& audioPlayer);
|
||||
|
||||
@ -96,6 +98,9 @@ namespace ZL {
|
||||
// Called by Game when the window is resized and orientation has changed.
|
||||
void onOrientationChanged();
|
||||
|
||||
// Called from Game::drawUI() to render chat bubbles through an FBO.
|
||||
void drawChatFbo(Renderer& renderer);
|
||||
|
||||
void saveSettings();
|
||||
void loadSettings();
|
||||
|
||||
@ -125,11 +130,12 @@ namespace ZL {
|
||||
void refreshChatUnreadIndicators();
|
||||
void resetPhoneChatNodes();
|
||||
void recomputePhoneChatPositions();
|
||||
void recomputePhoneChatPositionsPortrait();
|
||||
void openPhoneChatFromList(int chatIndex, std::shared_ptr<UiNode> chatRoot);
|
||||
void returnToPhoneChatList();
|
||||
void closePhoneScreenFromChat();
|
||||
void applyUniIntHud();
|
||||
void rebuildChatBubblesFromHistory(int chatIndex);
|
||||
void rebuildChatBubblesFromHistory(int chatIndex, bool isPortrait);
|
||||
void showModalMenuScreen();
|
||||
|
||||
void showAboutScreen();
|
||||
@ -210,6 +216,9 @@ namespace ZL {
|
||||
std::shared_ptr<UiNode> phoneChat1Root;
|
||||
std::shared_ptr<UiNode> phoneChat2Root;
|
||||
std::shared_ptr<UiNode> phoneChat3Root;
|
||||
std::shared_ptr<UiNode> verticalPhoneChat1Root;
|
||||
std::shared_ptr<UiNode> verticalPhoneChat2Root;
|
||||
std::shared_ptr<UiNode> verticalPhoneChat3Root;
|
||||
|
||||
std::shared_ptr<UiNode> modalMenuRoot;
|
||||
|
||||
@ -253,6 +262,27 @@ namespace ZL {
|
||||
static constexpr float CHAT_TOP_Y = 610.0f;
|
||||
static constexpr float CHAT_BOTTOM_Y = 290.0f;
|
||||
static constexpr float CHAT_SPACING = 10.0f;
|
||||
|
||||
static constexpr float P_CHAT_TOP_Y = 610.0f + 246;
|
||||
static constexpr float P_CHAT_BOTTOM_Y = 290.0f + 126;
|
||||
|
||||
// FBO dimensions for chat clipping.
|
||||
// OFFSET_UP / OFFSET_DOWN = how far the FBO extends above / below screen center.
|
||||
// Height = UP + DOWN. Quad: y0 = projH/2 - DOWN, y1 = projH/2 + UP.
|
||||
static constexpr float CHAT_FBO_WIDTH_L = 469.0f;
|
||||
static constexpr float CHAT_FBO_OFFSET_UP_L = 260.0f;
|
||||
static constexpr float CHAT_FBO_OFFSET_DOWN_L = 70.0f;
|
||||
|
||||
static constexpr float CHAT_FBO_WIDTH_P = 670.0f;
|
||||
static constexpr float CHAT_FBO_OFFSET_UP_P = 360.0f;
|
||||
static constexpr float CHAT_FBO_OFFSET_DOWN_P = 70.0f;
|
||||
|
||||
std::unique_ptr<FrameBuffer> chatFrameBuffer_;
|
||||
VertexRenderStruct chatFboQuad_;
|
||||
float chatFboQuadLastProjW_ = -1.0f;
|
||||
float chatFboQuadLastProjH_ = -1.0f;
|
||||
bool chatFboQuadLastPortrait_ = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace ZL
|
||||
|
||||
@ -854,6 +854,22 @@ namespace ZL {
|
||||
mesh.RefreshVBO();
|
||||
}
|
||||
|
||||
float UiChatBubble::computeWidthPortrait() const {
|
||||
if (!textRenderer) return P_MAX_WIDTH;
|
||||
const float measuredTextW = textRenderer->measureTextWidth(text, 1.0f);
|
||||
const float required = measuredTextW + P_CORNER_SIZE * 2.0f + P_PAD_X * 2.0f;
|
||||
return min(required, P_MAX_WIDTH);
|
||||
}
|
||||
|
||||
float UiChatBubble::computeHeightPortrait() const {
|
||||
if (!textRenderer) return P_CORNER_SIZE * 2 + P_PAD_Y * 2 + static_cast<float>(fontSize);
|
||||
const float textW = computeWidthPortrait() - P_CORNER_SIZE * 2.0f - P_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 P_CORNER_SIZE * 2.0f + P_PAD_Y * 2.0f + lines * static_cast<float>(fontSize) * 1.25f;
|
||||
}
|
||||
|
||||
float UiChatBubble::computeWidth() const {
|
||||
if (!textRenderer) return MAX_WIDTH;
|
||||
const float measuredTextW = textRenderer->measureTextWidth(text, 1.0f);
|
||||
@ -892,7 +908,7 @@ namespace ZL {
|
||||
buildQuadMesh(meshCornerRB, x + w - cs, y, x + w, y + cs);
|
||||
}
|
||||
|
||||
void UiChatBubble::draw(Renderer& renderer, float nodeScaleX, float nodeScaleY) const {
|
||||
void UiChatBubble::draw(Renderer& renderer, float nodeScaleX, float nodeScaleY, float projW, float projH) const {
|
||||
renderer.RenderUniform1i(textureUniformName, 0);
|
||||
|
||||
|
||||
@ -936,7 +952,7 @@ namespace ZL {
|
||||
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 });
|
||||
textRenderer->drawText(wrapped, tx, ty, nodeScaleX, false, { 1.f, 1.f, 1.f, 1.f }, projW, projH);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1456,8 +1472,10 @@ namespace ZL {
|
||||
return static_cast<int>(menuStack.size());
|
||||
}
|
||||
|
||||
void UiManager::draw(Renderer& renderer) {
|
||||
renderer.PushProjectionMatrix(Environment::projectionWidth, Environment::projectionHeight, -1, 1);
|
||||
void UiManager::draw(Renderer& renderer, float projW, float projH) {
|
||||
if (projW <= 0.0f) projW = Environment::projectionWidth;
|
||||
if (projH <= 0.0f) projH = Environment::projectionHeight;
|
||||
renderer.PushProjectionMatrix(projW, projH, -1, 1);
|
||||
renderer.PushMatrix();
|
||||
renderer.LoadIdentity();
|
||||
renderer.RenderUniform1f("uAlpha", 1.0f); // ensure alpha is fully opaque unless a widget overrides it
|
||||
@ -1481,7 +1499,7 @@ namespace ZL {
|
||||
|
||||
// 1b. Chat bubble (composite widget)
|
||||
if (node->chatBubble) {
|
||||
node->chatBubble->draw(renderer, node->scaleX, node->scaleY);
|
||||
node->chatBubble->draw(renderer, node->scaleX, node->scaleY, projW, projH);
|
||||
}
|
||||
|
||||
// 2. Потом кнопки
|
||||
@ -2136,6 +2154,7 @@ namespace ZL {
|
||||
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,
|
||||
bool isPortrait,
|
||||
const std::string& zipFile)
|
||||
{
|
||||
if (!root) return "";
|
||||
@ -2168,39 +2187,37 @@ namespace ZL {
|
||||
// Build the UiNode
|
||||
auto node = std::make_shared<UiNode>();
|
||||
node->name = nodeName;
|
||||
node->width = bubble->computeWidth();
|
||||
node->height = bubble->computeHeight();
|
||||
node->width = isPortrait? bubble->computeWidthPortrait() : bubble->computeWidth();
|
||||
node->height = isPortrait? bubble->computeHeightPortrait() : bubble->computeHeight();
|
||||
|
||||
if (incoming) {
|
||||
node->localX = -node->width * 0.5+110 - (UiChatBubble::MAX_WIDTH - node->width);// +(425.f - node->width);
|
||||
|
||||
if (isPortrait)
|
||||
{
|
||||
if (incoming) {
|
||||
node->localX = -300 + node->width * 0.5;
|
||||
}
|
||||
else
|
||||
{
|
||||
node->localX = 300 - node->width * 0.5;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/*if (node->width == UiChatBubble::MAX_WIDTH)
|
||||
{
|
||||
node->localX = node->width * 0.5 - 110 - (UiChatBubble::MAX_WIDTH - node->width);// -(425.f - node->width);
|
||||
if (incoming) {
|
||||
node->localX = -210 + node->width * 0.5;
|
||||
}
|
||||
else
|
||||
{*/
|
||||
//node->localX = node->width * 0.5 - 110;
|
||||
//node->localX = - 110 +x;
|
||||
node->localX = 210 - node->width * 0.5;
|
||||
//}
|
||||
{
|
||||
node->localX = 210 - node->width * 0.5;
|
||||
}
|
||||
}
|
||||
//430
|
||||
//1280
|
||||
|
||||
node->localY = 0.0f;
|
||||
node->visible = true;
|
||||
node->chatBubble = bubble;
|
||||
|
||||
node->layoutSettings.hGravity = HorizontalGravity::Center;
|
||||
/*
|
||||
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;
|
||||
|
||||
@ -262,11 +262,14 @@ namespace ZL {
|
||||
|
||||
static constexpr float CORNER_SIZE = 22.4f;
|
||||
static constexpr float MAX_WIDTH = 320.6f;
|
||||
//static constexpr float PAD_X = 7.0f;
|
||||
//static constexpr float PAD_Y = 5.0f;
|
||||
static constexpr float PAD_X = -10.0f;
|
||||
static constexpr float PAD_Y = -10.0f;
|
||||
|
||||
static constexpr float P_CORNER_SIZE = 32;
|
||||
static constexpr float P_MAX_WIDTH = 456;
|
||||
static constexpr float P_PAD_X = -14.0f;
|
||||
static constexpr float P_PAD_Y = -14.0f;
|
||||
|
||||
// 7 meshes: middle-center, top-strip, bottom-strip, LT, RT, LB, RB
|
||||
VertexRenderStruct meshMiddle;
|
||||
VertexRenderStruct meshTopStrip;
|
||||
@ -282,10 +285,12 @@ namespace ZL {
|
||||
// Must be called after textRenderer is initialised
|
||||
float computeWidth() const; // capped at MAX_WIDTH
|
||||
float computeHeight() const; // depends on computeWidth()
|
||||
float computeWidthPortrait() const;
|
||||
float computeHeightPortrait() const;
|
||||
void buildMeshes();
|
||||
// nodeScaleX/Y: the owning UiNode's current scale (for pop-in / animations).
|
||||
// Text position and glyph size are transformed to match the renderer's scale matrix.
|
||||
void draw(Renderer& renderer, float nodeScaleX = 1.0f, float nodeScaleY = 1.0f) const;
|
||||
void draw(Renderer& renderer, float nodeScaleX = 1.0f, float nodeScaleY = 1.0f, float projW = 0.0f, float projH = 0.0f) const;
|
||||
};
|
||||
|
||||
struct UiNode {
|
||||
@ -366,7 +371,7 @@ namespace ZL {
|
||||
void loadFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile = "");
|
||||
void appendFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile = "");
|
||||
|
||||
void draw(Renderer& renderer);
|
||||
void draw(Renderer& renderer, float projW = 0.0f, float projH = 0.0f);
|
||||
|
||||
// Multi-touch methods (used directly for touch events with per-finger IDs)
|
||||
void onTouchDown(int64_t fingerId, int x, int y);
|
||||
@ -461,6 +466,7 @@ namespace ZL {
|
||||
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,
|
||||
bool isPortrait,
|
||||
const std::string& zipFile);
|
||||
|
||||
// Remove all chat-bubble children from named parent and clear chatBubbles collection.
|
||||
|
||||
@ -380,9 +380,11 @@ float TextRenderer::measureTextWidth(const std::string& text, float scale) const
|
||||
return maxLineWidth;
|
||||
}
|
||||
|
||||
void TextRenderer::drawText(const std::string& text, float x, float y, float scale, bool centered, std::array<float, 4> color)
|
||||
void TextRenderer::drawText(const std::string& text, float x, float y, float scale, bool centered, std::array<float, 4> color, float projW, float projH)
|
||||
{
|
||||
if (!r || text.empty() || !atlasTexture) return;
|
||||
if (projW <= 0.0f) projW = Environment::projectionWidth;
|
||||
if (projH <= 0.0f) projH = Environment::projectionHeight;
|
||||
|
||||
// формируем ключ кеша
|
||||
std::string key = text + "|" + std::to_string(scale) + "|" + (centered ? "1" : "0");
|
||||
@ -471,16 +473,14 @@ void TextRenderer::drawText(const std::string& text, float x, float y, float sca
|
||||
// 4. Рендеринг
|
||||
r->shaderManager.PushShader(shaderName);
|
||||
|
||||
// Матрица проекции — используем виртуальные проекционные размеры,
|
||||
// чтобы координаты текста были независимы от физического разрешения экрана.
|
||||
float W = Environment::projectionWidth;
|
||||
float H = Environment::projectionHeight;
|
||||
// Матрица проекции — используем переданные проекционные размеры.
|
||||
// Это позволяет корректно рендерить текст как в основной сцене, так и внутри FBO.
|
||||
Eigen::Matrix4f proj = Eigen::Matrix4f::Identity();
|
||||
proj(0, 0) = 2.0f / W;
|
||||
proj(1, 1) = 2.0f / H;
|
||||
proj(0, 0) = 2.0f / projW;
|
||||
proj(1, 1) = 2.0f / projH;
|
||||
// Сдвигаем проекцию так, чтобы локальные координаты меша (pen-origin=0,0) оказались в (tx,ty)
|
||||
proj(0, 3) = -1.0f + 2.0f * (tx) / W;
|
||||
proj(1, 3) = -1.0f + 2.0f * (ty) / H;
|
||||
proj(0, 3) = -1.0f + 2.0f * (tx) / projW;
|
||||
proj(1, 3) = -1.0f + 2.0f * (ty) / projH;
|
||||
|
||||
//cached.mesh.RefreshVBO();
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@ public:
|
||||
~TextRenderer();
|
||||
|
||||
bool init(Renderer& renderer, const std::string& ttfPath, int pixelSize, const std::string& zipfilename);
|
||||
void drawText(const std::string& text, float x, float y, float scale, bool centered, std::array<float, 4> color = { 1.f,1.f,1.f,1.f });
|
||||
void drawText(const std::string& text, float x, float y, float scale, bool centered, std::array<float, 4> color = { 1.f,1.f,1.f,1.f }, float projW = 0.0f, float projH = 0.0f);
|
||||
|
||||
float measureTextWidth(const std::string& text, float scale = 1.0f) const;
|
||||
float getLineHeight(float scale = 1.0f) const { return lineHeight * scale; }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user