From 0babfff28b3e1f9ace2447648b82e51e58602820 Mon Sep 17 00:00:00 2001 From: Vlad Date: Sun, 22 Feb 2026 19:21:12 +0600 Subject: [PATCH] added multiplayer menu --- resources/config/multiplayer_menu.json | 105 +++++++++++++++++++++ src/Game.cpp | 81 ++++++++++++---- src/UiManager.cpp | 123 ++++++++++++++++++++++++- src/UiManager.h | 33 ++++++- 4 files changed, 323 insertions(+), 19 deletions(-) create mode 100644 resources/config/multiplayer_menu.json diff --git a/resources/config/multiplayer_menu.json b/resources/config/multiplayer_menu.json new file mode 100644 index 0000000..17bf917 --- /dev/null +++ b/resources/config/multiplayer_menu.json @@ -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 + } + ] + } + } \ No newline at end of file diff --git a/src/Game.cpp b/src/Game.cpp index 662ade7..4bed430 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -405,10 +405,54 @@ 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"; Environment::exitGameLoop = true; @@ -1338,10 +1382,8 @@ namespace ZL } #endif - #ifdef __ANDROID__ if (event.type == SDL_FINGERDOWN) { - // Координаты Finger в SDL нормализованы от 0.0 до 1.0 int mx = static_cast(event.tfinger.x * Environment::width); int my = static_cast(event.tfinger.y * Environment::height); handleDown(mx, my); @@ -1357,24 +1399,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) { @@ -1389,18 +1427,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 }; } } diff --git a/src/UiManager.cpp b/src/UiManager.cpp index 70f1de4..126140a 100644 --- a/src/UiManager.cpp +++ b/src/UiManager.cpp @@ -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(); 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(data); } @@ -299,6 +314,44 @@ namespace ZL { node->slider = s; } + else if (node->type == "TextField") { + auto tf = std::make_shared(); + tf->name = node->name; + tf->rect = node->rect; + + if (j.contains("placeholder")) tf->placeholder = j["placeholder"].get(); + if (j.contains("fontPath")) tf->fontPath = j["fontPath"].get(); + if (j.contains("fontSize")) tf->fontSize = j["fontSize"].get(); + if (j.contains("maxLength")) tf->maxLength = j["maxLength"].get(); + + 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(); + } + } + 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(); + } + } + 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(); + } + } + 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(); + } + } + + tf->textRenderer = std::make_unique(); + 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 cb) { @@ -476,13 +532,36 @@ namespace ZL { return true; } + std::shared_ptr 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 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 UiManager::findButton(const std::string& name) { for (auto& b : buttons) if (b->name == name) return b; return nullptr; diff --git a/src/UiManager.h b/src/UiManager.h index 5cfe6c8..8fa6e71 100644 --- a/src/UiManager.h +++ b/src/UiManager.h @@ -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 color = { 1.f, 1.f, 1.f, 1.f }; + std::array placeholderColor = { 0.5f, 0.5f, 0.5f, 1.f }; + std::array backgroundColor = { 0.2f, 0.2f, 0.2f, 1.f }; + std::array borderColor = { 0.5f, 0.5f, 0.5f, 1.f }; + int maxLength = 256; + bool focused = false; + + std::unique_ptr textRenderer; + std::function onTextChanged; + + void draw(Renderer& renderer) const; + }; + struct UiNode { std::string type; UiRect rect; @@ -98,6 +118,7 @@ namespace ZL { std::shared_ptr button; std::shared_ptr slider; std::shared_ptr textView; + std::shared_ptr 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 findTextView(const std::string& name); bool setText(const std::string& name, const std::string& newText); + std::shared_ptr findTextField(const std::string& name); + bool setTextFieldCallback(const std::string& name, std::function 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> buttons; std::vector> sliders; std::vector> textViews; + std::vector> textFields; std::map, std::vector> nodeActiveAnims; std::map, std::function> animCallbacks; // key: (nodeName, animName) std::shared_ptr pressedButton; std::shared_ptr pressedSlider; + std::shared_ptr focusedTextField; struct MenuState { std::shared_ptr root; std::vector> buttons; std::vector> sliders; + std::vector> textFields; std::shared_ptr pressedButton; std::shared_ptr pressedSlider; + std::shared_ptr focusedTextField; std::string path; std::map, std::function> animCallbacks; };