From 159fac02f018528aa34760067d83d859b93ad718 Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Wed, 15 Jul 2026 18:14:01 +0300 Subject: [PATCH] Fixing bugs in UI animations, added credits --- resources/w/ui/screen_credits.json | 367 ++++++++++++++++++++++-- resources/w/ui/screen_main_menu.json | 27 +- resources/w/ui_en/screen_credits.json | 359 +++++++++++++++++++++++ resources/w/ui_en/screen_main_menu.json | 29 +- src/MenuManager.cpp | 20 ++ src/MenuManager.h | 2 + src/UiManager.cpp | 139 ++++++--- src/UiManager.h | 7 +- 8 files changed, 864 insertions(+), 86 deletions(-) create mode 100644 resources/w/ui_en/screen_credits.json diff --git a/resources/w/ui/screen_credits.json b/resources/w/ui/screen_credits.json index b131d23..a288e10 100644 --- a/resources/w/ui/screen_credits.json +++ b/resources/w/ui/screen_credits.json @@ -7,34 +7,351 @@ { "type": "StaticImage", "name": "creditsBkg", + "width": 2992, + "height": 1632, + "horizontal_gravity": "center", + "vertical_gravity": "center", + "texture": "resources/w/ui/img/main/MenuBkg001_v001.png" + }, + { + "type": "LinearLayout", + "name": "creditsLinearLayout", + "orientation": "vertical", + "horizontal_gravity": "center", + "vertical_gravity": "top", + "horizontal_align": "left", + "animations": { + "slowScroll": { + "repeat": true, + "steps": [ + { + "type": "move", + "to": [ + 0, + 2600 + ], + "duration": 30.0 + } + ] + } + }, + "width": 300, + "height": 3000, + "spacing": 20, + "children": [ + { + "type": "TextView", + "name": "smallPause", + "width": 300, + "height": 700, + "text": "", + "fontSize": 40, + "textCentered": true, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsTitleText", + "width": 300, + "height": 60, + "text": "Тень над Бишкеком Демо", + "fontSize": 40, + "textCentered": true, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText1", + "width": 300, + "height": 20, + "text": "Продюсер:", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 0.85, 0.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText2", + "width": 300, + "height": 40, + "text": "Владислав Хорев", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText3", + "width": 300, + "height": 20, + "text": "Гейм-дизайн:", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 0.85, 0.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText4", + "width": 300, + "height": 20, + "text": "Владислав Хорев", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText5", + "width": 300, + "height": 40, + "text": "Лейла Боброва", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText6", + "width": 300, + "height": 20, + "text": "Сценарий:", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 0.85, 0.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText7", + "width": 300, + "height": 20, + "text": "Владислав Хорев", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText8", + "width": 300, + "height": 40, + "text": "Лейла Боброва", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText9", + "width": 300, + "height": 20, + "text": "Разработка:", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 0.85, 0.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText10", + "width": 300, + "height": 20, + "text": "Владислав Хорев", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText11", + "width": 300, + "height": 20, + "text": "Владислав Кан", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText12", + "width": 300, + "height": 20, + "text": "Бексултан Алмазбеков", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText13", + "width": 300, + "height": 40, + "text": "Сергей Зотов", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText14", + "width": 300, + "height": 20, + "text": "Персонажи и анимации:", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 0.85, 0.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText15", + "width": 300, + "height": 20, + "text": "Дмитрий Прохорчик", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText16", + "width": 300, + "height": 40, + "text": "Роман Тресцов", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText17", + "width": 300, + "height": 20, + "text": "3D ассеты:", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 0.85, 0.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText18", + "width": 300, + "height": 20, + "text": "Дмитрий Прохорчик", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText19", + "width": 300, + "height": 40, + "text": "Ishmaladi (turbosquid.com)", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText20", + "width": 300, + "height": 20, + "text": "Арты:", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 0.85, 0.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText21", + "width": 300, + "height": 40, + "text": "Maggie Tem", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText22", + "width": 300, + "height": 20, + "text": "UI/UX:", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 0.85, 0.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText23", + "width": 300, + "height": 40, + "text": "Кенже Казматова", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText24", + "width": 300, + "height": 20, + "text": "Музыка:", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 0.85, 0.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText25", + "width": 300, + "height": 40, + "text": "Pazetic_Ocean", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText26", + "width": 300, + "height": 20, + "text": "Звуки:", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 0.85, 0.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText27", + "width": 300, + "height": 300, + "text": "IENBA, ImAFoley, stradie,\nItsTheGoodstuff, uwesoundboiz,\nAnthousai, Foxfire-,\nn-lerche, CogFireStudios,\nadcbicycle, AntoineRomo,\nSmallConfusion, joedeshon,\nefectirijillo (FreeSound.org)", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText28", + "width": 300, + "height": 40, + "text": "Некоторые арты, 3D модели\nи текстуры были созданы\nс помощью нейросетей.", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + } + ] + }, + { + "type": "Button", + "name": "creditsBackButton", "width": "match_parent", "height": "match_parent", - "texture": "resources/w/ui/img/main/bkg.png" - }, - { - "type": "StaticImage", - "name": "creditsImage", - "horizontal_gravity": "center", - "vertical_gravity": "center", - "width": 800, - "height": 500, - "texture": "resources/w/ui/img/main/about.png" - }, - { - "type": "Button", - "name": "skipButton", - "horizontal_gravity": "right", - "vertical_gravity": "top", - "x": -23, - "y": -35, - "width": 200, - "height": 200, - "clickZoneWidth": 176, - "clickZoneHeight": 176, + "x": 0, + "y": 0, "textures": { - "normal": "resources/w/ui/img/RedClose001_State=Default001.png", - "hover": "resources/w/ui/img/RedClose001_State=Selected001.png", - "pressed": "resources/w/ui/img/RedClose001_State=Tap001.png" + "normal": "resources/transparent.png", + "hover": "resources/transparent.png", + "pressed": "resources/transparent.png" } } ] diff --git a/resources/w/ui/screen_main_menu.json b/resources/w/ui/screen_main_menu.json index db4fada..c4b1532 100644 --- a/resources/w/ui/screen_main_menu.json +++ b/resources/w/ui/screen_main_menu.json @@ -47,7 +47,7 @@ "horizontal_align": "center", "vertical_align": "center", "spacing": 24, - "y": 150, + "y": 140, "width": "match_parent", "height": "match_parent", "children": [ @@ -55,7 +55,7 @@ "type": "TextButton", "name": "menuStartButton", "width": 300, - "height": 50, + "height": 44, "text": "Начать игру", "fontSize": 48, "textCentered": true @@ -64,7 +64,7 @@ "type": "TextButton", "name": "menuLoadButton", "width": 300, - "height": 50, + "height": 44, "text": "Загрузить игру", "fontSize": 48, "textCentered": true @@ -73,7 +73,7 @@ "type": "TextButton", "name": "menuSettingsButton", "width": 300, - "height": 50, + "height": 44, "text": "Настройки", "fontSize": 48, "textCentered": true @@ -82,7 +82,7 @@ "type": "TextButton", "name": "menuLanguageButton", "width": 300, - "height": 50, + "height": 44, "text": "Язык / Language", "fontSize": 48, "textCentered": true @@ -91,8 +91,17 @@ "type": "TextButton", "name": "menuAboutButton", "width": 300, - "height": 50, - "text": "О нас", + "height": 44, + "text": "О игре", + "fontSize": 48, + "textCentered": true + }, + { + "type": "TextButton", + "name": "menuCreditsButton", + "width": 300, + "height": 44, + "text": "Авторы", "fontSize": 48, "textCentered": true }, @@ -100,7 +109,7 @@ "type": "TextButton", "name": "menuExitButton", "width": 300, - "height": 50, + "height": 44, "text": "Выход", "fontSize": 48, "textCentered": true @@ -116,7 +125,7 @@ "y": 10, "width": 160, "height": 36, - "text": "v. 0.7", + "text": "v. 1.0", "fontSize": 24, "textCentered": false, "topAligned": true, diff --git a/resources/w/ui_en/screen_credits.json b/resources/w/ui_en/screen_credits.json new file mode 100644 index 0000000..2b62265 --- /dev/null +++ b/resources/w/ui_en/screen_credits.json @@ -0,0 +1,359 @@ +{ + "root": { + "type": "FrameLayout", + "width": "match_parent", + "height": "match_parent", + "children": [ + { + "type": "StaticImage", + "name": "creditsBkg", + "width": 2992, + "height": 1632, + "horizontal_gravity": "center", + "vertical_gravity": "center", + "texture": "resources/w/ui/img/main/MenuBkg001_v001.png" + }, + { + "type": "LinearLayout", + "name": "creditsLinearLayout", + "orientation": "vertical", + "horizontal_gravity": "center", + "vertical_gravity": "top", + "horizontal_align": "left", + "animations": { + "slowScroll": { + "repeat": true, + "steps": [ + { + "type": "move", + "to": [ + 0, + 2600 + ], + "duration": 30.0 + } + ] + } + }, + "width": 300, + "height": 3000, + "spacing": 20, + "children": [ + { + "type": "TextView", + "name": "smallPause", + "width": 300, + "height": 700, + "text": "", + "fontSize": 40, + "textCentered": true, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsTitleText", + "width": 300, + "height": 60, + "text": "Shadow over Bishkek Demo", + "fontSize": 40, + "textCentered": true, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText1", + "width": 300, + "height": 20, + "text": "Producer:", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 0.85, 0.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText2", + "width": 300, + "height": 40, + "text": "Vladislav Khorev", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText3", + "width": 300, + "height": 20, + "text": "Game Design:", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 0.85, 0.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText4", + "width": 300, + "height": 20, + "text": "Vladislav Khorev", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText5", + "width": 300, + "height": 40, + "text": "Leila Bobrova", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText6", + "width": 300, + "height": 20, + "text": "Story:", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 0.85, 0.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText7", + "width": 300, + "height": 20, + "text": "Vladislav Khorev", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText8", + "width": 300, + "height": 40, + "text": "Leila Bobrova", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText9", + "width": 300, + "height": 20, + "text": "Development:", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 0.85, 0.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText10", + "width": 300, + "height": 20, + "text": "Vladislav Khorev", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText11", + "width": 300, + "height": 20, + "text": "Vladislav Kan", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText12", + "width": 300, + "height": 20, + "text": "Beksultan Almazbekov", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText13", + "width": 300, + "height": 40, + "text": "Sergei Zotov", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText14", + "width": 300, + "height": 20, + "text": "Characters and animations:", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 0.85, 0.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText15", + "width": 300, + "height": 20, + "text": "Dmitry Prohorchik", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText16", + "width": 300, + "height": 40, + "text": "Roman Trestcov", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText17", + "width": 300, + "height": 20, + "text": "3D Assets:", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 0.85, 0.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText18", + "width": 300, + "height": 20, + "text": "Dmitry Prohorchik", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText19", + "width": 300, + "height": 40, + "text": "Ishmaladi (turbosquid.com)", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText20", + "width": 300, + "height": 20, + "text": "Arts:", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 0.85, 0.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText21", + "width": 300, + "height": 40, + "text": "Maggie Tem", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText22", + "width": 300, + "height": 20, + "text": "UI/UX:", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 0.85, 0.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText23", + "width": 300, + "height": 40, + "text": "Kenje Kazmatova", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText24", + "width": 300, + "height": 20, + "text": "Music:", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 0.85, 0.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText25", + "width": 300, + "height": 40, + "text": "Pazetic_Ocean", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText26", + "width": 300, + "height": 20, + "text": "Sounds:", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 0.85, 0.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText27", + "width": 300, + "height": 300, + "text": "IENBA, ImAFoley, stradie,\nItsTheGoodstuff, uwesoundboiz,\nAnthousai, Foxfire-,\nn-lerche, CogFireStudios,\nadcbicycle, AntoineRomo,\nSmallConfusion, joedeshon,\nefectirijillo (FreeSound.org)", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "creditsText28", + "width": 300, + "height": 40, + "text": "Some art, 3D models\nand textures were created\nusing neural networks.", + "fontSize": 32, + "textCentered": false, + "color": [1.0, 1.0, 1.0, 1.0] + } + ] + }, + { + "type": "Button", + "name": "creditsBackButton", + "width": "match_parent", + "height": "match_parent", + "x": 0, + "y": 0, + "textures": { + "normal": "resources/transparent.png", + "hover": "resources/transparent.png", + "pressed": "resources/transparent.png" + } + } + ] + } +} diff --git a/resources/w/ui_en/screen_main_menu.json b/resources/w/ui_en/screen_main_menu.json index 612fc3f..40b95c8 100644 --- a/resources/w/ui_en/screen_main_menu.json +++ b/resources/w/ui_en/screen_main_menu.json @@ -39,7 +39,7 @@ "width": 320, "height": 180, "y": 235, - "texture": "resources/w/ui_en/img/main/letters002.png" + "texture": "resources/w/ui/img/main/letters002.png" }, { "type": "LinearLayout", @@ -47,7 +47,7 @@ "horizontal_align": "center", "vertical_align": "center", "spacing": 24, - "y": 150, + "y": 140, "width": "match_parent", "height": "match_parent", "children": [ @@ -55,7 +55,7 @@ "type": "TextButton", "name": "menuStartButton", "width": 300, - "height": 50, + "height": 44, "text": "Start Game", "fontSize": 48, "textCentered": true @@ -64,7 +64,7 @@ "type": "TextButton", "name": "menuLoadButton", "width": 300, - "height": 50, + "height": 44, "text": "Load Game", "fontSize": 48, "textCentered": true @@ -73,7 +73,7 @@ "type": "TextButton", "name": "menuSettingsButton", "width": 300, - "height": 50, + "height": 44, "text": "Settings", "fontSize": 48, "textCentered": true @@ -82,7 +82,7 @@ "type": "TextButton", "name": "menuLanguageButton", "width": 300, - "height": 50, + "height": 44, "text": "Язык / Language", "fontSize": 48, "textCentered": true @@ -91,8 +91,17 @@ "type": "TextButton", "name": "menuAboutButton", "width": 300, - "height": 50, - "text": "About Us", + "height": 44, + "text": "About Game", + "fontSize": 48, + "textCentered": true + }, + { + "type": "TextButton", + "name": "menuCreditsButton", + "width": 300, + "height": 44, + "text": "Credits", "fontSize": 48, "textCentered": true }, @@ -100,7 +109,7 @@ "type": "TextButton", "name": "menuExitButton", "width": 300, - "height": 50, + "height": 44, "text": "Exit", "fontSize": 48, "textCentered": true @@ -116,7 +125,7 @@ "y": 10, "width": 160, "height": 36, - "text": "v. 0.7", + "text": "v. 1.0", "fontSize": 24, "textCentered": false, "topAligned": true, diff --git a/src/MenuManager.cpp b/src/MenuManager.cpp index 095fd2c..eace199 100644 --- a/src/MenuManager.cpp +++ b/src/MenuManager.cpp @@ -215,6 +215,7 @@ namespace ZL { portraitQuestJournalItemRoot = loadLocalizedUi("resources/w/ui/portrait_screen_journal_item.json", zipFile); mainMenuRoot = loadLocalizedUi("resources/w/ui/screen_main_menu.json", zipFile); aboutScreenRoot = loadLocalizedUi("resources/w/ui/screen_about.json", zipFile); + creditsScreenRoot = loadLocalizedUi("resources/w/ui/screen_credits.json", zipFile); settingsScreenRoot = loadLocalizedUi("resources/w/ui/screen_settings.json", zipFile); settingsGraphicsScreenRoot = loadLocalizedUi("resources/w/ui/screen_settings_graphics.json", zipFile); languageScreenRoot = loadLocalizedUi("resources/w/ui/screen_language.json", zipFile); @@ -344,6 +345,8 @@ namespace ZL { topUiManager.clearMenuStack(); uiManager.replaceRoot(mainMenuRoot); + + uiManager.setTextButtonCallback("menuStartButton", [this](const std::string&) { audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); enterGameplay(); @@ -353,6 +356,10 @@ namespace ZL { showAboutScreen(); audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); + uiManager.setTextButtonCallback("menuCreditsButton", [this](const std::string&) { + showCreditsScreen(); + audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + }); uiManager.setTextButtonCallback("menuSettingsButton", [this](const std::string&) { showSettingsScreen(); audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); @@ -387,6 +394,19 @@ namespace ZL { }); } + void MenuManager::showCreditsScreen() { + uiManager.pushMenuFromSavedRoot(creditsScreenRoot); + uiManager.setButtonCallback("creditsBackButton", [this](const std::string&) { + uiManager.popMenu(); + uiManager.updateAllLayouts(); + audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + }); + + uiManager.stopAnimationOnNode("creditsLinearLayout", "slowScroll"); + uiManager.resetAnimationOnNode("creditsLinearLayout"); + uiManager.startAnimationOnNode("creditsLinearLayout", "slowScroll"); + } + void MenuManager::showSettingsScreen() { uiManager.pushMenuFromSavedRoot(settingsScreenRoot); uiManager.setTextButtonCallback("settingsBackButton", [this](const std::string&) { diff --git a/src/MenuManager.h b/src/MenuManager.h index 8bcf4d4..0e09171 100644 --- a/src/MenuManager.h +++ b/src/MenuManager.h @@ -172,6 +172,7 @@ namespace ZL { //void closePortraitJournalItemCompletely(); void showAboutScreen(); + void showCreditsScreen(); void showSettingsScreen(); void showSettingsGraphicsScreen(); void showLanguageScreen(); @@ -262,6 +263,7 @@ namespace ZL { std::shared_ptr mainMenuRoot; std::shared_ptr aboutScreenRoot; + std::shared_ptr creditsScreenRoot; std::shared_ptr settingsScreenRoot; std::shared_ptr settingsGraphicsScreenRoot; std::shared_ptr languageScreenRoot; diff --git a/src/UiManager.cpp b/src/UiManager.cpp index 87f96ce..965e631 100644 --- a/src/UiManager.cpp +++ b/src/UiManager.cpp @@ -182,7 +182,7 @@ namespace ZL { mesh.RefreshVBO(); } - void UiTextButton::draw(Renderer& renderer) const { + void UiTextButton::draw(Renderer& renderer, float accumX, float accumY) const { // Draw background texture (optional) const std::shared_ptr* tex = nullptr; @@ -221,16 +221,18 @@ namespace ZL { ? wrapTextByPixels(text, *textRenderer, rect.w - textPaddingX * 2.0f, scale) : text; - float tx = rect.x + rect.w / 2.0f + animOffsetX; + // Прибавляем accumX и accumY + float tx = rect.x + rect.w / 2.0f + animOffsetX + accumX; if (!textCentered) { - tx = rect.x + textPaddingX + animOffsetX; + tx = rect.x + textPaddingX + animOffsetX + accumX; } float ty; if (topAligned) { - ty = rect.y + rect.h - textPaddingY - static_cast(fontSize) + animOffsetY; - } else { - ty = rect.y + rect.h * 0.5f + textPaddingY + animOffsetY; + ty = rect.y + rect.h - textPaddingY - static_cast(fontSize) + animOffsetY + accumY; + } + else { + ty = rect.y + rect.h * 0.5f + textPaddingY + animOffsetY + accumY; } textRenderer->drawText(displayText, tx, ty, scale, textCentered, color); @@ -238,24 +240,19 @@ namespace ZL { glEnable(GL_DEPTH_TEST); } - void UiTextView::draw(Renderer& renderer) const { + void UiTextView::draw(Renderer& renderer, float accumX, float accumY) const { (void)renderer; if (!textRenderer || text.empty()) { return; } const float scale = 1.0f; - - // Backward compatibility: - // Old UI files, including the original inventory panel, positioned TextView - // around the rect center. If a TextView does not explicitly request wrapping, - // top alignment, padding or line limiting, keep that old behavior. const bool usesModernRectText = wrap || topAligned || paddingX != 0.0f || paddingY != 0.0f || maxLines > 0; if (!usesModernRectText) { textRenderer->drawText( text, - rect.x + rect.w * 0.5f, - rect.y + rect.h * 0.5f, + rect.x + rect.w * 0.5f + accumX, + rect.y + rect.h * 0.5f + accumY, scale, textCentered, color @@ -272,12 +269,10 @@ namespace ZL { float ty = rect.y + rect.h * 0.5f; if (topAligned) { - // TextRenderer expects a baseline position. This offset places the first - // visible line close to the top inside the TextView rectangle. ty = rect.y + rect.h - paddingY - static_cast(fontSize); } - textRenderer->drawText(finalText, tx, ty, scale, textCentered, color); + textRenderer->drawText(finalText, tx + accumX, ty + accumY, scale, textCentered, color); } void UiSlider::buildTrackMesh() { @@ -1480,60 +1475,61 @@ namespace ZL { renderer.LoadIdentity(); renderer.RenderUniform1f("uAlpha", 1.0f); // ensure alpha is fully opaque unless a widget overrides it - std::function&)> drawNode = - [&](const std::shared_ptr& node) { + std::function&, float, float)> drawNode = + [&](const std::shared_ptr& node, float accumX, float accumY) { if (!node || !node->visible) return; + // Считаем полное смещение для текущего узла и его потомков + float curAccumX = accumX + node->animOffsetX; + float curAccumY = accumY + node->animOffsetY; + renderer.PushMatrix(); - renderer.TranslateMatrix({ node->screenRect.x + node->screenRect.w * 0.5f, + renderer.TranslateMatrix({ node->animOffsetX, node->animOffsetY, 0.0f }); + + renderer.TranslateMatrix({ node->screenRect.x + node->screenRect.w * 0.5f, node->screenRect.y + node->screenRect.h * 0.5f, 0.0f }); renderer.ScaleMatrix({ node->scaleX, node->scaleY, 1.0f }); - renderer.TranslateMatrix({ -(node->screenRect.x + node->screenRect.w * 0.5f), + renderer.TranslateMatrix({ -(node->screenRect.x + node->screenRect.w * 0.5f), -(node->screenRect.y + node->screenRect.h * 0.5f), 0.0f }); - - // 1. Сначала изображения if (node->staticImage) { node->staticImage->draw(renderer); } - // 1b. Chat bubble (composite widget) if (node->chatBubble) { node->chatBubble->draw(renderer, node->scaleX, node->scaleY, projW, projH); } - // 2. Потом кнопки if (node->button) { node->button->draw(renderer); } if (node->textButton) { - node->textButton->draw(renderer); + // Передаем накопленные смещения + node->textButton->draw(renderer, curAccumX, curAccumY); } - - // 3. Потом слайдеры + if (node->slider) { node->slider->draw(renderer); } - - // 4. Потом текстовые поля + if (node->textField) { node->textField->draw(renderer); } - - // 5. ПОСЛЕДНИЙ - ТЕКСТ (поверх всего!) + if (node->textView) { - node->textView->draw(renderer); + // Передаем накопленные смещения + node->textView->draw(renderer, curAccumX, curAccumY); } - // Рисуем детей for (auto& child : node->children) { - drawNode(child); + // Прокидываем сумму дальше + drawNode(child, curAccumX, curAccumY); } renderer.PopMatrix(); }; - drawNode(root); + drawNode(root, 0.0f, 0.0f); renderer.PopMatrix(); renderer.PopProjectionMatrix(); @@ -1625,6 +1621,12 @@ namespace ZL { node->textButton->animScaleX = act.origScaleX; node->textButton->animScaleY = act.origScaleY; } + else + { + //logger() << "[animation] Resetting animation for node: " << node->name << " y=" << act.origOffsetY << std::endl; + node->animOffsetX = act.origOffsetX; + node->animOffsetY = act.origOffsetY; + } act.stepIndex = 0; act.elapsedMs = 0.0f; act.stepStarted = false; @@ -1650,6 +1652,12 @@ namespace ZL { node->textButton->animOffsetX = step.toX; node->textButton->animOffsetY = step.toY; } + else + { + //logger() << "[animation] step.durationMs <= 0.0f: " << node->name << " y=" << step.toY << std::endl; + node->animOffsetX = step.toX; + node->animOffsetY = step.toY; + } } else if (step.type == "scale") { if (node->button) { @@ -1681,8 +1689,9 @@ namespace ZL { act.origScaleY = node->textButton->animScaleY; } else { - act.origOffsetX = act.origOffsetY = 0.0f; - act.origScaleX = act.origScaleY = 1.0f; + //logger() << "[animation] !act.stepStarted && act.stepIndex == 0 && act.elapsedMs == 0.0f: " << node->name << " y=" << node->animOffsetY << std::endl; + act.origOffsetX = node->animOffsetX; + act.origOffsetY = node->animOffsetY; } } @@ -1703,8 +1712,11 @@ namespace ZL { act.startScaleY = node->textButton->animScaleY; } else { - act.startOffsetX = act.startOffsetY = 0.0f; - act.startScaleX = act.startScaleY = 1.0f; + //logger() << "[animation] !act.stepStarted && prevElapsed == 0.0f: " << node->name << " y=" << node->animOffsetY << std::endl; + act.startOffsetX = node->animOffsetX; + act.startOffsetY = node->animOffsetY; + act.startScaleX = node->scaleX; + act.startScaleY = node->scaleY; } if (step.type == "move") { act.endOffsetX = step.toX; @@ -1731,6 +1743,12 @@ namespace ZL { node->textButton->animOffsetX = nx; node->textButton->animOffsetY = ny; } + else { + //logger() << "[animation] if (step.type ==move): " << node->name << " y=" << ny << std::endl; + + node->animOffsetX = nx; + node->animOffsetY = ny; + } } else if (step.type == "scale") { float sx = act.startScaleX + (act.endScaleX - act.startScaleX) * te; @@ -1986,12 +2004,46 @@ namespace ZL { aa.origScaleX = node->textButton->animScaleX; aa.origScaleY = node->textButton->animScaleY; } + else + { + //logger() << "[animation] startAnimationOnNode: " << node->name << " y=" << node->animOffsetY << std::endl; + aa.origOffsetX = node->animOffsetX; + aa.origOffsetY = node->animOffsetY; + } auto cbIt = animCallbacks.find({ nodeName, animName }); if (cbIt != animCallbacks.end()) aa.onComplete = cbIt->second; nodeActiveAnims[node].push_back(std::move(aa)); return true; } + bool UiManager::resetAnimationOnNode(const std::string& nodeName) + { + if (!root) return false; + auto node = findNodeByName(root, nodeName); + if (!node) return false; + + if (node->button) { + node->button->animOffsetX = 0; + node->button->animOffsetY = 0; + node->button->animScaleX = 1.0f; + node->button->animScaleY = 1.0f; + } + else if (node->textButton) { + node->textButton->animOffsetX = 0; + node->textButton->animOffsetY = 0; + node->textButton->animScaleX = 1.0f; + node->textButton->animScaleY = 1.0f; + } + else + { + //logger() << "[animation] startAnimationOnNode: " << node->name << " y=" << node->animOffsetY << std::endl; + node->animOffsetX = 0; + node->animOffsetY = 0; + } + + return true; + } + bool UiManager::stopAnimationOnNode(const std::string& nodeName, const std::string& animName) { if (!root) return false; auto node = findNodeByName(root, nodeName); @@ -2042,6 +2094,13 @@ namespace ZL { aa.origScaleX = n->textButton->animScaleX; aa.origScaleY = n->textButton->animScaleY; } + else + { + + //logger() << "[animation] startAnimation: " << n->name << " y=" << n->animOffsetY << std::endl; + aa.origOffsetX = n->animOffsetX; + aa.origOffsetY = n->animOffsetY; + } auto cbIt = animCallbacks.find({ n->name, animName }); if (cbIt != animCallbacks.end()) aa.onComplete = cbIt->second; nodeActiveAnims[n].push_back(std::move(aa)); diff --git a/src/UiManager.h b/src/UiManager.h index 384aba1..2ad0a4d 100644 --- a/src/UiManager.h +++ b/src/UiManager.h @@ -187,7 +187,7 @@ namespace ZL { float animScaleY = 1.0f; void buildMesh(); - void draw(Renderer& renderer) const; + void draw(Renderer& renderer, float accumX = 0.0f, float accumY = 0.0f) const; }; struct UiTextView { @@ -206,7 +206,7 @@ namespace ZL { std::unique_ptr textRenderer; - void draw(Renderer& renderer) const; + void draw(Renderer& renderer, float accumX = 0.0f, float accumY = 0.0f) const; }; struct UiTextField { @@ -313,6 +313,8 @@ namespace ZL { float scaleX = 1.0f; float scaleY = 1.0f; + float animOffsetX = 0.0f; + float animOffsetY = 0.0f; bool visible = true; @@ -475,6 +477,7 @@ namespace ZL { void update(float deltaMs); void startAnimation(const std::string& animName); bool startAnimationOnNode(const std::string& nodeName, const std::string& animName); + bool resetAnimationOnNode(const std::string& nodeName); 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();