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_)
|
if (!menuManager.cutsceneHudActive_)
|
||||||
menuManager.uiManager.draw(renderer);
|
menuManager.uiManager.draw(renderer);
|
||||||
|
menuManager.drawChatFbo(renderer);
|
||||||
if (currentLocation())
|
if (currentLocation())
|
||||||
{
|
{
|
||||||
currentLocation()->dialogueSystem.draw(renderer);
|
currentLocation()->dialogueSystem.draw(renderer);
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
|
||||||
namespace ZL {
|
namespace ZL {
|
||||||
|
|
||||||
static int questStatusPriority(Quest::QuestStatus status) {
|
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);
|
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);
|
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);
|
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);
|
phoneChat1Root = loadUiFromFile("resources/w/ui/screen_phone_chat1.json", renderer, zipFile);
|
||||||
phoneChat2Root = loadUiFromFile("resources/w/ui/screen_phone_chat2.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);
|
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);
|
modalMenuRoot = loadUiFromFile("resources/w/ui/screen_modal_menu.json", renderer, zipFile);
|
||||||
newInventoryRoot = loadUiFromFile("resources/w/ui/screen_inventory.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.setButtonCallback("phoneMain", [this](const std::string&) {});
|
||||||
uiManager.setTextButtonCallback("chat1button", [this](const std::string&) {
|
uiManager.setTextButtonCallback("chat1button", [this](const std::string&) {
|
||||||
gameState_.chatUnread[0] = false;
|
gameState_.chatUnread[0] = false;
|
||||||
openPhoneChatFromList(0, phoneChat1Root);
|
openPhoneChatFromList(0, isPortraitMode() ? verticalPhoneChat1Root : phoneChat1Root);
|
||||||
});
|
});
|
||||||
uiManager.setTextButtonCallback("chat2button", [this](const std::string&) {
|
uiManager.setTextButtonCallback("chat2button", [this](const std::string&) {
|
||||||
gameState_.chatUnread[1] = false;
|
gameState_.chatUnread[1] = false;
|
||||||
openPhoneChatFromList(1, phoneChat2Root);
|
openPhoneChatFromList(1, isPortraitMode() ? verticalPhoneChat2Root : phoneChat2Root);
|
||||||
});
|
});
|
||||||
uiManager.setTextButtonCallback("chat3button", [this](const std::string&) {
|
uiManager.setTextButtonCallback("chat3button", [this](const std::string&) {
|
||||||
gameState_.chatUnread[2] = false;
|
gameState_.chatUnread[2] = false;
|
||||||
openPhoneChatFromList(2, phoneChat3Root);
|
openPhoneChatFromList(2, isPortraitMode() ? verticalPhoneChat3Root : phoneChat3Root);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -784,7 +788,33 @@ namespace ZL {
|
|||||||
gameState_.tutorialPhoneChatScreenOpened = true;
|
gameState_.tutorialPhoneChatScreenOpened = true;
|
||||||
uiManager.pushMenuFromSavedRoot(chatRoot);
|
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&) {
|
uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) {
|
||||||
closePhoneScreenFromChat();
|
closePhoneScreenFromChat();
|
||||||
@ -808,6 +838,8 @@ namespace ZL {
|
|||||||
activeChatIndex_ = -1;
|
activeChatIndex_ = -1;
|
||||||
currentPhoneSubScreen_ = PhoneSubScreen::ChatList;
|
currentPhoneSubScreen_ = PhoneSubScreen::ChatList;
|
||||||
phoneChatVisibleBubbles_.clear();
|
phoneChatVisibleBubbles_.clear();
|
||||||
|
chatUiManager.replaceRoot(nullptr);
|
||||||
|
chatFrameBuffer_.reset();
|
||||||
uiManager.popMenu();
|
uiManager.popMenu();
|
||||||
uiManager.updateAllLayouts();
|
uiManager.updateAllLayouts();
|
||||||
if (gameState_.tutorialPhoneChatScreenOpened)
|
if (gameState_.tutorialPhoneChatScreenOpened)
|
||||||
@ -832,6 +864,8 @@ namespace ZL {
|
|||||||
currentPhoneSubScreen_ = PhoneSubScreen::None;
|
currentPhoneSubScreen_ = PhoneSubScreen::None;
|
||||||
uiState_ = GameUiState::Gameplay;
|
uiState_ = GameUiState::Gameplay;
|
||||||
phoneChatVisibleBubbles_.clear();
|
phoneChatVisibleBubbles_.clear();
|
||||||
|
chatUiManager.replaceRoot(nullptr);
|
||||||
|
chatFrameBuffer_.reset();
|
||||||
const int depth = uiManager.menuStackSize();
|
const int depth = uiManager.menuStackSize();
|
||||||
for (int i = 0; i < depth; ++i) uiManager.popMenu();
|
for (int i = 0; i < depth; ++i) uiManager.popMenu();
|
||||||
uiManager.updateAllLayouts();
|
uiManager.updateAllLayouts();
|
||||||
@ -1261,42 +1295,69 @@ namespace ZL {
|
|||||||
if (i > 0) totalHeight += CHAT_SPACING;
|
if (i > 0) totalHeight += CHAT_SPACING;
|
||||||
}
|
}
|
||||||
|
|
||||||
const float available = CHAT_TOP_Y - CHAT_BOTTOM_Y;
|
const float fboH = CHAT_FBO_OFFSET_UP_L + CHAT_FBO_OFFSET_DOWN_L;
|
||||||
const float topY = (totalHeight <= available)
|
const float topY = (totalHeight <= fboH) ? fboH : totalHeight;
|
||||||
? CHAT_TOP_Y
|
|
||||||
: CHAT_BOTTOM_Y + totalHeight;
|
|
||||||
|
|
||||||
float cursor = topY;
|
float cursor = topY;
|
||||||
for (auto& bubble : phoneChatVisibleBubbles_) {
|
for (auto& bubble : phoneChatVisibleBubbles_) {
|
||||||
auto node = uiManager.findNode(bubble.nodeName);
|
auto node = chatUiManager.findNode(bubble.nodeName);
|
||||||
if (!node) continue;
|
if (!node) continue;
|
||||||
node->localY = cursor - bubble.height;
|
node->localY = cursor - bubble.height;
|
||||||
cursor -= bubble.height + CHAT_SPACING;
|
cursor -= bubble.height + CHAT_SPACING;
|
||||||
}
|
}
|
||||||
uiManager.updateAllLayouts();
|
chatUiManager.updateAllLayouts();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MenuManager::rebuildChatBubblesFromHistory(int chatIndex) {
|
void MenuManager::recomputePhoneChatPositionsPortrait() {
|
||||||
uiManager.clearChatBubbles("chatMessagesContainer");
|
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();
|
phoneChatVisibleBubbles_.clear();
|
||||||
|
|
||||||
if (chatIndex < 0 || chatIndex > 2) return;
|
if (chatIndex < 0 || chatIndex > 2) return;
|
||||||
for (const auto& msg : gameState_.chatHistory[chatIndex]) {
|
for (const auto& msg : gameState_.chatHistory[chatIndex]) {
|
||||||
const bool inc = msg.incoming;
|
const bool inc = msg.incoming;
|
||||||
const std::string nodeName = uiManager.addChatBubble(
|
const std::string nodeName = chatUiManager.addChatBubble(
|
||||||
"chatMessagesContainer", msg.text, inc,
|
"chatMessagesContainer", msg.text, inc,
|
||||||
inc ? texBubbleInCenter_ : texBubbleOutCenter_,
|
inc ? texBubbleInCenter_ : texBubbleOutCenter_,
|
||||||
inc ? texBubbleInLT_ : texBubbleOutLT_,
|
inc ? texBubbleInLT_ : texBubbleOutLT_,
|
||||||
inc ? texBubbleInLB_ : texBubbleOutLB_,
|
inc ? texBubbleInLB_ : texBubbleOutLB_,
|
||||||
inc ? texBubbleInRT_ : texBubbleOutRT_,
|
inc ? texBubbleInRT_ : texBubbleOutRT_,
|
||||||
inc ? texBubbleInRB_ : texBubbleOutRB_,
|
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()) {
|
if (!nodeName.empty()) {
|
||||||
auto n = uiManager.findNode(nodeName);
|
auto n = chatUiManager.findNode(nodeName);
|
||||||
phoneChatVisibleBubbles_.push_back({ nodeName, n ? n->height : 60.0f });
|
phoneChatVisibleBubbles_.push_back({ nodeName, n ? n->height : 60.0f });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
recomputePhoneChatPositions();
|
|
||||||
|
if (isPortrait)
|
||||||
|
{
|
||||||
|
recomputePhoneChatPositionsPortrait();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
recomputePhoneChatPositions();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MenuManager::onChatBubbleReady(const std::string& text, bool incoming) {
|
void MenuManager::onChatBubbleReady(const std::string& text, bool incoming) {
|
||||||
@ -1310,25 +1371,34 @@ namespace ZL {
|
|||||||
|
|
||||||
if (uiState_ != GameUiState::PhoneScreen) return;
|
if (uiState_ != GameUiState::PhoneScreen) return;
|
||||||
|
|
||||||
const std::string nodeName = uiManager.addChatBubble(
|
bool isPortrait = isPortraitMode();
|
||||||
|
|
||||||
|
const std::string nodeName = chatUiManager.addChatBubble(
|
||||||
"chatMessagesContainer", text, incoming,
|
"chatMessagesContainer", text, incoming,
|
||||||
incoming ? texBubbleInCenter_ : texBubbleOutCenter_,
|
incoming ? texBubbleInCenter_ : texBubbleOutCenter_,
|
||||||
incoming ? texBubbleInLT_ : texBubbleOutLT_,
|
incoming ? texBubbleInLT_ : texBubbleOutLT_,
|
||||||
incoming ? texBubbleInLB_ : texBubbleOutLB_,
|
incoming ? texBubbleInLB_ : texBubbleOutLB_,
|
||||||
incoming ? texBubbleInRT_ : texBubbleOutRT_,
|
incoming ? texBubbleInRT_ : texBubbleOutRT_,
|
||||||
incoming ? texBubbleInRB_ : texBubbleOutRB_,
|
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;
|
if (nodeName.empty()) return;
|
||||||
|
|
||||||
auto node = uiManager.findNode(nodeName);
|
auto node = chatUiManager.findNode(nodeName);
|
||||||
if (node) {
|
if (node) {
|
||||||
node->scaleX = 0.0f;
|
node->scaleX = 0.0f;
|
||||||
node->scaleY = 0.0f;
|
node->scaleY = 0.0f;
|
||||||
phoneChatVisibleBubbles_.push_back({ nodeName, node->height });
|
phoneChatVisibleBubbles_.push_back({ nodeName, node->height });
|
||||||
}
|
}
|
||||||
recomputePhoneChatPositions();
|
if (isPortrait)
|
||||||
uiManager.startPopIn(nodeName, 300.0f);
|
{
|
||||||
|
recomputePhoneChatPositionsPortrait();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
recomputePhoneChatPositions();
|
||||||
|
}
|
||||||
|
chatUiManager.startPopIn(nodeName, 300.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MenuManager::setDarklandsMode(bool enabled)
|
void MenuManager::setDarklandsMode(bool enabled)
|
||||||
@ -1537,6 +1607,57 @@ namespace ZL {
|
|||||||
|
|
||||||
void MenuManager::update(float deltaMs) {
|
void MenuManager::update(float deltaMs) {
|
||||||
updateToasts(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() {
|
void MenuManager::saveSettings() {
|
||||||
@ -1609,10 +1730,19 @@ namespace ZL {
|
|||||||
currentPhoneSubScreen_ = PhoneSubScreen::MapUni;
|
currentPhoneSubScreen_ = PhoneSubScreen::MapUni;
|
||||||
break;
|
break;
|
||||||
case PhoneSubScreen::ChatList:
|
case PhoneSubScreen::ChatList:
|
||||||
case PhoneSubScreen::Chat:
|
|
||||||
openPhoneScreen();
|
openPhoneScreen();
|
||||||
openPhoneMessenger();
|
openPhoneMessenger();
|
||||||
break;
|
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:
|
default:
|
||||||
openPhoneScreen();
|
openPhoneScreen();
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "AudioPlayerAsync.h"
|
#include "AudioPlayerAsync.h"
|
||||||
|
#include "render/FrameBuffer.h"
|
||||||
|
|
||||||
// Forward-declared here to avoid pulling Game.h into MenuManager.h.
|
// Forward-declared here to avoid pulling Game.h into MenuManager.h.
|
||||||
namespace ZL { struct SaveSlotInfo; }
|
namespace ZL { struct SaveSlotInfo; }
|
||||||
@ -31,6 +32,7 @@ namespace ZL {
|
|||||||
public:
|
public:
|
||||||
UiManager uiManager;
|
UiManager uiManager;
|
||||||
UiManager topUiManager;
|
UiManager topUiManager;
|
||||||
|
UiManager chatUiManager;
|
||||||
|
|
||||||
MenuManager(Renderer& iRenderer, GameState& gameState, AudioPlayerAsync& audioPlayer);
|
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.
|
// Called by Game when the window is resized and orientation has changed.
|
||||||
void onOrientationChanged();
|
void onOrientationChanged();
|
||||||
|
|
||||||
|
// Called from Game::drawUI() to render chat bubbles through an FBO.
|
||||||
|
void drawChatFbo(Renderer& renderer);
|
||||||
|
|
||||||
void saveSettings();
|
void saveSettings();
|
||||||
void loadSettings();
|
void loadSettings();
|
||||||
|
|
||||||
@ -125,11 +130,12 @@ namespace ZL {
|
|||||||
void refreshChatUnreadIndicators();
|
void refreshChatUnreadIndicators();
|
||||||
void resetPhoneChatNodes();
|
void resetPhoneChatNodes();
|
||||||
void recomputePhoneChatPositions();
|
void recomputePhoneChatPositions();
|
||||||
|
void recomputePhoneChatPositionsPortrait();
|
||||||
void openPhoneChatFromList(int chatIndex, std::shared_ptr<UiNode> chatRoot);
|
void openPhoneChatFromList(int chatIndex, std::shared_ptr<UiNode> chatRoot);
|
||||||
void returnToPhoneChatList();
|
void returnToPhoneChatList();
|
||||||
void closePhoneScreenFromChat();
|
void closePhoneScreenFromChat();
|
||||||
void applyUniIntHud();
|
void applyUniIntHud();
|
||||||
void rebuildChatBubblesFromHistory(int chatIndex);
|
void rebuildChatBubblesFromHistory(int chatIndex, bool isPortrait);
|
||||||
void showModalMenuScreen();
|
void showModalMenuScreen();
|
||||||
|
|
||||||
void showAboutScreen();
|
void showAboutScreen();
|
||||||
@ -210,6 +216,9 @@ namespace ZL {
|
|||||||
std::shared_ptr<UiNode> phoneChat1Root;
|
std::shared_ptr<UiNode> phoneChat1Root;
|
||||||
std::shared_ptr<UiNode> phoneChat2Root;
|
std::shared_ptr<UiNode> phoneChat2Root;
|
||||||
std::shared_ptr<UiNode> phoneChat3Root;
|
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;
|
std::shared_ptr<UiNode> modalMenuRoot;
|
||||||
|
|
||||||
@ -253,6 +262,27 @@ namespace ZL {
|
|||||||
static constexpr float CHAT_TOP_Y = 610.0f;
|
static constexpr float CHAT_TOP_Y = 610.0f;
|
||||||
static constexpr float CHAT_BOTTOM_Y = 290.0f;
|
static constexpr float CHAT_BOTTOM_Y = 290.0f;
|
||||||
static constexpr float CHAT_SPACING = 10.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
|
} // namespace ZL
|
||||||
|
|||||||
@ -854,6 +854,22 @@ namespace ZL {
|
|||||||
mesh.RefreshVBO();
|
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 {
|
float UiChatBubble::computeWidth() const {
|
||||||
if (!textRenderer) return MAX_WIDTH;
|
if (!textRenderer) return MAX_WIDTH;
|
||||||
const float measuredTextW = textRenderer->measureTextWidth(text, 1.0f);
|
const float measuredTextW = textRenderer->measureTextWidth(text, 1.0f);
|
||||||
@ -892,7 +908,7 @@ namespace ZL {
|
|||||||
buildQuadMesh(meshCornerRB, x + w - cs, y, x + w, y + cs);
|
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);
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
|
|
||||||
|
|
||||||
@ -936,7 +952,7 @@ namespace ZL {
|
|||||||
const float tx = cx + (tx0 - cx) * nodeScaleX;
|
const float tx = cx + (tx0 - cx) * nodeScaleX;
|
||||||
const float ty = cy + (ty0 - cy) * nodeScaleY;
|
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());
|
return static_cast<int>(menuStack.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void UiManager::draw(Renderer& renderer) {
|
void UiManager::draw(Renderer& renderer, float projW, float projH) {
|
||||||
renderer.PushProjectionMatrix(Environment::projectionWidth, Environment::projectionHeight, -1, 1);
|
if (projW <= 0.0f) projW = Environment::projectionWidth;
|
||||||
|
if (projH <= 0.0f) projH = Environment::projectionHeight;
|
||||||
|
renderer.PushProjectionMatrix(projW, projH, -1, 1);
|
||||||
renderer.PushMatrix();
|
renderer.PushMatrix();
|
||||||
renderer.LoadIdentity();
|
renderer.LoadIdentity();
|
||||||
renderer.RenderUniform1f("uAlpha", 1.0f); // ensure alpha is fully opaque unless a widget overrides it
|
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)
|
// 1b. Chat bubble (composite widget)
|
||||||
if (node->chatBubble) {
|
if (node->chatBubble) {
|
||||||
node->chatBubble->draw(renderer, node->scaleX, node->scaleY);
|
node->chatBubble->draw(renderer, node->scaleX, node->scaleY, projW, projH);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Потом кнопки
|
// 2. Потом кнопки
|
||||||
@ -2136,6 +2154,7 @@ namespace ZL {
|
|||||||
std::shared_ptr<Texture> texLT, std::shared_ptr<Texture> texLB,
|
std::shared_ptr<Texture> texLT, std::shared_ptr<Texture> texLB,
|
||||||
std::shared_ptr<Texture> texRT, std::shared_ptr<Texture> texRB,
|
std::shared_ptr<Texture> texRT, std::shared_ptr<Texture> texRB,
|
||||||
Renderer& renderer, const std::string& fontPath, int fontSize,
|
Renderer& renderer, const std::string& fontPath, int fontSize,
|
||||||
|
bool isPortrait,
|
||||||
const std::string& zipFile)
|
const std::string& zipFile)
|
||||||
{
|
{
|
||||||
if (!root) return "";
|
if (!root) return "";
|
||||||
@ -2168,39 +2187,37 @@ namespace ZL {
|
|||||||
// Build the UiNode
|
// Build the UiNode
|
||||||
auto node = std::make_shared<UiNode>();
|
auto node = std::make_shared<UiNode>();
|
||||||
node->name = nodeName;
|
node->name = nodeName;
|
||||||
node->width = bubble->computeWidth();
|
node->width = isPortrait? bubble->computeWidthPortrait() : bubble->computeWidth();
|
||||||
node->height = bubble->computeHeight();
|
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
|
else
|
||||||
{
|
{
|
||||||
/*if (node->width == UiChatBubble::MAX_WIDTH)
|
if (incoming) {
|
||||||
{
|
node->localX = -210 + node->width * 0.5;
|
||||||
node->localX = node->width * 0.5 - 110 - (UiChatBubble::MAX_WIDTH - node->width);// -(425.f - node->width);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{*/
|
{
|
||||||
//node->localX = node->width * 0.5 - 110;
|
node->localX = 210 - node->width * 0.5;
|
||||||
//node->localX = - 110 +x;
|
}
|
||||||
node->localX = 210 - node->width * 0.5;
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
//430
|
|
||||||
//1280
|
|
||||||
|
|
||||||
node->localY = 0.0f;
|
node->localY = 0.0f;
|
||||||
node->visible = true;
|
node->visible = true;
|
||||||
node->chatBubble = bubble;
|
node->chatBubble = bubble;
|
||||||
|
|
||||||
node->layoutSettings.hGravity = HorizontalGravity::Center;
|
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).
|
// 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.
|
// The original static chat nodes all had "vertical_gravity": "bottom" for the same reason.
|
||||||
node->layoutSettings.vGravity = VerticalGravity::Bottom;
|
node->layoutSettings.vGravity = VerticalGravity::Bottom;
|
||||||
|
|||||||
@ -262,11 +262,14 @@ namespace ZL {
|
|||||||
|
|
||||||
static constexpr float CORNER_SIZE = 22.4f;
|
static constexpr float CORNER_SIZE = 22.4f;
|
||||||
static constexpr float MAX_WIDTH = 320.6f;
|
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_X = -10.0f;
|
||||||
static constexpr float PAD_Y = -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
|
// 7 meshes: middle-center, top-strip, bottom-strip, LT, RT, LB, RB
|
||||||
VertexRenderStruct meshMiddle;
|
VertexRenderStruct meshMiddle;
|
||||||
VertexRenderStruct meshTopStrip;
|
VertexRenderStruct meshTopStrip;
|
||||||
@ -282,10 +285,12 @@ namespace ZL {
|
|||||||
// Must be called after textRenderer is initialised
|
// Must be called after textRenderer is initialised
|
||||||
float computeWidth() const; // capped at MAX_WIDTH
|
float computeWidth() const; // capped at MAX_WIDTH
|
||||||
float computeHeight() const; // depends on computeWidth()
|
float computeHeight() const; // depends on computeWidth()
|
||||||
|
float computeWidthPortrait() const;
|
||||||
|
float computeHeightPortrait() const;
|
||||||
void buildMeshes();
|
void buildMeshes();
|
||||||
// nodeScaleX/Y: the owning UiNode's current scale (for pop-in / animations).
|
// 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.
|
// 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 {
|
struct UiNode {
|
||||||
@ -366,7 +371,7 @@ namespace ZL {
|
|||||||
void loadFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile = "");
|
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 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)
|
// Multi-touch methods (used directly for touch events with per-finger IDs)
|
||||||
void onTouchDown(int64_t fingerId, int x, int y);
|
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> texLT, std::shared_ptr<Texture> texLB,
|
||||||
std::shared_ptr<Texture> texRT, std::shared_ptr<Texture> texRB,
|
std::shared_ptr<Texture> texRT, std::shared_ptr<Texture> texRB,
|
||||||
Renderer& renderer, const std::string& fontPath, int fontSize,
|
Renderer& renderer, const std::string& fontPath, int fontSize,
|
||||||
|
bool isPortrait,
|
||||||
const std::string& zipFile);
|
const std::string& zipFile);
|
||||||
|
|
||||||
// Remove all chat-bubble children from named parent and clear chatBubbles collection.
|
// 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;
|
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 (!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");
|
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. Рендеринг
|
// 4. Рендеринг
|
||||||
r->shaderManager.PushShader(shaderName);
|
r->shaderManager.PushShader(shaderName);
|
||||||
|
|
||||||
// Матрица проекции — используем виртуальные проекционные размеры,
|
// Матрица проекции — используем переданные проекционные размеры.
|
||||||
// чтобы координаты текста были независимы от физического разрешения экрана.
|
// Это позволяет корректно рендерить текст как в основной сцене, так и внутри FBO.
|
||||||
float W = Environment::projectionWidth;
|
|
||||||
float H = Environment::projectionHeight;
|
|
||||||
Eigen::Matrix4f proj = Eigen::Matrix4f::Identity();
|
Eigen::Matrix4f proj = Eigen::Matrix4f::Identity();
|
||||||
proj(0, 0) = 2.0f / W;
|
proj(0, 0) = 2.0f / projW;
|
||||||
proj(1, 1) = 2.0f / H;
|
proj(1, 1) = 2.0f / projH;
|
||||||
// Сдвигаем проекцию так, чтобы локальные координаты меша (pen-origin=0,0) оказались в (tx,ty)
|
// Сдвигаем проекцию так, чтобы локальные координаты меша (pen-origin=0,0) оказались в (tx,ty)
|
||||||
proj(0, 3) = -1.0f + 2.0f * (tx) / W;
|
proj(0, 3) = -1.0f + 2.0f * (tx) / projW;
|
||||||
proj(1, 3) = -1.0f + 2.0f * (ty) / H;
|
proj(1, 3) = -1.0f + 2.0f * (ty) / projH;
|
||||||
|
|
||||||
//cached.mesh.RefreshVBO();
|
//cached.mesh.RefreshVBO();
|
||||||
|
|
||||||
|
|||||||
@ -26,7 +26,7 @@ public:
|
|||||||
~TextRenderer();
|
~TextRenderer();
|
||||||
|
|
||||||
bool init(Renderer& renderer, const std::string& ttfPath, int pixelSize, const std::string& zipfilename);
|
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 measureTextWidth(const std::string& text, float scale = 1.0f) const;
|
||||||
float getLineHeight(float scale = 1.0f) const { return lineHeight * scale; }
|
float getLineHeight(float scale = 1.0f) const { return lineHeight * scale; }
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user