From 6b4b549b3cf418d88207a617479bfca98e52dd3e Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Sat, 28 Feb 2026 23:01:26 +0300 Subject: [PATCH] Working on UI for web --- resources/config/main_menu.json | 159 +++++++++++++++----------------- src/Game.cpp | 1 + src/Space.cpp | 2 +- src/UiManager.cpp | 144 +++++++++++++++-------------- src/UiManager.h | 4 +- 5 files changed, 151 insertions(+), 159 deletions(-) diff --git a/resources/config/main_menu.json b/resources/config/main_menu.json index f0f8c8b..129d810 100644 --- a/resources/config/main_menu.json +++ b/resources/config/main_menu.json @@ -1,114 +1,103 @@ { "root": { - "type": "FrameLayout", - "x": 0, - "y": 0, - "width": 1280, - "height": 720, - "children": [ - { - "type": "LinearLayout", - "name": "settingsButtons", - "orientation": "vertical", - "vertical_align" : "center", - "horizontal_align" : "center", - "spacing": 10, - "x": 0, - "y": 0, - "width": 1280, - "height": 720, - "children": [ - { - "type": "Button", - "name": "titleBtn", - "width": 254, - "height": 35, - "textures": { + "type": "LinearLayout", + "orientation": "vertical", + "vertical_align": "center", + "horizontal_align": "center", + "spacing": 10, + "x": 0, + "y": 0, + "width": "match_parent", + "height": "match_parent", + "children": [ + { + "type": "Button", + "name": "titleBtn", + "width": 254, + "height": 35, + "textures": { "normal": "resources/main_menu/title.png", "hover": "resources/main_menu/title.png", "pressed": "resources/main_menu/title.png" - } - }, - { - "type": "Button", - "name": "underlineBtn", - "width": 168, - "height": 44, - "textures": { + } + }, + { + "type": "Button", + "name": "underlineBtn", + "width": 168, + "height": 44, + "textures": { "normal": "resources/main_menu/line.png", "hover": "resources/main_menu/line.png", "pressed": "resources/main_menu/line.png" - } - }, - { - "type": "Button", - "name": "subtitleBtn", - "width": 144, - "height": 11, - "textures": { + } + }, + { + "type": "Button", + "name": "subtitleBtn", + "width": 144, + "height": 11, + "textures": { "normal": "resources/main_menu/subtitle.png", "hover": "resources/main_menu/subtitle.png", "pressed": "resources/main_menu/subtitle.png" - } - }, - { - "type": "Button", - "name": "singleButton", - "width": 382, - "height": 56, - "textures": { + } + }, + { + "type": "Button", + "name": "singleButton", + "width": 382, + "height": 56, + "textures": { "normal": "resources/main_menu/single.png", "hover": "resources/main_menu/single.png", "pressed": "resources/main_menu/single.png" - } - }, - { - "type": "Button", - "name": "multiplayerButton", - "width": 382, - "height": 56, - "textures": { + } + }, + { + "type": "Button", + "name": "multiplayerButton", + "width": 382, + "height": 56, + "textures": { "normal": "resources/main_menu/multi.png", "hover": "resources/main_menu/multi.png", "pressed": "resources/main_menu/multi.png" - } - }, - { - "type": "Button", - "name": "multiplayerButton2", - "width": 382, - "height": 56, - "textures": { + } + }, + { + "type": "Button", + "name": "multiplayerButton2", + "width": 382, + "height": 56, + "textures": { "normal": "resources/main_menu/multi.png", "hover": "resources/main_menu/multi.png", "pressed": "resources/main_menu/multi.png" - } - }, - { - "type": "Button", - "name": "exitButton", - "width": 382, - "height": 56, - "textures": { + } + }, + { + "type": "Button", + "name": "exitButton", + "width": 382, + "height": 56, + "textures": { "normal": "resources/main_menu/exit.png", "hover": "resources/main_menu/exit.png", "pressed": "resources/main_menu/exit.png" - } - }, - { - "type": "Button", - "name": "versionLabel", - "width": 81, - "height": 9, - "textures": { + } + }, + { + "type": "Button", + "name": "versionLabel", + "width": 81, + "height": 9, + "textures": { "normal": "resources/main_menu/version.png", "hover": "resources/main_menu/version.png", "pressed": "resources/main_menu/version.png" - } } - ] } - ] - } + ] } - \ No newline at end of file +} \ No newline at end of file diff --git a/src/Game.cpp b/src/Game.cpp index 253b475..210d181 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -342,6 +342,7 @@ namespace ZL Environment::width = event.window.data1; Environment::height = event.window.data2; Environment::computeProjectionDimensions(); + menuManager.uiManager.updateAllLayouts(); std::cout << "Window resized: " << Environment::width << "x" << Environment::height << std::endl; space.clearTextRendererCache(); diff --git a/src/Space.cpp b/src/Space.cpp index ba82066..691af98 100644 --- a/src/Space.cpp +++ b/src/Space.cpp @@ -362,7 +362,7 @@ namespace ZL } crosshairCfgLoaded = loadCrosshairConfig("resources/config/crosshair_config.json"); - std::cerr << "[Crosshair] loaded=" << crosshairCfgLoaded + std::cout << "[Crosshair] loaded=" << crosshairCfgLoaded << " enabled=" << crosshairCfg.enabled << " w=" << Environment::width << " h=" << Environment::height << " alpha=" << crosshairCfg.alpha diff --git a/src/UiManager.cpp b/src/UiManager.cpp index 80f5f95..d063248 100644 --- a/src/UiManager.cpp +++ b/src/UiManager.cpp @@ -201,9 +201,31 @@ namespace ZL { // так как LinearLayout их пересчитает. node->localX = j.value("x", 0.0f); node->localY = j.value("y", 0.0f); - node->width = j.value("width", 0.0f); - node->height = j.value("height", 0.0f); - + if (j.contains("width")) { + if (j["width"].is_string() && j["width"] == "match_parent") { + node->width = -1.0f; // Наш маркер для match_parent + } + else { + node->width = j["width"].get(); + } + } + else + { + node->width = 0.0f; + } + if (j.contains("height")) { + if (j["height"].is_string() && j["height"] == "match_parent") { + node->height = -1.0f; // Наш маркер для match_parent + } + else { + node->height = j["height"].get(); + } + } + else + { + node->height = 0.0f; + } + // 3. Параметры компоновки if (j.contains("orientation")) { std::string orient = j["orientation"].get(); @@ -220,14 +242,13 @@ namespace ZL { if (j.contains("vertical_align")) { std::string valign = j["vertical_align"]; if (valign == "center") node->layoutSettings.vAlign = VerticalAlign::Center; - else if (valign == "top") node->layoutSettings.vAlign = VerticalAlign::Top; + else if (valign == "bottom") node->layoutSettings.vAlign = VerticalAlign::Bottom; } // Подготавливаем базовый rect для компонентов (кнопок и т.д.) // На этапе парсинга мы даем им "желаемый" размер UiRect initialRect = { node->localX, node->localY, node->width, node->height }; - if (typeStr == "Button") { auto btn = std::make_shared(); btn->name = node->name; @@ -441,7 +462,7 @@ namespace ZL { void UiManager::replaceRoot(std::shared_ptr newRoot) { root = newRoot; - layoutNode(root, 0, 0); + layoutNode(root, 0, 0, Environment::projectionWidth, Environment::projectionHeight); buttons.clear(); sliders.clear(); textViews.clear(); @@ -465,55 +486,18 @@ namespace ZL { replaceRoot(newRoot); } - - void UiManager::updateLayout(const std::shared_ptr& node, float parentX, float parentY) { - // 1. Сначала определяем позицию самого контейнера - // Если это корень, он обычно занимает весь экран или заданные координаты + void UiManager::layoutNode(const std::shared_ptr& node, float parentX, float parentY, float parentW, float parentH) { + node->screenRect.w = (node->width < 0) ? parentW : node->width; + node->screenRect.h = (node->height < 0) ? parentH : node->height; + + // 2. Позиция относительно родителя node->screenRect.x = parentX + node->localX; node->screenRect.y = parentY + node->localY; - node->screenRect.w = node->width; - node->screenRect.h = node->height; - float currentX = node->screenRect.x; - // В UI обычно Y растет сверху вниз, но в нашем OpenGL (Renderer.cpp) - // используется орто-матрица с Y-вверх. Учтем это. - float currentY = node->screenRect.y + node->screenRect.h; - - for (auto& child : node->children) { - if (node->layoutType == LayoutType::Linear) { - if (node->orientation == Orientation::Vertical) { - // Игнорируем child->localX/Y для LinearVertical - currentY -= child->height; // Сдвигаемся вниз на высоту элемента - updateLayout(child, node->screenRect.x, currentY - node->screenRect.y); - currentY -= node->spacing; // Добавляем отступ - } - else { - // Логика для Horizontal... - updateLayout(child, currentX, 0); // упрощенно - currentX += child->width + node->spacing; - } - } - else { - // FrameLayout: используем честные localX и localY - updateLayout(child, node->screenRect.x, node->screenRect.y); - } - } - - // После вычисления позиций, нужно обновить меши кнопок (buildMesh) - // на основе вычисленных screenRect. - if (node->button) { - node->button->rect = node->screenRect; - node->button->buildMesh(); - } - } - - void UiManager::layoutNode(const std::shared_ptr& node, float parentX, float parentY) { - // 1. Базовый расчет позиции текущего узла относительно родителя - node->screenRect.x = parentX + node->localX; - node->screenRect.y = parentY + node->localY; - node->screenRect.w = node->width; - node->screenRect.h = node->height; + // Используем удобные локальные переменные для расчетов детей + float currentW = node->screenRect.w; + float currentH = node->screenRect.h; if (node->layoutType == LayoutType::Linear) { float totalContentWidth = 0; @@ -531,14 +515,12 @@ namespace ZL { } } - // Вычисляем начальные смещения (Offsets) float startX = 0; - float startY = node->height; // Начинаем сверху (Y растет вверх) + float startY = currentH; - // Вертикальное выравнивание всего блока внутри контейнера if (node->orientation == Orientation::Vertical) { if (node->layoutSettings.vAlign == VerticalAlign::Center) { - startY = (node->height + totalContentHeight) / 2.0f; + startY = (currentH + totalContentHeight) / 2.0f; } else if (node->layoutSettings.vAlign == VerticalAlign::Bottom) { startY = totalContentHeight; @@ -548,10 +530,10 @@ namespace ZL { // Горизонтальное выравнивание всего блока if (node->orientation == Orientation::Horizontal) { if (node->layoutSettings.hAlign == HorizontalAlign::Center) { - startX = (node->width - totalContentWidth) / 2.0f; + startX = (currentW - totalContentWidth) / 2.0f; } else if (node->layoutSettings.hAlign == HorizontalAlign::Right) { - startX = node->width - totalContentWidth; + startX = currentW - totalContentWidth; } } @@ -559,12 +541,15 @@ namespace ZL { float cursorY = startY; for (auto& child : node->children) { - if (node->orientation == Orientation::Vertical) { - cursorY -= child->height; - // Горизонтальное выравнивание внутри строки (Cross-axis alignment) + float childW = (child->width < 0) ? currentW : child->width; + float childH = (child->height < 0) ? currentH : child->height; + + if (node->orientation == Orientation::Vertical) { + cursorY -= childH; // используем вычисленный childH + float childX = 0; - float freeSpaceX = node->width - child->width; + float freeSpaceX = currentW - childW; if (node->layoutSettings.hAlign == HorizontalAlign::Center) childX = freeSpaceX / 2.0f; else if (node->layoutSettings.hAlign == HorizontalAlign::Right) childX = freeSpaceX; @@ -573,24 +558,34 @@ namespace ZL { cursorY -= node->spacing; } else { - // Вертикальное выравнивание внутри колонки - float childY = 0; - float freeSpaceY = node->height - child->height; - if (node->layoutSettings.vAlign == VerticalAlign::Center) childY = freeSpaceY / 2.0f; - else if (node->layoutSettings.vAlign == VerticalAlign::Bottom) childY = 0; // Внизу - else childY = freeSpaceY; // Вверху - child->localX = cursorX; + + // Вертикальное выравнивание внутри "строки" (Cross-axis alignment) + float childY = 0; + float freeSpaceY = currentH - childH; + + if (node->layoutSettings.vAlign == VerticalAlign::Center) { + childY = freeSpaceY / 2.0f; + } + else if (node->layoutSettings.vAlign == VerticalAlign::Top) { + childY = freeSpaceY; // Прижимаем к верхнему краю (т.к. Y растет вверх) + } + else if (node->layoutSettings.vAlign == VerticalAlign::Bottom) { + childY = 0; // Прижимаем к нижнему краю + } + child->localY = childY; - cursorX += child->width + node->spacing; + + // Сдвигаем курсор вправо для следующего элемента + cursorX += childW + node->spacing; } - layoutNode(child, node->screenRect.x, node->screenRect.y); + layoutNode(child, node->screenRect.x, node->screenRect.y, currentW, currentH); } } else { // Если Frame, просто идем по детям с их фиксированными координатами for (auto& child : node->children) { - layoutNode(child, node->screenRect.x, node->screenRect.y); + layoutNode(child, node->screenRect.x, node->screenRect.y, node->width, node->height); } } @@ -630,6 +625,13 @@ namespace ZL { } } + void UiManager::updateAllLayouts() { + if (!root) return; + + // Запускаем расчет от корня, передавая размеры экрана как "родительские" + layoutNode(root, 0, 0, Environment::projectionWidth, Environment::projectionHeight); + } + void UiManager::collectButtonsAndSliders(const std::shared_ptr& node) { if (node->button) { buttons.push_back(node->button); diff --git a/src/UiManager.h b/src/UiManager.h index 88a7082..0d2a704 100644 --- a/src/UiManager.h +++ b/src/UiManager.h @@ -245,10 +245,10 @@ namespace ZL { bool startAnimationOnNode(const std::string& nodeName, const std::string& animName); bool stopAnimationOnNode(const std::string& nodeName, const std::string& animName); bool setAnimationCallback(const std::string& nodeName, const std::string& animName, std::function cb); + void updateAllLayouts(); private: - void updateLayout(const std::shared_ptr& node, float parentX, float parentY); - void layoutNode(const std::shared_ptr& node, float parentX, float parentY); + void layoutNode(const std::shared_ptr& node, float parentX, float parentY, float parentW, float parentH); void syncComponentRects(const std::shared_ptr& node); void collectButtonsAndSliders(const std::shared_ptr& node);