Merge branch 'main' into linux
This commit is contained in:
commit
3094fc6112
@ -8,7 +8,7 @@
|
|||||||
body, html {
|
body, html {
|
||||||
margin: 0; padding: 0; width: 100%; height: 100%;
|
margin: 0; padding: 0; width: 100%; height: 100%;
|
||||||
overflow: hidden; background-color: #000;
|
overflow: hidden; background-color: #000;
|
||||||
position: fixed; /* Предотвращает pull-to-refresh на Android */
|
position: fixed;
|
||||||
}
|
}
|
||||||
#canvas {
|
#canvas {
|
||||||
display: block;
|
display: block;
|
||||||
@ -17,10 +17,23 @@
|
|||||||
width: 100vw; height: 100vh;
|
width: 100vw; height: 100vh;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#fs-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px; right: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
z-index: 10;
|
||||||
|
background: rgba(255,255,255,0.3);
|
||||||
|
color: white; border: 1px solid white;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: sans-serif;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
#status { color: white; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
|
#status { color: white; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<button id="fs-button">Fullscreen</button>
|
||||||
<div id="status">Downloading...</div>
|
<div id="status">Downloading...</div>
|
||||||
<canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex="-1"></canvas>
|
<canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex="-1"></canvas>
|
||||||
|
|
||||||
@ -36,12 +49,18 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Обработка ориентации
|
document.getElementById('fs-button').addEventListener('click', function() {
|
||||||
|
if (!document.fullscreenElement) {
|
||||||
|
document.documentElement.requestFullscreen().catch(e => {
|
||||||
|
console.error(`Error attempting to enable full-screen mode: ${e.message}`);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
document.exitFullscreen();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
window.addEventListener("orientationchange", function() {
|
window.addEventListener("orientationchange", function() {
|
||||||
// Chrome на Android обновляет innerWidth/Height не мгновенно.
|
|
||||||
// Ждем завершения анимации поворота.
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// В Emscripten это вызовет ваш onWindowResized в C++
|
|
||||||
window.dispatchEvent(new Event('resize'));
|
window.dispatchEvent(new Event('resize'));
|
||||||
}, 200);
|
}, 200);
|
||||||
});
|
});
|
||||||
|
|||||||
BIN
resources/black.png
(Stored with Git LFS)
Normal file
BIN
resources/black.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/blue_transparent.png
(Stored with Git LFS)
Normal file
BIN
resources/blue_transparent.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/button_players.png
(Stored with Git LFS)
Normal file
BIN
resources/button_players.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -8,18 +8,39 @@
|
|||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"type": "TextView",
|
"type": "TextView",
|
||||||
"name": "velocityText",
|
"name": "gameScoreText",
|
||||||
"x": 10,
|
"x": 0,
|
||||||
"y": 10,
|
"y": 30,
|
||||||
"width": 200,
|
"width": 200,
|
||||||
"height": 40,
|
"height": 60,
|
||||||
"horizontal_gravity": "left",
|
"horizontal_gravity": "left",
|
||||||
"vertical_gravity": "top",
|
"vertical_gravity": "top",
|
||||||
"text": "Velocity: 0",
|
"text": "Score: 0",
|
||||||
"fontSize": 24,
|
"fontSize": 36,
|
||||||
"color": [1.0, 1.0, 1.0, 1.0],
|
"color": [
|
||||||
|
0,
|
||||||
|
217,
|
||||||
|
255,
|
||||||
|
1
|
||||||
|
],
|
||||||
"centered": false
|
"centered": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "Button",
|
||||||
|
"name": "showPlayersButton",
|
||||||
|
"x": 0,
|
||||||
|
"y": 100,
|
||||||
|
"width": 150,
|
||||||
|
"height": 150,
|
||||||
|
"horizontal_gravity": "left",
|
||||||
|
"vertical_gravity": "top",
|
||||||
|
"textures": {
|
||||||
|
"normal": "resources/button_players.png",
|
||||||
|
"hover": "resources/button_players.png",
|
||||||
|
"pressed": "resources/button_players.png",
|
||||||
|
"disabled": "resources/button_players.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "Button",
|
"type": "Button",
|
||||||
"name": "shootButton",
|
"name": "shootButton",
|
||||||
|
|||||||
@ -139,11 +139,12 @@ namespace ZL {
|
|||||||
if (auto btn = uiManager.findButton("takeButton")) btn->state = ButtonState::Disabled;
|
if (auto btn = uiManager.findButton("takeButton")) btn->state = ButtonState::Disabled;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
auto velocityTv = uiManager.findTextView("velocityText");
|
auto velocityTv = uiManager.findTextView("velocityText");
|
||||||
if (velocityTv) {
|
if (velocityTv) {
|
||||||
velocityTv->rect.x = 10.0f;
|
velocityTv->rect.x = 10.0f;
|
||||||
velocityTv->rect.y = static_cast<float>(Environment::height) - velocityTv->rect.h - 10.0f;
|
velocityTv->rect.y = static_cast<float>(Environment::height) - velocityTv->rect.h - 10.0f;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
uiManager.setButtonPressCallback("shootButton", [this](const std::string&) {
|
uiManager.setButtonPressCallback("shootButton", [this](const std::string&) {
|
||||||
if (onFirePressed) onFirePressed();
|
if (onFirePressed) onFirePressed();
|
||||||
@ -184,6 +185,11 @@ namespace ZL {
|
|||||||
if (onTakeButtonPressed) onTakeButtonPressed();
|
if (onTakeButtonPressed) onTakeButtonPressed();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
uiManager.setButtonCallback("showPlayersButton", [this](const std::string&) {
|
||||||
|
if (onShowPlayersPressed) onShowPlayersPressed();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
uiManager.setSliderCallback("velocitySlider", [this](const std::string&, float value) {
|
uiManager.setSliderCallback("velocitySlider", [this](const std::string&, float value) {
|
||||||
int newVel = static_cast<int>(roundf(value * 10));
|
int newVel = static_cast<int>(roundf(value * 10));
|
||||||
|
|||||||
@ -73,6 +73,7 @@ namespace ZL {
|
|||||||
std::function<void()> onTakeButtonPressed;
|
std::function<void()> onTakeButtonPressed;
|
||||||
std::function<void(const std::string&, int)> onSingleplayerPressed;
|
std::function<void(const std::string&, int)> onSingleplayerPressed;
|
||||||
std::function<void(const std::string&, int)> onMultiplayerPressed;
|
std::function<void(const std::string&, int)> onMultiplayerPressed;
|
||||||
|
std::function<void()> onShowPlayersPressed;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZL
|
} // namespace ZL
|
||||||
184
src/Space.cpp
184
src/Space.cpp
@ -332,6 +332,10 @@ namespace ZL
|
|||||||
firePressed = true;
|
firePressed = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
menuManager.onShowPlayersPressed = [this]() {
|
||||||
|
buildAndShowPlayerList();
|
||||||
|
};
|
||||||
|
|
||||||
menuManager.onTakeButtonPressed = [this]() {
|
menuManager.onTakeButtonPressed = [this]() {
|
||||||
if (Environment::shipState.shipType != 1) return;
|
if (Environment::shipState.shipType != 1) return;
|
||||||
if (!networkClient) return;
|
if (!networkClient) return;
|
||||||
@ -349,6 +353,7 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
if (bestIdx >= 0) {
|
if (bestIdx >= 0) {
|
||||||
networkClient->Send("BOX_PICKUP:" + std::to_string(bestIdx));
|
networkClient->Send("BOX_PICKUP:" + std::to_string(bestIdx));
|
||||||
|
this->playerScore += 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1000,6 +1005,16 @@ namespace ZL
|
|||||||
|
|
||||||
int Space::pickTargetId() const
|
int Space::pickTargetId() const
|
||||||
{
|
{
|
||||||
|
// Use manually selected target if it's still alive and in range
|
||||||
|
if (manualTrackedTargetId >= 0) {
|
||||||
|
auto it = remotePlayerStates.find(manualTrackedTargetId);
|
||||||
|
if (it != remotePlayerStates.end() && !deadRemotePlayers.count(manualTrackedTargetId)) {
|
||||||
|
float d2 = (Environment::shipState.position - it->second.position).squaredNorm();
|
||||||
|
if (d2 <= TARGET_MAX_DIST_SQ) return manualTrackedTargetId;
|
||||||
|
}
|
||||||
|
// Target no longer valid — fall through to auto-pick
|
||||||
|
}
|
||||||
|
|
||||||
int bestId = -1;
|
int bestId = -1;
|
||||||
float bestDistSq = 1e30f;
|
float bestDistSq = 1e30f;
|
||||||
|
|
||||||
@ -1008,7 +1023,7 @@ namespace ZL
|
|||||||
|
|
||||||
float d2 = (Environment::shipState.position - st.position).squaredNorm();
|
float d2 = (Environment::shipState.position - st.position).squaredNorm();
|
||||||
|
|
||||||
if (d2 > TARGET_MAX_DIST_SQ) continue; // слишком далеко
|
if (d2 > TARGET_MAX_DIST_SQ) continue;
|
||||||
|
|
||||||
if (d2 < bestDistSq) {
|
if (d2 < bestDistSq) {
|
||||||
bestDistSq = d2;
|
bestDistSq = d2;
|
||||||
@ -1727,6 +1742,7 @@ namespace ZL
|
|||||||
|
|
||||||
std::cerr << "GAME OVER: collision with planet (moved back and exploded)\n";
|
std::cerr << "GAME OVER: collision with planet (moved back and exploded)\n";
|
||||||
|
|
||||||
|
clearPlayerListIfVisible();
|
||||||
menuManager.showGameOver(this->playerScore);
|
menuManager.showGameOver(this->playerScore);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -1802,6 +1818,7 @@ namespace ZL
|
|||||||
planetObject.planetStones.statuses[collidedTriIdx] = ChunkStatus::Empty;
|
planetObject.planetStones.statuses[collidedTriIdx] = ChunkStatus::Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearPlayerListIfVisible();
|
||||||
menuManager.showGameOver(this->playerScore);
|
menuManager.showGameOver(this->playerScore);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1837,6 +1854,11 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (playerScore != prevPlayerScore)
|
||||||
|
{
|
||||||
|
prevPlayerScore = playerScore;
|
||||||
|
menuManager.uiManager.setText("gameScoreText", "Score: " + std::to_string(playerScore));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Space::fireProjectiles() {
|
void Space::fireProjectiles() {
|
||||||
@ -1845,6 +1867,7 @@ namespace ZL
|
|||||||
Vector3f{ 1.5f, 0.9f - 6.f, 5.0f }
|
Vector3f{ 1.5f, 0.9f - 6.f, 5.0f }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const float projectileSpeed = PROJECTILE_VELOCITY;
|
const float projectileSpeed = PROJECTILE_VELOCITY;
|
||||||
const float lifeMs = PROJECTILE_LIFE;
|
const float lifeMs = PROJECTILE_LIFE;
|
||||||
const float size = 0.5f;
|
const float size = 0.5f;
|
||||||
@ -1878,6 +1901,7 @@ namespace ZL
|
|||||||
gameOver = true;
|
gameOver = true;
|
||||||
Environment::shipState.velocity = 0.0f;
|
Environment::shipState.velocity = 0.0f;
|
||||||
std::cout << "Client: Lost connection to server\n";
|
std::cout << "Client: Lost connection to server\n";
|
||||||
|
clearPlayerListIfVisible();
|
||||||
menuManager.showConnectionLost();
|
menuManager.showConnectionLost();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1916,6 +1940,7 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработка событий смерти, присланных сервером
|
// Обработка событий смерти, присланных сервером
|
||||||
auto deaths = networkClient->getPendingDeaths();
|
auto deaths = networkClient->getPendingDeaths();
|
||||||
if (!deaths.empty()) {
|
if (!deaths.empty()) {
|
||||||
@ -1940,10 +1965,12 @@ namespace ZL
|
|||||||
shipAlive = false;
|
shipAlive = false;
|
||||||
gameOver = true;
|
gameOver = true;
|
||||||
Environment::shipState.velocity = 0.0f;
|
Environment::shipState.velocity = 0.0f;
|
||||||
|
clearPlayerListIfVisible();
|
||||||
menuManager.showGameOver(this->playerScore);
|
menuManager.showGameOver(this->playerScore);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
deadRemotePlayers.insert(d.targetId);
|
deadRemotePlayers.insert(d.targetId);
|
||||||
|
if (d.targetId == manualTrackedTargetId) manualTrackedTargetId = -1;
|
||||||
std::cout << "Marked remote player " << d.targetId << " as dead" << std::endl;
|
std::cout << "Marked remote player " << d.targetId << " as dead" << std::endl;
|
||||||
}
|
}
|
||||||
if (d.killerId == localId) {
|
if (d.killerId == localId) {
|
||||||
@ -1952,6 +1979,7 @@ namespace ZL
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
rebuildPlayerListIfVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto respawns = networkClient->getPendingRespawns();
|
auto respawns = networkClient->getPendingRespawns();
|
||||||
@ -1969,6 +1997,7 @@ namespace ZL
|
|||||||
std::cout << "Client: Remote player " << respawnId << " respawned, removed from dead list" << std::endl;
|
std::cout << "Client: Remote player " << respawnId << " respawned, removed from dead list" << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
rebuildPlayerListIfVisible();
|
||||||
|
|
||||||
auto disconnects = networkClient->getPendingDisconnects();
|
auto disconnects = networkClient->getPendingDisconnects();
|
||||||
for (int pid : disconnects) {
|
for (int pid : disconnects) {
|
||||||
@ -1979,9 +2008,11 @@ namespace ZL
|
|||||||
trackedTargetId = -1;
|
trackedTargetId = -1;
|
||||||
targetAcquireAnim = 0.f;
|
targetAcquireAnim = 0.f;
|
||||||
}
|
}
|
||||||
|
if (pid == manualTrackedTargetId) manualTrackedTargetId = -1;
|
||||||
std::cout << "Client: Remote player " << pid << " left the game, removed from scene\n";
|
std::cout << "Client: Remote player " << pid << " left the game, removed from scene\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rebuildPlayerListIfVisible();
|
||||||
auto boxDestructions = networkClient->getPendingBoxDestructions();
|
auto boxDestructions = networkClient->getPendingBoxDestructions();
|
||||||
if (!boxDestructions.empty()) {
|
if (!boxDestructions.empty()) {
|
||||||
std::cout << "Game: Received " << boxDestructions.size() << " box destruction events" << std::endl;
|
std::cout << "Game: Received " << boxDestructions.size() << " box destruction events" << std::endl;
|
||||||
@ -2044,6 +2075,8 @@ namespace ZL
|
|||||||
|
|
||||||
void Space::handleDown(int mx, int my)
|
void Space::handleDown(int mx, int my)
|
||||||
{
|
{
|
||||||
|
if (playerListVisible) return;
|
||||||
|
|
||||||
Environment::tapDownHold = true;
|
Environment::tapDownHold = true;
|
||||||
|
|
||||||
Environment::tapDownStartPos(0) = mx;
|
Environment::tapDownStartPos(0) = mx;
|
||||||
@ -2061,6 +2094,8 @@ namespace ZL
|
|||||||
|
|
||||||
void Space::handleMotion(int mx, int my)
|
void Space::handleMotion(int mx, int my)
|
||||||
{
|
{
|
||||||
|
if (playerListVisible) return;
|
||||||
|
|
||||||
if (Environment::tapDownHold) {
|
if (Environment::tapDownHold) {
|
||||||
Environment::tapDownCurrentPos(0) = mx;
|
Environment::tapDownCurrentPos(0) = mx;
|
||||||
Environment::tapDownCurrentPos(1) = my;
|
Environment::tapDownCurrentPos(1) = my;
|
||||||
@ -2098,4 +2133,151 @@ namespace ZL
|
|||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::shared_ptr<UiNode> Space::buildPlayerListRoot()
|
||||||
|
{
|
||||||
|
const float btnW = 400;
|
||||||
|
const float btnH = 50.0f;
|
||||||
|
|
||||||
|
// Collect alive remote players
|
||||||
|
std::vector<std::pair<int, std::string>> players;
|
||||||
|
for (auto& kv : remotePlayerStates) {
|
||||||
|
if (!deadRemotePlayers.count(kv.first))
|
||||||
|
players.push_back({ kv.first, kv.second.nickname });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root: FrameLayout match_parent x match_parent
|
||||||
|
auto root = std::make_shared<UiNode>();
|
||||||
|
root->name = "playerListRoot";
|
||||||
|
root->layoutType = LayoutType::Frame;
|
||||||
|
root->width = -1.0f; // match_parent
|
||||||
|
root->height = -1.0f;
|
||||||
|
|
||||||
|
// List container: LinearLayout vertical, centered
|
||||||
|
float listH = btnH * (float)players.size();
|
||||||
|
auto listNode = std::make_shared<UiNode>();
|
||||||
|
listNode->name = "playerList";
|
||||||
|
listNode->layoutType = LayoutType::Linear;
|
||||||
|
listNode->orientation = Orientation::Vertical;
|
||||||
|
listNode->width = btnW;
|
||||||
|
listNode->height = listH;
|
||||||
|
listNode->layoutSettings.hGravity = HorizontalGravity::Center;
|
||||||
|
listNode->layoutSettings.vGravity = VerticalGravity::Center;
|
||||||
|
|
||||||
|
for (auto& [pid, nick] : players) {
|
||||||
|
auto btnNode = std::make_shared<UiNode>();
|
||||||
|
btnNode->name = "playerBtn_" + std::to_string(pid);
|
||||||
|
btnNode->layoutType = LayoutType::Frame;
|
||||||
|
btnNode->width = btnW;
|
||||||
|
btnNode->height = btnH;
|
||||||
|
|
||||||
|
|
||||||
|
auto tb = std::make_shared<UiTextButton>();
|
||||||
|
tb->name = btnNode->name;
|
||||||
|
tb->text = nick;
|
||||||
|
tb->fontSize = 20;
|
||||||
|
tb->color = { 1.f, 1.f, 1.f, 1.f };
|
||||||
|
tb->textCentered = true;
|
||||||
|
tb->textRenderer = std::make_unique<TextRenderer>();
|
||||||
|
if (!tb->textRenderer->init(renderer, tb->fontPath, tb->fontSize, CONST_ZIP_FILE)) {
|
||||||
|
std::cerr << "Failed to init TextRenderer for TextField: " << tb->name << std::endl;
|
||||||
|
}
|
||||||
|
//tb->texNormal = std::make_unique<Texture>(CreateTextureDataFromPng("resources/black.png", ""));
|
||||||
|
|
||||||
|
btnNode->textButton = tb;
|
||||||
|
/*auto button = std::make_shared<UiButton>();
|
||||||
|
button->name = "Hello";
|
||||||
|
button->texNormal = std::make_unique<Texture>(CreateTextureDataFromPng("resources/loading.png", ""));
|
||||||
|
btnNode->button = button;*/
|
||||||
|
listNode->children.push_back(btnNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backdrop: invisible full-screen TextButton — placed LAST so player buttons get priority
|
||||||
|
auto backdropNode = std::make_shared<UiNode>();
|
||||||
|
backdropNode->name = "playerListBackdrop";
|
||||||
|
backdropNode->layoutType = LayoutType::Frame;
|
||||||
|
backdropNode->width = -1.0f;
|
||||||
|
backdropNode->height = -1.0f;
|
||||||
|
auto backdropTb = std::make_shared<UiTextButton>();
|
||||||
|
backdropTb->name = "playerListBackdrop";
|
||||||
|
backdropNode->textButton = backdropTb;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
auto backgroundNode = std::make_shared<UiNode>();
|
||||||
|
backgroundNode->name = "playerListBackground";
|
||||||
|
backgroundNode->layoutType = LayoutType::Frame;
|
||||||
|
backgroundNode->width = btnW;
|
||||||
|
backgroundNode->height = listH;
|
||||||
|
backgroundNode->layoutSettings.hGravity = HorizontalGravity::Center;
|
||||||
|
backgroundNode->layoutSettings.vGravity = VerticalGravity::Center;
|
||||||
|
auto backdropImage = std::make_shared<UiStaticImage>();
|
||||||
|
backdropImage->name = "playerListBackgroundImage";
|
||||||
|
backdropImage->texture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/blue_transparent.png", ""));
|
||||||
|
backgroundNode->staticImage = backdropImage;
|
||||||
|
*/
|
||||||
|
root->children.push_back(listNode);
|
||||||
|
root->children.push_back(backdropNode);
|
||||||
|
//root->children.push_back(backgroundNode);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Space::buildAndShowPlayerList()
|
||||||
|
{
|
||||||
|
auto listRoot = buildPlayerListRoot();
|
||||||
|
menuManager.uiManager.pushMenuFromSavedRoot(listRoot);
|
||||||
|
menuManager.uiManager.updateAllLayouts();
|
||||||
|
playerListVisible = true;
|
||||||
|
|
||||||
|
for (auto& kv : remotePlayerStates) {
|
||||||
|
if (deadRemotePlayers.count(kv.first)) continue;
|
||||||
|
int pid = kv.first;
|
||||||
|
std::string btnName = "playerBtn_" + std::to_string(pid);
|
||||||
|
menuManager.uiManager.setTextButtonCallback(btnName, [this, pid](const std::string&) {
|
||||||
|
manualTrackedTargetId = pid;
|
||||||
|
closePlayerList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
menuManager.uiManager.setTextButtonCallback("playerListBackdrop", [this](const std::string&) {
|
||||||
|
closePlayerList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Space::closePlayerList()
|
||||||
|
{
|
||||||
|
menuManager.uiManager.popMenu();
|
||||||
|
menuManager.uiManager.updateAllLayouts();
|
||||||
|
playerListVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Space::rebuildPlayerListIfVisible()
|
||||||
|
{
|
||||||
|
if (!playerListVisible) return;
|
||||||
|
|
||||||
|
auto listRoot = buildPlayerListRoot();
|
||||||
|
menuManager.uiManager.replaceRoot(listRoot);
|
||||||
|
|
||||||
|
for (auto& kv : remotePlayerStates) {
|
||||||
|
if (deadRemotePlayers.count(kv.first)) continue;
|
||||||
|
int pid = kv.first;
|
||||||
|
std::string btnName = "playerBtn_" + std::to_string(pid);
|
||||||
|
menuManager.uiManager.setTextButtonCallback(btnName, [this, pid](const std::string&) {
|
||||||
|
manualTrackedTargetId = pid;
|
||||||
|
closePlayerList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
menuManager.uiManager.setTextButtonCallback("playerListBackdrop", [this](const std::string&) {
|
||||||
|
closePlayerList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Space::clearPlayerListIfVisible()
|
||||||
|
{
|
||||||
|
if (!playerListVisible) return;
|
||||||
|
menuManager.uiManager.clearMenuStack();
|
||||||
|
playerListVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ZL
|
} // namespace ZL
|
||||||
|
|||||||
11
src/Space.h
11
src/Space.h
@ -126,8 +126,12 @@ namespace ZL {
|
|||||||
|
|
||||||
std::unordered_set<int> deadRemotePlayers;
|
std::unordered_set<int> deadRemotePlayers;
|
||||||
int playerScore = 0;
|
int playerScore = 0;
|
||||||
|
int prevPlayerScore = 0;
|
||||||
bool wasConnectedToServer = false;
|
bool wasConnectedToServer = false;
|
||||||
|
|
||||||
|
bool playerListVisible = false;
|
||||||
|
int manualTrackedTargetId = -1;
|
||||||
|
|
||||||
static constexpr float TARGET_MAX_DIST = 50000.0f;
|
static constexpr float TARGET_MAX_DIST = 50000.0f;
|
||||||
static constexpr float TARGET_MAX_DIST_SQ = TARGET_MAX_DIST * TARGET_MAX_DIST;
|
static constexpr float TARGET_MAX_DIST_SQ = TARGET_MAX_DIST * TARGET_MAX_DIST;
|
||||||
|
|
||||||
@ -146,6 +150,13 @@ namespace ZL {
|
|||||||
void resetPlayerState();
|
void resetPlayerState();
|
||||||
void clearTextRendererCache();
|
void clearTextRendererCache();
|
||||||
|
|
||||||
|
// Player list overlay
|
||||||
|
void buildAndShowPlayerList();
|
||||||
|
void closePlayerList();
|
||||||
|
void rebuildPlayerListIfVisible();
|
||||||
|
void clearPlayerListIfVisible();
|
||||||
|
std::shared_ptr<UiNode> buildPlayerListRoot();
|
||||||
|
|
||||||
void updateSparkEmitters(float deltaMs);
|
void updateSparkEmitters(float deltaMs);
|
||||||
void prepareSparkEmittersForDraw();
|
void prepareSparkEmittersForDraw();
|
||||||
void drawShipSparkEmitters();
|
void drawShipSparkEmitters();
|
||||||
|
|||||||
@ -72,6 +72,65 @@ namespace ZL {
|
|||||||
renderer.PopMatrix();
|
renderer.PopMatrix();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UiTextButton::buildMesh() {
|
||||||
|
mesh.data.PositionData.clear();
|
||||||
|
mesh.data.TexCoordData.clear();
|
||||||
|
|
||||||
|
float x0 = rect.x;
|
||||||
|
float y0 = rect.y;
|
||||||
|
float x1 = rect.x + rect.w;
|
||||||
|
float y1 = rect.y + rect.h;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UiTextButton::draw(Renderer& renderer) const {
|
||||||
|
renderer.PushMatrix();
|
||||||
|
renderer.TranslateMatrix({ animOffsetX, animOffsetY, 0.0f });
|
||||||
|
renderer.ScaleMatrix({ animScaleX, animScaleY, 1.0f });
|
||||||
|
|
||||||
|
// Draw background texture (optional)
|
||||||
|
const std::shared_ptr<Texture>* tex = nullptr;
|
||||||
|
switch (state) {
|
||||||
|
case ButtonState::Normal: if (texNormal) tex = &texNormal; break;
|
||||||
|
case ButtonState::Hover: tex = texHover ? &texHover : (texNormal ? &texNormal : nullptr); break;
|
||||||
|
case ButtonState::Pressed: tex = texPressed ? &texPressed : (texNormal ? &texNormal : nullptr); break;
|
||||||
|
case ButtonState::Disabled: tex = texDisabled ? &texDisabled : (texNormal ? &texNormal : nullptr); break;
|
||||||
|
}
|
||||||
|
if (tex && *tex) {
|
||||||
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, (*tex)->getTexID());
|
||||||
|
renderer.DrawVertexRenderStruct(mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.PopMatrix();
|
||||||
|
|
||||||
|
// Draw text on top (uses absolute coords, add anim offset manually)
|
||||||
|
if (textRenderer && !text.empty()) {
|
||||||
|
float cx = rect.x + rect.w / 2.0f + animOffsetX;
|
||||||
|
float cy = rect.y + rect.h / 2.0f + animOffsetY;
|
||||||
|
textRenderer->drawText(text, cx, cy, 1.0f, textCentered, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void UiSlider::buildTrackMesh() {
|
void UiSlider::buildTrackMesh() {
|
||||||
trackMesh.data.PositionData.clear();
|
trackMesh.data.PositionData.clear();
|
||||||
trackMesh.data.TexCoordData.clear();
|
trackMesh.data.TexCoordData.clear();
|
||||||
@ -393,6 +452,49 @@ namespace ZL {
|
|||||||
node->textField = tf;
|
node->textField = tf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeStr == "TextButton") {
|
||||||
|
auto tb = std::make_shared<UiTextButton>();
|
||||||
|
tb->name = node->name;
|
||||||
|
tb->rect = initialRect;
|
||||||
|
tb->border = j.value("border", 0.0f);
|
||||||
|
|
||||||
|
// Textures are optional
|
||||||
|
if (j.contains("textures") && j["textures"].is_object()) {
|
||||||
|
auto t = j["textures"];
|
||||||
|
auto loadTex = [&](const std::string& key) -> std::shared_ptr<Texture> {
|
||||||
|
if (!t.contains(key) || !t[key].is_string()) return nullptr;
|
||||||
|
std::string path = t[key].get<std::string>();
|
||||||
|
try {
|
||||||
|
auto data = CreateTextureDataFromPng(path.c_str(), zipFile.c_str());
|
||||||
|
return std::make_shared<Texture>(data);
|
||||||
|
}
|
||||||
|
catch (const std::exception& e) {
|
||||||
|
std::cerr << "UiManager: TextButton '" << tb->name << "' failed to load texture " << path << ": " << e.what() << std::endl;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
tb->texNormal = loadTex("normal");
|
||||||
|
tb->texHover = loadTex("hover");
|
||||||
|
tb->texPressed = loadTex("pressed");
|
||||||
|
tb->texDisabled = loadTex("disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j.contains("text")) tb->text = j["text"].get<std::string>();
|
||||||
|
if (j.contains("fontPath")) tb->fontPath = j["fontPath"].get<std::string>();
|
||||||
|
if (j.contains("fontSize")) tb->fontSize = j["fontSize"].get<int>();
|
||||||
|
if (j.contains("textCentered")) tb->textCentered = j["textCentered"].get<bool>();
|
||||||
|
if (j.contains("color") && j["color"].is_array() && j["color"].size() == 4) {
|
||||||
|
for (int i = 0; i < 4; ++i) tb->color[i] = j["color"][i].get<float>();
|
||||||
|
}
|
||||||
|
|
||||||
|
tb->textRenderer = std::make_unique<TextRenderer>();
|
||||||
|
if (!tb->textRenderer->init(renderer, tb->fontPath, tb->fontSize, zipFile)) {
|
||||||
|
std::cerr << "UiManager: Failed to init TextRenderer for TextButton: " << tb->name << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
node->textButton = tb;
|
||||||
|
}
|
||||||
|
|
||||||
if (j.contains("animations") && j["animations"].is_object()) {
|
if (j.contains("animations") && j["animations"].is_object()) {
|
||||||
for (auto it = j["animations"].begin(); it != j["animations"].end(); ++it) {
|
for (auto it = j["animations"].begin(); it != j["animations"].end(); ++it) {
|
||||||
std::string animName = it.key();
|
std::string animName = it.key();
|
||||||
@ -536,6 +638,7 @@ namespace ZL {
|
|||||||
root->localY // finalLocalY
|
root->localY // finalLocalY
|
||||||
);
|
);
|
||||||
buttons.clear();
|
buttons.clear();
|
||||||
|
textButtons.clear();
|
||||||
sliders.clear();
|
sliders.clear();
|
||||||
textViews.clear();
|
textViews.clear();
|
||||||
textFields.clear();
|
textFields.clear();
|
||||||
@ -547,6 +650,9 @@ namespace ZL {
|
|||||||
for (auto& b : buttons) {
|
for (auto& b : buttons) {
|
||||||
b->buildMesh();
|
b->buildMesh();
|
||||||
}
|
}
|
||||||
|
for (auto& tb : textButtons) {
|
||||||
|
tb->buildMesh();
|
||||||
|
}
|
||||||
for (auto& s : sliders) {
|
for (auto& s : sliders) {
|
||||||
s->buildTrackMesh();
|
s->buildTrackMesh();
|
||||||
s->buildKnobMesh();
|
s->buildKnobMesh();
|
||||||
@ -694,11 +800,14 @@ namespace ZL {
|
|||||||
// 1. Обновляем кнопку
|
// 1. Обновляем кнопку
|
||||||
if (node->button) {
|
if (node->button) {
|
||||||
node->button->rect = node->screenRect;
|
node->button->rect = node->screenRect;
|
||||||
// Если у кнопки есть анимационные смещения, они учитываются внутри buildMesh
|
|
||||||
// или при рендеринге через Uniform-переменные матрицы модели.
|
|
||||||
node->button->buildMesh();
|
node->button->buildMesh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node->textButton) {
|
||||||
|
node->textButton->rect = node->screenRect;
|
||||||
|
node->textButton->buildMesh();
|
||||||
|
}
|
||||||
|
|
||||||
// 2. Обновляем слайдер
|
// 2. Обновляем слайдер
|
||||||
if (node->slider) {
|
if (node->slider) {
|
||||||
node->slider->rect = node->screenRect;
|
node->slider->rect = node->screenRect;
|
||||||
@ -744,6 +853,9 @@ namespace ZL {
|
|||||||
if (node->button) {
|
if (node->button) {
|
||||||
buttons.push_back(node->button);
|
buttons.push_back(node->button);
|
||||||
}
|
}
|
||||||
|
if (node->textButton) {
|
||||||
|
textButtons.push_back(node->textButton);
|
||||||
|
}
|
||||||
if (node->slider) {
|
if (node->slider) {
|
||||||
sliders.push_back(node->slider);
|
sliders.push_back(node->slider);
|
||||||
}
|
}
|
||||||
@ -862,10 +974,13 @@ namespace ZL {
|
|||||||
MenuState prev;
|
MenuState prev;
|
||||||
prev.root = root;
|
prev.root = root;
|
||||||
prev.buttons = buttons;
|
prev.buttons = buttons;
|
||||||
|
prev.textButtons = textButtons;
|
||||||
prev.sliders = sliders;
|
prev.sliders = sliders;
|
||||||
|
prev.textViews = textViews;
|
||||||
prev.textFields = textFields;
|
prev.textFields = textFields;
|
||||||
prev.staticImages = staticImages;
|
prev.staticImages = staticImages;
|
||||||
prev.pressedButtons = pressedButtons;
|
prev.pressedButtons = pressedButtons;
|
||||||
|
prev.pressedTextButtons = pressedTextButtons;
|
||||||
prev.pressedSliders = pressedSliders;
|
prev.pressedSliders = pressedSliders;
|
||||||
prev.focusedTextField = focusedTextField;
|
prev.focusedTextField = focusedTextField;
|
||||||
prev.path = "";
|
prev.path = "";
|
||||||
@ -884,6 +999,14 @@ namespace ZL {
|
|||||||
b->animScaleY = 1.0f;
|
b->animScaleY = 1.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (auto& tb : textButtons) {
|
||||||
|
if (tb) {
|
||||||
|
tb->animOffsetX = 0.0f;
|
||||||
|
tb->animOffsetY = 0.0f;
|
||||||
|
tb->animScaleX = 1.0f;
|
||||||
|
tb->animScaleY = 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
replaceRoot(newRoot);
|
replaceRoot(newRoot);
|
||||||
menuStack.push_back(std::move(prev));
|
menuStack.push_back(std::move(prev));
|
||||||
@ -913,10 +1036,13 @@ namespace ZL {
|
|||||||
|
|
||||||
root = s.root;
|
root = s.root;
|
||||||
buttons = s.buttons;
|
buttons = s.buttons;
|
||||||
|
textButtons = s.textButtons;
|
||||||
sliders = s.sliders;
|
sliders = s.sliders;
|
||||||
|
textViews = s.textViews;
|
||||||
textFields = s.textFields;
|
textFields = s.textFields;
|
||||||
staticImages = s.staticImages;
|
staticImages = s.staticImages;
|
||||||
pressedButtons = s.pressedButtons;
|
pressedButtons = s.pressedButtons;
|
||||||
|
pressedTextButtons = s.pressedTextButtons;
|
||||||
pressedSliders = s.pressedSliders;
|
pressedSliders = s.pressedSliders;
|
||||||
focusedTextField = s.focusedTextField;
|
focusedTextField = s.focusedTextField;
|
||||||
|
|
||||||
@ -931,6 +1057,15 @@ namespace ZL {
|
|||||||
b->buildMesh();
|
b->buildMesh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (auto& tb : textButtons) {
|
||||||
|
if (tb) {
|
||||||
|
tb->animOffsetX = 0.0f;
|
||||||
|
tb->animOffsetY = 0.0f;
|
||||||
|
tb->animScaleX = 1.0f;
|
||||||
|
tb->animScaleY = 1.0f;
|
||||||
|
tb->buildMesh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (auto& sl : sliders) {
|
for (auto& sl : sliders) {
|
||||||
if (sl) {
|
if (sl) {
|
||||||
@ -956,6 +1091,9 @@ namespace ZL {
|
|||||||
for (const auto& b : buttons) {
|
for (const auto& b : buttons) {
|
||||||
b->draw(renderer);
|
b->draw(renderer);
|
||||||
}
|
}
|
||||||
|
for (const auto& tb : textButtons) {
|
||||||
|
tb->draw(renderer);
|
||||||
|
}
|
||||||
for (const auto& s : sliders) {
|
for (const auto& s : sliders) {
|
||||||
s->draw(renderer);
|
s->draw(renderer);
|
||||||
}
|
}
|
||||||
@ -1007,6 +1145,12 @@ namespace ZL {
|
|||||||
node->button->animScaleX = act.origScaleX;
|
node->button->animScaleX = act.origScaleX;
|
||||||
node->button->animScaleY = act.origScaleY;
|
node->button->animScaleY = act.origScaleY;
|
||||||
}
|
}
|
||||||
|
if (node->textButton) {
|
||||||
|
node->textButton->animOffsetX = act.origOffsetX;
|
||||||
|
node->textButton->animOffsetY = act.origOffsetY;
|
||||||
|
node->textButton->animScaleX = act.origScaleX;
|
||||||
|
node->textButton->animScaleY = act.origScaleY;
|
||||||
|
}
|
||||||
act.stepIndex = 0;
|
act.stepIndex = 0;
|
||||||
act.elapsedMs = 0.0f;
|
act.elapsedMs = 0.0f;
|
||||||
act.stepStarted = false;
|
act.stepStarted = false;
|
||||||
@ -1028,12 +1172,20 @@ namespace ZL {
|
|||||||
node->button->animOffsetX = step.toX;
|
node->button->animOffsetX = step.toX;
|
||||||
node->button->animOffsetY = step.toY;
|
node->button->animOffsetY = step.toY;
|
||||||
}
|
}
|
||||||
|
if (node->textButton) {
|
||||||
|
node->textButton->animOffsetX = step.toX;
|
||||||
|
node->textButton->animOffsetY = step.toY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (step.type == "scale") {
|
else if (step.type == "scale") {
|
||||||
if (node->button) {
|
if (node->button) {
|
||||||
node->button->animScaleX = step.toX;
|
node->button->animScaleX = step.toX;
|
||||||
node->button->animScaleY = step.toY;
|
node->button->animScaleY = step.toY;
|
||||||
}
|
}
|
||||||
|
if (node->textButton) {
|
||||||
|
node->textButton->animScaleX = step.toX;
|
||||||
|
node->textButton->animScaleY = step.toY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
act.stepIndex++;
|
act.stepIndex++;
|
||||||
act.elapsedMs = 0.0f;
|
act.elapsedMs = 0.0f;
|
||||||
@ -1048,6 +1200,12 @@ namespace ZL {
|
|||||||
act.origScaleX = node->button->animScaleX;
|
act.origScaleX = node->button->animScaleX;
|
||||||
act.origScaleY = node->button->animScaleY;
|
act.origScaleY = node->button->animScaleY;
|
||||||
}
|
}
|
||||||
|
else if (node->textButton) {
|
||||||
|
act.origOffsetX = node->textButton->animOffsetX;
|
||||||
|
act.origOffsetY = node->textButton->animOffsetY;
|
||||||
|
act.origScaleX = node->textButton->animScaleX;
|
||||||
|
act.origScaleY = node->textButton->animScaleY;
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
act.origOffsetX = act.origOffsetY = 0.0f;
|
act.origOffsetX = act.origOffsetY = 0.0f;
|
||||||
act.origScaleX = act.origScaleY = 1.0f;
|
act.origScaleX = act.origScaleY = 1.0f;
|
||||||
@ -1064,6 +1222,12 @@ namespace ZL {
|
|||||||
act.startScaleX = node->button->animScaleX;
|
act.startScaleX = node->button->animScaleX;
|
||||||
act.startScaleY = node->button->animScaleY;
|
act.startScaleY = node->button->animScaleY;
|
||||||
}
|
}
|
||||||
|
else if (node->textButton) {
|
||||||
|
act.startOffsetX = node->textButton->animOffsetX;
|
||||||
|
act.startOffsetY = node->textButton->animOffsetY;
|
||||||
|
act.startScaleX = node->textButton->animScaleX;
|
||||||
|
act.startScaleY = node->textButton->animScaleY;
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
act.startOffsetX = act.startOffsetY = 0.0f;
|
act.startOffsetX = act.startOffsetY = 0.0f;
|
||||||
act.startScaleX = act.startScaleY = 1.0f;
|
act.startScaleX = act.startScaleY = 1.0f;
|
||||||
@ -1089,6 +1253,10 @@ namespace ZL {
|
|||||||
node->button->animOffsetX = nx;
|
node->button->animOffsetX = nx;
|
||||||
node->button->animOffsetY = ny;
|
node->button->animOffsetY = ny;
|
||||||
}
|
}
|
||||||
|
if (node->textButton) {
|
||||||
|
node->textButton->animOffsetX = nx;
|
||||||
|
node->textButton->animOffsetY = ny;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (step.type == "scale") {
|
else if (step.type == "scale") {
|
||||||
float sx = act.startScaleX + (act.endScaleX - act.startScaleX) * te;
|
float sx = act.startScaleX + (act.endScaleX - act.startScaleX) * te;
|
||||||
@ -1097,6 +1265,10 @@ namespace ZL {
|
|||||||
node->button->animScaleX = sx;
|
node->button->animScaleX = sx;
|
||||||
node->button->animScaleY = sy;
|
node->button->animScaleY = sy;
|
||||||
}
|
}
|
||||||
|
if (node->textButton) {
|
||||||
|
node->textButton->animScaleX = sx;
|
||||||
|
node->textButton->animScaleY = sy;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (step.type == "wait") {
|
else if (step.type == "wait") {
|
||||||
//wait
|
//wait
|
||||||
@ -1146,6 +1318,17 @@ namespace ZL {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (auto& tb : textButtons) {
|
||||||
|
if (tb->state != ButtonState::Disabled)
|
||||||
|
{
|
||||||
|
if (tb->rect.containsConsideringBorder((float)x, (float)y, tb->border)) {
|
||||||
|
if (tb->state != ButtonState::Pressed) tb->state = ButtonState::Hover;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (tb->state != ButtonState::Pressed) tb->state = ButtonState::Normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto it = pressedSliders.find(fingerId);
|
auto it = pressedSliders.find(fingerId);
|
||||||
@ -1180,6 +1363,18 @@ namespace ZL {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto& tb : textButtons) {
|
||||||
|
if (tb->state != ButtonState::Disabled)
|
||||||
|
{
|
||||||
|
if (tb->rect.containsConsideringBorder((float)x, (float)y, tb->border)) {
|
||||||
|
tb->state = ButtonState::Pressed;
|
||||||
|
pressedTextButtons[fingerId] = tb;
|
||||||
|
if (tb->onPress) tb->onPress(tb->name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (auto& s : sliders) {
|
for (auto& s : sliders) {
|
||||||
if (s->rect.contains((float)x, (float)y)) {
|
if (s->rect.contains((float)x, (float)y)) {
|
||||||
pressedSliders[fingerId] = s;
|
pressedSliders[fingerId] = s;
|
||||||
@ -1212,6 +1407,7 @@ namespace ZL {
|
|||||||
|
|
||||||
void UiManager::onTouchUp(int64_t fingerId, int x, int y) {
|
void UiManager::onTouchUp(int64_t fingerId, int x, int y) {
|
||||||
std::vector<std::shared_ptr<UiButton>> clicked;
|
std::vector<std::shared_ptr<UiButton>> clicked;
|
||||||
|
std::vector<std::shared_ptr<UiTextButton>> clickedText;
|
||||||
|
|
||||||
auto btnIt = pressedButtons.find(fingerId);
|
auto btnIt = pressedButtons.find(fingerId);
|
||||||
if (btnIt != pressedButtons.end()) {
|
if (btnIt != pressedButtons.end()) {
|
||||||
@ -1229,6 +1425,21 @@ namespace ZL {
|
|||||||
pressedButtons.erase(btnIt);
|
pressedButtons.erase(btnIt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto tbIt = pressedTextButtons.find(fingerId);
|
||||||
|
if (tbIt != pressedTextButtons.end()) {
|
||||||
|
auto tb = tbIt->second;
|
||||||
|
if (tb) {
|
||||||
|
bool contains = tb->rect.contains((float)x, (float)y);
|
||||||
|
if (tb->state == ButtonState::Pressed) {
|
||||||
|
if (contains) {
|
||||||
|
clickedText.push_back(tb);
|
||||||
|
}
|
||||||
|
tb->state = (contains && fingerId == MOUSE_FINGER_ID) ? ButtonState::Hover : ButtonState::Normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pressedTextButtons.erase(tbIt);
|
||||||
|
}
|
||||||
|
|
||||||
pressedSliders.erase(fingerId);
|
pressedSliders.erase(fingerId);
|
||||||
|
|
||||||
for (auto& b : clicked) {
|
for (auto& b : clicked) {
|
||||||
@ -1236,6 +1447,11 @@ namespace ZL {
|
|||||||
b->onClick(b->name);
|
b->onClick(b->name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (auto& tb : clickedText) {
|
||||||
|
if (tb->onClick) {
|
||||||
|
tb->onClick(tb->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UiManager::onKeyPress(unsigned char key) {
|
void UiManager::onKeyPress(unsigned char key) {
|
||||||
@ -1287,6 +1503,12 @@ namespace ZL {
|
|||||||
aa.origScaleX = node->button->animScaleX;
|
aa.origScaleX = node->button->animScaleX;
|
||||||
aa.origScaleY = node->button->animScaleY;
|
aa.origScaleY = node->button->animScaleY;
|
||||||
}
|
}
|
||||||
|
else if (node->textButton) {
|
||||||
|
aa.origOffsetX = node->textButton->animOffsetX;
|
||||||
|
aa.origOffsetY = node->textButton->animOffsetY;
|
||||||
|
aa.origScaleX = node->textButton->animScaleX;
|
||||||
|
aa.origScaleY = node->textButton->animScaleY;
|
||||||
|
}
|
||||||
auto cbIt = animCallbacks.find({ nodeName, animName });
|
auto cbIt = animCallbacks.find({ nodeName, animName });
|
||||||
if (cbIt != animCallbacks.end()) aa.onComplete = cbIt->second;
|
if (cbIt != animCallbacks.end()) aa.onComplete = cbIt->second;
|
||||||
nodeActiveAnims[node].push_back(std::move(aa));
|
nodeActiveAnims[node].push_back(std::move(aa));
|
||||||
@ -1337,6 +1559,12 @@ namespace ZL {
|
|||||||
aa.origScaleX = n->button->animScaleX;
|
aa.origScaleX = n->button->animScaleX;
|
||||||
aa.origScaleY = n->button->animScaleY;
|
aa.origScaleY = n->button->animScaleY;
|
||||||
}
|
}
|
||||||
|
else if (n->textButton) {
|
||||||
|
aa.origOffsetX = n->textButton->animOffsetX;
|
||||||
|
aa.origOffsetY = n->textButton->animOffsetY;
|
||||||
|
aa.origScaleX = n->textButton->animScaleX;
|
||||||
|
aa.origScaleY = n->textButton->animScaleY;
|
||||||
|
}
|
||||||
auto cbIt = animCallbacks.find({ n->name, animName });
|
auto cbIt = animCallbacks.find({ n->name, animName });
|
||||||
if (cbIt != animCallbacks.end()) aa.onComplete = cbIt->second;
|
if (cbIt != animCallbacks.end()) aa.onComplete = cbIt->second;
|
||||||
nodeActiveAnims[n].push_back(std::move(aa));
|
nodeActiveAnims[n].push_back(std::move(aa));
|
||||||
@ -1374,4 +1602,36 @@ namespace ZL {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<UiTextButton> UiManager::findTextButton(const std::string& name) {
|
||||||
|
for (auto& tb : textButtons) if (tb->name == name) return tb;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UiManager::setTextButtonCallback(const std::string& name, std::function<void(const std::string&)> cb) {
|
||||||
|
auto tb = findTextButton(name);
|
||||||
|
if (!tb) {
|
||||||
|
std::cerr << "UiManager: setTextButtonCallback failed, textButton not found: " << name << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
tb->onClick = std::move(cb);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UiManager::setTextButtonPressCallback(const std::string& name, std::function<void(const std::string&)> cb) {
|
||||||
|
auto tb = findTextButton(name);
|
||||||
|
if (!tb) {
|
||||||
|
std::cerr << "UiManager: setTextButtonPressCallback failed, textButton not found: " << name << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
tb->onPress = std::move(cb);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UiManager::setTextButtonText(const std::string& name, const std::string& newText) {
|
||||||
|
auto tb = findTextButton(name);
|
||||||
|
if (!tb) return false;
|
||||||
|
tb->text = newText;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ZL
|
} // namespace ZL
|
||||||
@ -125,6 +125,42 @@ namespace ZL {
|
|||||||
void draw(Renderer& renderer) const;
|
void draw(Renderer& renderer) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct UiTextButton {
|
||||||
|
std::string name;
|
||||||
|
UiRect rect;
|
||||||
|
float border = 0;
|
||||||
|
|
||||||
|
// Textures are optional — button can be text-only
|
||||||
|
std::shared_ptr<Texture> texNormal;
|
||||||
|
std::shared_ptr<Texture> texHover;
|
||||||
|
std::shared_ptr<Texture> texPressed;
|
||||||
|
std::shared_ptr<Texture> texDisabled;
|
||||||
|
|
||||||
|
ButtonState state = ButtonState::Normal;
|
||||||
|
VertexRenderStruct mesh;
|
||||||
|
|
||||||
|
// Text drawn on top of the button
|
||||||
|
std::string text;
|
||||||
|
std::string fontPath = "resources/fonts/DroidSans.ttf";
|
||||||
|
int fontSize = 32;
|
||||||
|
std::array<float, 4> color = { 1.f, 1.f, 1.f, 1.f };
|
||||||
|
bool textCentered = true;
|
||||||
|
|
||||||
|
std::unique_ptr<TextRenderer> textRenderer;
|
||||||
|
|
||||||
|
std::function<void(const std::string&)> onClick;
|
||||||
|
std::function<void(const std::string&)> onPress;
|
||||||
|
|
||||||
|
// Animation runtime
|
||||||
|
float animOffsetX = 0.0f;
|
||||||
|
float animOffsetY = 0.0f;
|
||||||
|
float animScaleX = 1.0f;
|
||||||
|
float animScaleY = 1.0f;
|
||||||
|
|
||||||
|
void buildMesh();
|
||||||
|
void draw(Renderer& renderer) const;
|
||||||
|
};
|
||||||
|
|
||||||
struct UiTextView {
|
struct UiTextView {
|
||||||
std::string name;
|
std::string name;
|
||||||
UiRect rect;
|
UiRect rect;
|
||||||
@ -197,6 +233,7 @@ namespace ZL {
|
|||||||
|
|
||||||
// Компоненты (только один из них обычно активен для ноды)
|
// Компоненты (только один из них обычно активен для ноды)
|
||||||
std::shared_ptr<UiButton> button;
|
std::shared_ptr<UiButton> button;
|
||||||
|
std::shared_ptr<UiTextButton> textButton;
|
||||||
std::shared_ptr<UiSlider> slider;
|
std::shared_ptr<UiSlider> slider;
|
||||||
std::shared_ptr<UiTextView> textView;
|
std::shared_ptr<UiTextView> textView;
|
||||||
std::shared_ptr<UiTextField> textField;
|
std::shared_ptr<UiTextField> textField;
|
||||||
@ -249,12 +286,12 @@ namespace ZL {
|
|||||||
|
|
||||||
// Returns true if any finger is currently interacting with UI
|
// Returns true if any finger is currently interacting with UI
|
||||||
bool isUiInteraction() const {
|
bool isUiInteraction() const {
|
||||||
return !pressedButtons.empty() || !pressedSliders.empty() || focusedTextField != nullptr;
|
return !pressedButtons.empty() || !pressedTextButtons.empty() || !pressedSliders.empty() || focusedTextField != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if this specific finger is currently interacting with UI
|
// Returns true if this specific finger is currently interacting with UI
|
||||||
bool isUiInteractionForFinger(int64_t fingerId) const {
|
bool isUiInteractionForFinger(int64_t fingerId) const {
|
||||||
return pressedButtons.count(fingerId) > 0 || pressedSliders.count(fingerId) > 0 || focusedTextField != nullptr;
|
return pressedButtons.count(fingerId) > 0 || pressedTextButtons.count(fingerId) > 0 || pressedSliders.count(fingerId) > 0 || focusedTextField != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void stopAllAnimations() {
|
void stopAllAnimations() {
|
||||||
@ -268,6 +305,14 @@ namespace ZL {
|
|||||||
b->animScaleY = 1.0f;
|
b->animScaleY = 1.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (auto& tb : textButtons) {
|
||||||
|
if (tb) {
|
||||||
|
tb->animOffsetX = 0.0f;
|
||||||
|
tb->animOffsetY = 0.0f;
|
||||||
|
tb->animScaleX = 1.0f;
|
||||||
|
tb->animScaleY = 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<UiButton> findButton(const std::string& name);
|
std::shared_ptr<UiButton> findButton(const std::string& name);
|
||||||
@ -275,6 +320,11 @@ namespace ZL {
|
|||||||
bool setButtonCallback(const std::string& name, std::function<void(const std::string&)> cb);
|
bool setButtonCallback(const std::string& name, std::function<void(const std::string&)> cb);
|
||||||
bool setButtonPressCallback(const std::string& name, std::function<void(const std::string&)> cb);
|
bool setButtonPressCallback(const std::string& name, std::function<void(const std::string&)> cb);
|
||||||
|
|
||||||
|
std::shared_ptr<UiTextButton> findTextButton(const std::string& name);
|
||||||
|
bool setTextButtonCallback(const std::string& name, std::function<void(const std::string&)> cb);
|
||||||
|
bool setTextButtonPressCallback(const std::string& name, std::function<void(const std::string&)> cb);
|
||||||
|
bool setTextButtonText(const std::string& name, const std::string& newText);
|
||||||
|
|
||||||
bool addSlider(const std::string& name, const UiRect& rect, Renderer& renderer, const std::string& zipFile,
|
bool addSlider(const std::string& name, const UiRect& rect, Renderer& renderer, const std::string& zipFile,
|
||||||
const std::string& trackPath, const std::string& knobPath, float initialValue = 0.0f, bool vertical = true);
|
const std::string& trackPath, const std::string& knobPath, float initialValue = 0.0f, bool vertical = true);
|
||||||
|
|
||||||
@ -333,6 +383,7 @@ namespace ZL {
|
|||||||
|
|
||||||
std::shared_ptr<UiNode> root;
|
std::shared_ptr<UiNode> root;
|
||||||
std::vector<std::shared_ptr<UiButton>> buttons;
|
std::vector<std::shared_ptr<UiButton>> buttons;
|
||||||
|
std::vector<std::shared_ptr<UiTextButton>> textButtons;
|
||||||
std::vector<std::shared_ptr<UiSlider>> sliders;
|
std::vector<std::shared_ptr<UiSlider>> sliders;
|
||||||
std::vector<std::shared_ptr<UiTextView>> textViews;
|
std::vector<std::shared_ptr<UiTextView>> textViews;
|
||||||
std::vector<std::shared_ptr<UiTextField>> textFields;
|
std::vector<std::shared_ptr<UiTextField>> textFields;
|
||||||
@ -343,16 +394,20 @@ namespace ZL {
|
|||||||
|
|
||||||
// Per-finger tracking for multi-touch support
|
// Per-finger tracking for multi-touch support
|
||||||
std::map<int64_t, std::shared_ptr<UiButton>> pressedButtons;
|
std::map<int64_t, std::shared_ptr<UiButton>> pressedButtons;
|
||||||
|
std::map<int64_t, std::shared_ptr<UiTextButton>> pressedTextButtons;
|
||||||
std::map<int64_t, std::shared_ptr<UiSlider>> pressedSliders;
|
std::map<int64_t, std::shared_ptr<UiSlider>> pressedSliders;
|
||||||
std::shared_ptr<UiTextField> focusedTextField;
|
std::shared_ptr<UiTextField> focusedTextField;
|
||||||
|
|
||||||
struct MenuState {
|
struct MenuState {
|
||||||
std::shared_ptr<UiNode> root;
|
std::shared_ptr<UiNode> root;
|
||||||
std::vector<std::shared_ptr<UiButton>> buttons;
|
std::vector<std::shared_ptr<UiButton>> buttons;
|
||||||
|
std::vector<std::shared_ptr<UiTextButton>> textButtons;
|
||||||
std::vector<std::shared_ptr<UiSlider>> sliders;
|
std::vector<std::shared_ptr<UiSlider>> sliders;
|
||||||
|
std::vector<std::shared_ptr<UiTextView>> textViews;
|
||||||
std::vector<std::shared_ptr<UiTextField>> textFields;
|
std::vector<std::shared_ptr<UiTextField>> textFields;
|
||||||
std::vector<std::shared_ptr<UiStaticImage>> staticImages;
|
std::vector<std::shared_ptr<UiStaticImage>> staticImages;
|
||||||
std::map<int64_t, std::shared_ptr<UiButton>> pressedButtons;
|
std::map<int64_t, std::shared_ptr<UiButton>> pressedButtons;
|
||||||
|
std::map<int64_t, std::shared_ptr<UiTextButton>> pressedTextButtons;
|
||||||
std::map<int64_t, std::shared_ptr<UiSlider>> pressedSliders;
|
std::map<int64_t, std::shared_ptr<UiSlider>> pressedSliders;
|
||||||
std::shared_ptr<UiTextField> focusedTextField;
|
std::shared_ptr<UiTextField> focusedTextField;
|
||||||
std::string path;
|
std::string path;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user