Merge branch 'spark'

This commit is contained in:
Vladislav Khorev 2026-02-22 17:02:40 +03:00
commit 46d7a2a25d
4 changed files with 323 additions and 19 deletions

View File

@ -0,0 +1,105 @@
{
"root": {
"type": "LinearLayout",
"x": 0,
"y": 0,
"width": 1920,
"height": 1080,
"orientation": "vertical",
"spacing": 20,
"children": [
{
"type": "TextView",
"name": "titleText",
"x": 300,
"y": 100,
"width": 1320,
"height": 100,
"text": "Multiplayer",
"fontPath": "resources/fonts/DroidSans.ttf",
"fontSize": 72,
"color": [1, 1, 1, 1],
"centered": true
},
{
"type": "TextView",
"name": "serverLabel",
"x": 400,
"y": 250,
"width": 1120,
"height": 50,
"text": "Enter server name or IP:",
"fontPath": "resources/fonts/DroidSans.ttf",
"fontSize": 32,
"color": [1, 1, 1, 1],
"centered": false
},
{
"type": "TextField",
"name": "serverInputField",
"x": 400,
"y": 320,
"width": 1120,
"height": 60,
"placeholder": "Enter server name or IP",
"fontPath": "resources/fonts/DroidSans.ttf",
"fontSize": 28,
"maxLength": 256,
"color": [1, 1, 1, 1],
"placeholderColor": [0.6, 0.6, 0.6, 1],
"backgroundColor": [0.15, 0.15, 0.15, 1],
"borderColor": [0.7, 0.7, 0.7, 1]
},
{
"type": "LinearLayout",
"x": 400,
"y": 450,
"width": 1120,
"height": 80,
"orientation": "horizontal",
"spacing": 30,
"children": [
{
"type": "Button",
"name": "connectButton",
"x": 0,
"y": 0,
"width": 530,
"height": 80,
"textures": {
"normal": "resources/main_menu/single.png",
"hover": "resources/main_menu/single.png",
"pressed": "resources/main_menu/single.png"
}
},
{
"type": "Button",
"name": "backButton",
"x": 590,
"y": 0,
"width": 530,
"height": 80,
"textures": {
"normal": "resources/main_menu/exit.png",
"hover": "resources/main_menu/exit.png",
"pressed": "resources/main_menu/exit.png"
}
}
]
},
{
"type": "TextView",
"name": "statusText",
"x": 400,
"y": 580,
"width": 1120,
"height": 50,
"text": "Ready to connect",
"fontPath": "resources/fonts/DroidSans.ttf",
"fontSize": 24,
"color": [0.8, 0.8, 0.8, 1],
"centered": false
}
]
}
}

View File

@ -405,9 +405,53 @@ namespace ZL
std::cerr << "Single button pressed: " << name << " -> load gameplay UI\n";
loadGameplayUI();
});
uiManager.setButtonCallback("multiplayerButton", [loadGameplayUI](const std::string& name) {
/*uiManager.setButtonCallback("multiplayerButton", [loadGameplayUI](const std::string& name) {
std::cerr << "Multiplayer button pressed: " << name << " -> load gameplay UI\n";
loadGameplayUI();
});*/
uiManager.setButtonCallback("multiplayerButton", [this](const std::string& name) {
std::cerr << "Multiplayer button pressed → opening multiplayer menu\n";
uiManager.startAnimationOnNode("playButton", "buttonsExit");
uiManager.startAnimationOnNode("settingsButton", "buttonsExit");
uiManager.startAnimationOnNode("multiplayerButton", "buttonsExit");
uiManager.startAnimationOnNode("exitButton", "buttonsExit");
if (uiManager.pushMenuFromFile("resources/config/multiplayer_menu.json", this->renderer, CONST_ZIP_FILE)) {
// Callback для кнопки подключения
uiManager.setButtonCallback("connectButton", [this](const std::string& buttonName) {
std::string serverAddress = uiManager.getTextFieldValue("serverInputField");
if (serverAddress.empty()) {
uiManager.setText("statusText", "Please enter server address");
return;
}
uiManager.setText("statusText", "Connecting to " + serverAddress + "...");
std::cerr << "Connecting to server: " << serverAddress << std::endl;
// Здесь добавить вашу логику подключения к серверу
// connectToServer(serverAddress);
});
// Callback для кнопки назад
uiManager.setButtonCallback("backButton", [this](const std::string& buttonName) {
uiManager.popMenu();
});
// Callback для отслеживания ввода текста
uiManager.setTextFieldCallback("serverInputField",
[this](const std::string& fieldName, const std::string& newText) {
std::cout << "Server input field changed to: " << newText << std::endl;
});
std::cerr << "Multiplayer menu loaded successfully\n";
}
else {
std::cerr << "Failed to load multiplayer menu\n";
}
});
uiManager.setButtonCallback("exitButton", [](const std::string& name) {
std::cerr << "Exit from main menu pressed: " << name << " -> exiting\n";
@ -1670,10 +1714,8 @@ namespace ZL
}
#endif
#ifdef __ANDROID__
if (event.type == SDL_FINGERDOWN) {
// Координаты Finger в SDL нормализованы от 0.0 до 1.0
int mx = static_cast<int>(event.tfinger.x * Environment::width);
int my = static_cast<int>(event.tfinger.y * Environment::height);
handleDown(mx, my);
@ -1689,24 +1731,20 @@ namespace ZL
handleMotion(mx, my);
}
#else
if (event.type == SDL_MOUSEBUTTONDOWN) {
int mx = event.button.x;
int my = event.button.y;
handleDown(mx, my);
}
if (event.type == SDL_MOUSEBUTTONUP) {
int mx = event.button.x;
int my = event.button.y;
handleUp(mx, my);
}
if (event.type == SDL_MOUSEMOTION) {
int mx = event.motion.x;
int my = event.motion.y;
handleMotion(mx, my);
}
if (event.type == SDL_MOUSEWHEEL) {
@ -1721,18 +1759,29 @@ namespace ZL
Environment::zoom = zoomstep;
}
}
if (event.type == SDL_KEYUP)
{
if (event.key.keysym.sym == SDLK_i)
{
// Обработка ввода текста
if (event.type == SDL_KEYDOWN) {
if (event.key.keysym.sym == SDLK_BACKSPACE) {
uiManager.onKeyBackspace();
}
}
if (event.type == SDL_TEXTINPUT) {
// Пропускаем ctrl+c и другие команды
if ((event.text.text[0] & 0x80) == 0) { // ASCII символы
uiManager.onKeyPress((unsigned char)event.text.text[0]);
}
}
if (event.type == SDL_KEYUP) {
if (event.key.keysym.sym == SDLK_i) {
x = x + 1;
}
if (event.key.keysym.sym == SDLK_k)
{
if (event.key.keysym.sym == SDLK_k) {
x = x - 1;
}
if (event.key.keysym.sym == SDLK_a)
{
if (event.key.keysym.sym == SDLK_a) {
Environment::shipState.position = { 9466.15820, 1046.00159, 18531.2090 };
}
}

View File

@ -168,6 +168,20 @@ namespace ZL {
renderer.DisableVertexAttribArray(vTexCoordName);
}
void UiTextField::draw(Renderer& renderer) const {
if (textRenderer) {
float textX = rect.x + 10.0f;
float textY = rect.y + rect.h / 2.0f;
if (text.empty()) {
textRenderer->drawText(placeholder, textX, textY, 1.0f, false, placeholderColor);
}
else {
textRenderer->drawText(text, textX, textY, 1.0f, false, color);
}
}
}
void UiManager::loadFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile) {
std::string content;
try {
@ -207,6 +221,7 @@ namespace ZL {
buttons.clear();
sliders.clear();
textViews.clear();
textFields.clear();
collectButtonsAndSliders(root);
nodeActiveAnims.clear();
@ -277,7 +292,7 @@ namespace ZL {
if (!t.contains(key) || !t[key].is_string()) return nullptr;
std::string path = t[key].get<std::string>();
try {
std::cout << "UiManager: --loading texture for button '" << "' : " << path << " Zip file: " << zipFile << std::endl;
std::cout << "UiManager: --loading texture for slider '" << s->name << "' : " << path << " Zip file: " << zipFile << std::endl;
auto data = CreateTextureDataFromPng(path.c_str(), zipFile.c_str());
return std::make_shared<Texture>(data);
}
@ -299,6 +314,44 @@ namespace ZL {
node->slider = s;
}
else if (node->type == "TextField") {
auto tf = std::make_shared<UiTextField>();
tf->name = node->name;
tf->rect = node->rect;
if (j.contains("placeholder")) tf->placeholder = j["placeholder"].get<std::string>();
if (j.contains("fontPath")) tf->fontPath = j["fontPath"].get<std::string>();
if (j.contains("fontSize")) tf->fontSize = j["fontSize"].get<int>();
if (j.contains("maxLength")) tf->maxLength = j["maxLength"].get<int>();
if (j.contains("color") && j["color"].is_array() && j["color"].size() == 4) {
for (int i = 0; i < 4; ++i) {
tf->color[i] = j["color"][i].get<float>();
}
}
if (j.contains("placeholderColor") && j["placeholderColor"].is_array() && j["placeholderColor"].size() == 4) {
for (int i = 0; i < 4; ++i) {
tf->placeholderColor[i] = j["placeholderColor"][i].get<float>();
}
}
if (j.contains("backgroundColor") && j["backgroundColor"].is_array() && j["backgroundColor"].size() == 4) {
for (int i = 0; i < 4; ++i) {
tf->backgroundColor[i] = j["backgroundColor"][i].get<float>();
}
}
if (j.contains("borderColor") && j["borderColor"].is_array() && j["borderColor"].size() == 4) {
for (int i = 0; i < 4; ++i) {
tf->borderColor[i] = j["borderColor"][i].get<float>();
}
}
tf->textRenderer = std::make_unique<TextRenderer>();
if (!tf->textRenderer->init(renderer, tf->fontPath, tf->fontSize, zipFile)) {
std::cerr << "Failed to init TextRenderer for TextField: " << tf->name << std::endl;
}
node->textField = tf;
}
if (j.contains("animations") && j["animations"].is_object()) {
for (auto it = j["animations"].begin(); it != j["animations"].end(); ++it) {
@ -406,7 +459,10 @@ namespace ZL {
if (node->textView) {
textViews.push_back(node->textView);
}
for (auto& c : node->children) collectButtonsAndSliders(c); // ìîæíî ïåðåèìåíîâàòü â collectControls
if (node->textField) {
textFields.push_back(node->textField);
}
for (auto& c : node->children) collectButtonsAndSliders(c);
}
bool UiManager::setButtonCallback(const std::string& name, std::function<void(const std::string&)> cb) {
@ -476,13 +532,36 @@ namespace ZL {
return true;
}
std::shared_ptr<UiTextField> UiManager::findTextField(const std::string& name) {
for (auto& tf : textFields) if (tf->name == name) return tf;
return nullptr;
}
bool UiManager::setTextFieldCallback(const std::string& name, std::function<void(const std::string&, const std::string&)> cb) {
auto tf = findTextField(name);
if (!tf) {
std::cerr << "UiManager: setTextFieldCallback failed, textfield not found: " << name << std::endl;
return false;
}
tf->onTextChanged = std::move(cb);
return true;
}
std::string UiManager::getTextFieldValue(const std::string& name) {
auto tf = findTextField(name);
if (!tf) return "";
return tf->text;
}
bool UiManager::pushMenuFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile) {
MenuState prev;
prev.root = root;
prev.buttons = buttons;
prev.sliders = sliders;
prev.textFields = textFields;
prev.pressedButton = pressedButton;
prev.pressedSlider = pressedSlider;
prev.focusedTextField = focusedTextField;
prev.path = "";
prev.animCallbacks = animCallbacks;
@ -490,6 +569,7 @@ namespace ZL {
try {
nodeActiveAnims.clear();
animCallbacks.clear();
focusedTextField = nullptr;
for (auto& b : buttons) {
if (b) {
b->animOffsetX = 0.0f;
@ -523,8 +603,10 @@ namespace ZL {
root = s.root;
buttons = s.buttons;
sliders = s.sliders;
textFields = s.textFields;
pressedButton = s.pressedButton;
pressedSlider = s.pressedSlider;
focusedTextField = s.focusedTextField;
animCallbacks = s.animCallbacks;
@ -565,6 +647,9 @@ namespace ZL {
for (const auto& tv : textViews) {
tv->draw(renderer);
}
for (const auto& tf : textFields) {
tf->draw(renderer);
}
renderer.PopMatrix();
renderer.PopProjectionMatrix();
@ -785,6 +870,16 @@ namespace ZL {
break;
}
}
for (auto& tf : textFields) {
if (tf->rect.contains((float)x, (float)y)) {
focusedTextField = tf;
tf->focused = true;
}
else {
tf->focused = false;
}
}
}
void UiManager::onMouseUp(int x, int y) {
@ -806,6 +901,30 @@ namespace ZL {
}
}
void UiManager::onKeyPress(unsigned char key) {
if (!focusedTextField) return;
if (key >= 32 && key <= 126) {
if (focusedTextField->text.length() < (size_t)focusedTextField->maxLength) {
focusedTextField->text += key;
if (focusedTextField->onTextChanged) {
focusedTextField->onTextChanged(focusedTextField->name, focusedTextField->text);
}
}
}
}
void UiManager::onKeyBackspace() {
if (!focusedTextField) return;
if (!focusedTextField->text.empty()) {
focusedTextField->text.pop_back();
if (focusedTextField->onTextChanged) {
focusedTextField->onTextChanged(focusedTextField->name, focusedTextField->text);
}
}
}
std::shared_ptr<UiButton> UiManager::findButton(const std::string& name) {
for (auto& b : buttons) if (b->name == name) return b;
return nullptr;

View File

@ -90,6 +90,26 @@ namespace ZL {
}
};
struct UiTextField {
std::string name;
UiRect rect;
std::string text = "";
std::string placeholder = "";
std::string fontPath = "resources/fonts/DroidSans.ttf";
int fontSize = 32;
std::array<float, 4> color = { 1.f, 1.f, 1.f, 1.f };
std::array<float, 4> placeholderColor = { 0.5f, 0.5f, 0.5f, 1.f };
std::array<float, 4> backgroundColor = { 0.2f, 0.2f, 0.2f, 1.f };
std::array<float, 4> borderColor = { 0.5f, 0.5f, 0.5f, 1.f };
int maxLength = 256;
bool focused = false;
std::unique_ptr<TextRenderer> textRenderer;
std::function<void(const std::string&, const std::string&)> onTextChanged;
void draw(Renderer& renderer) const;
};
struct UiNode {
std::string type;
UiRect rect;
@ -98,6 +118,7 @@ namespace ZL {
std::shared_ptr<UiButton> button;
std::shared_ptr<UiSlider> slider;
std::shared_ptr<UiTextView> textView;
std::shared_ptr<UiTextField> textField;
std::string orientation = "vertical";
float spacing = 0.0f;
@ -126,9 +147,11 @@ namespace ZL {
void onMouseMove(int x, int y);
void onMouseDown(int x, int y);
void onMouseUp(int x, int y);
void onKeyPress(unsigned char key);
void onKeyBackspace();
bool isUiInteraction() const {
return pressedButton != nullptr || pressedSlider != nullptr;
return pressedButton != nullptr || pressedSlider != nullptr || focusedTextField != nullptr;
}
void stopAllAnimations() {
@ -158,6 +181,10 @@ namespace ZL {
std::shared_ptr<UiTextView> findTextView(const std::string& name);
bool setText(const std::string& name, const std::string& newText);
std::shared_ptr<UiTextField> findTextField(const std::string& name);
bool setTextFieldCallback(const std::string& name, std::function<void(const std::string&, const std::string&)> cb);
std::string getTextFieldValue(const std::string& name);
bool pushMenuFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile = "");
bool popMenu();
void clearMenuStack();
@ -200,19 +227,23 @@ namespace ZL {
std::vector<std::shared_ptr<UiButton>> buttons;
std::vector<std::shared_ptr<UiSlider>> sliders;
std::vector<std::shared_ptr<UiTextView>> textViews;
std::vector<std::shared_ptr<UiTextField>> textFields;
std::map<std::shared_ptr<UiNode>, std::vector<ActiveAnim>> nodeActiveAnims;
std::map<std::pair<std::string, std::string>, std::function<void()>> animCallbacks; // key: (nodeName, animName)
std::shared_ptr<UiButton> pressedButton;
std::shared_ptr<UiSlider> pressedSlider;
std::shared_ptr<UiTextField> focusedTextField;
struct MenuState {
std::shared_ptr<UiNode> root;
std::vector<std::shared_ptr<UiButton>> buttons;
std::vector<std::shared_ptr<UiSlider>> sliders;
std::vector<std::shared_ptr<UiTextField>> textFields;
std::shared_ptr<UiButton> pressedButton;
std::shared_ptr<UiSlider> pressedSlider;
std::shared_ptr<UiTextField> focusedTextField;
std::string path;
std::map<std::pair<std::string, std::string>, std::function<void()>> animCallbacks;
};