diff --git a/UnleashedRecomp/CMakeLists.txt b/UnleashedRecomp/CMakeLists.txt index be4a5d34..40a9fc30 100644 --- a/UnleashedRecomp/CMakeLists.txt +++ b/UnleashedRecomp/CMakeLists.txt @@ -77,7 +77,6 @@ set(SWA_PATCHES_CXX_SOURCES set(SWA_UI_CXX_SOURCES "ui/achievement_menu.cpp" "ui/achievement_overlay.cpp" - "ui/imgui_view.cpp" "ui/options_menu.cpp" "ui/sdl_listener.cpp" "ui/window.cpp" diff --git a/UnleashedRecomp/app.h b/UnleashedRecomp/app.h index a303ba47..4e1d379e 100644 --- a/UnleashedRecomp/app.h +++ b/UnleashedRecomp/app.h @@ -1,6 +1,3 @@ #pragma once -#include - extern double g_deltaTime; -extern XDBFWrapper g_xdbf; diff --git a/UnleashedRecomp/gpu/video.cpp b/UnleashedRecomp/gpu/video.cpp index d76c7c9a..e8504e3f 100644 --- a/UnleashedRecomp/gpu/video.cpp +++ b/UnleashedRecomp/gpu/video.cpp @@ -5,9 +5,12 @@ #include #include #include +#include #include #include -#include +#include +#include +#include #include "imgui_snapshot.h" #include "imgui_common.h" @@ -1011,8 +1014,9 @@ static void CreateImGuiBackend() io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange; - for (auto& view : GetImGuiViews()) - view->Init(); + AchievementMenu::Init(); + AchievementOverlay::Init(); + OptionsMenu::Init(); ImGui_ImplSDL2_InitForOther(Window::s_pWindow); @@ -1310,6 +1314,18 @@ static void CreateHostDevice() desc.renderTargetBlend[0] = RenderBlendDesc::Copy(); desc.renderTargetCount = 1; g_gammaCorrectionPipeline = g_device->createGraphicsPipeline(desc); + + g_xdbfTextureCache = std::unordered_map(); + + for (auto& achievement : g_xdbfWrapper.GetAchievements(XDBF_LANGUAGE_ENGLISH)) + { + // huh? + if (!achievement.pImageBuffer || !achievement.ImageBufferSize) + continue; + + g_xdbfTextureCache[achievement.ID] = + LoadTexture((uint8_t*)achievement.pImageBuffer, achievement.ImageBufferSize).release(); + } } static void WaitForGPU() @@ -1653,8 +1669,9 @@ static void DrawImGui() ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); - for (auto& view : GetImGuiViews()) - view->Draw(); + AchievementMenu::Draw(); + OptionsMenu::Draw(); + AchievementOverlay::Draw(); ImGui::Render(); diff --git a/UnleashedRecomp/kernel/xdbf.h b/UnleashedRecomp/kernel/xdbf.h new file mode 100644 index 00000000..7f239764 --- /dev/null +++ b/UnleashedRecomp/kernel/xdbf.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include + +extern XDBFWrapper g_xdbfWrapper; +extern std::unordered_map g_xdbfTextureCache; diff --git a/UnleashedRecomp/main.cpp b/UnleashedRecomp/main.cpp index ea5b14ec..f0a47471 100644 --- a/UnleashedRecomp/main.cpp +++ b/UnleashedRecomp/main.cpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #define GAME_XEX_PATH "game:\\default.xex" @@ -24,7 +24,8 @@ const size_t XMAIOEnd = XMAIOBegin + 0x0000FFFF; Memory g_memory{ reinterpret_cast(0x100000000), 0x100000000 }; Heap g_userHeap; CodeCache g_codeCache; -XDBFWrapper g_xdbf; +XDBFWrapper g_xdbfWrapper; +std::unordered_map g_xdbfTextureCache; // Name inspired from nt's entry point void KiSystemStartup() @@ -124,7 +125,7 @@ uint32_t LdrLoadModule(const char* path) auto res = Xex2FindOptionalHeader(xex, XEX_HEADER_RESOURCE_INFO); - g_xdbf = XDBFWrapper((uint8_t*)g_memory.Translate(res->Offset.get()), res->SizeOfData); + g_xdbfWrapper = XDBFWrapper((uint8_t*)g_memory.Translate(res->Offset.get()), res->SizeOfData); return entry; } diff --git a/UnleashedRecomp/ui/achievement_menu.cpp b/UnleashedRecomp/ui/achievement_menu.cpp index e37962b7..ec7867d4 100644 --- a/UnleashedRecomp/ui/achievement_menu.cpp +++ b/UnleashedRecomp/ui/achievement_menu.cpp @@ -1,136 +1,115 @@ #include "achievement_menu.h" #include "imgui_utils.h" -#include #include #include +#include #include #include #include #include #include -AchievementMenu m_achievementMenu; +static std::vector m_achievements; -static std::vector g_achievements; -static std::unordered_map g_achievementTextures; +static ImFont* m_fntSeurat; +static ImFont* m_fntNewRodin; -static ImFont* g_fntSeurat; -static ImFont* g_fntNewRodin; +static int m_firstVisibleRowIndex; +static int m_prevSelectedRowIndex; +static int m_selectedRowIndex; +static double m_rowSelectionTime; -static int g_firstVisibleRowIndex; -static int g_prevSelectedRowIndex; -static int g_selectedRowIndex; -static double g_rowSelectionTime; - -static bool g_upWasHeld; -static bool g_downWasHeld; +static bool m_upWasHeld; +static bool m_downWasHeld; static void ResetSelection() { - g_firstVisibleRowIndex = 0; - g_selectedRowIndex = 0; - g_prevSelectedRowIndex = 0; - g_rowSelectionTime = ImGui::GetTime(); - g_upWasHeld = false; - g_downWasHeld = false; + m_firstVisibleRowIndex = 0; + m_selectedRowIndex = 0; + m_prevSelectedRowIndex = 0; + m_rowSelectionTime = ImGui::GetTime(); + m_upWasHeld = false; + m_downWasHeld = false; } -void AchievementMenu::Init() -{ - auto& io = ImGui::GetIO(); - - constexpr float FONT_SCALE = 2.0f; - - g_fntSeurat = io.Fonts->AddFontFromFileTTF("FOT-SeuratPro-M.otf", 24.0f * FONT_SCALE); - g_fntNewRodin = io.Fonts->AddFontFromFileTTF("FOT-NewRodinPro-UB.otf", 20.0f * FONT_SCALE); - - g_achievements = g_xdbf.GetAchievements((EXDBFLanguage)Config::Language.Value); - - for (auto& achievement : g_achievements) - { - auto texture = LoadTexture((uint8_t*)achievement.pImageBuffer, achievement.ImageBufferSize); - g_achievementTextures[achievement.ID] = texture.release(); - } -} - -static void DrawContainer(ImVec2 min, ImVec2 max, ImU32 gradientTop, ImU32 gradientBottom, float cornerRadius = 25.0f) +static void DrawContainer(ImVec2 min, ImVec2 max, ImU32 gradientTop, ImU32 gradientBottom, float cornerRadius = 25) { auto drawList = ImGui::GetForegroundDrawList(); - - ImVec2 v1 = { min.x, min.y + cornerRadius }; - ImVec2 v2 = { min.x + cornerRadius, min.y }; - ImVec2 v3 = { max.x, min.y }; - ImVec2 v4 = { max.x, min.y + cornerRadius }; - ImVec2 v5 = { max.x, max.y - cornerRadius }; - ImVec2 v6 = { max.x - cornerRadius, max.y }; - ImVec2 v7 = { min.x, max.y }; - ImVec2 v8 = { min.x, max.y - cornerRadius }; - ImVec2 vertices[] = { v1, v2, v3, v4, v5, v6, v7, v8 }; + auto vertices = GetPauseContainerVertices(min, max, cornerRadius); // TODO: add a drop shadow. SetGradient(min, max, gradientTop, gradientBottom); - drawList->AddConvexPolyFilled(vertices, IM_ARRAYSIZE(vertices), IM_COL32(255, 255, 255, 255)); + drawList->AddConvexPolyFilled(vertices.data(), vertices.size(), IM_COL32(255, 255, 255, 255)); ResetGradient(); - drawList->AddPolyline(vertices, IM_ARRAYSIZE(vertices), IM_COL32(247, 247, 247, 255), true, Scale(2.5f)); + drawList->AddPolyline(vertices.data(), vertices.size(), IM_COL32(247, 247, 247, 255), true, Scale(2.5f)); - for (int i = 0; i < IM_ARRAYSIZE(vertices); i++) + for (int i = 0; i < vertices.size(); i++) { - vertices[i].x -= 0.4f; - vertices[i].y -= 0.2f; + vertices[i].x -= Scale(0.4f); + vertices[i].y -= Scale(0.2f); } auto colLineTop = IM_COL32(165, 170, 165, 230); auto colLineBottom = IM_COL32(190, 190, 190, 230); - auto lineThickness = Scale(1.0f); + auto lineThickness = Scale(1); // Top left corner bottom to top left corner top. - drawList->AddLine(vertices[0], vertices[1], colLineTop, lineThickness * 0.5f); + drawList->AddLine(vertices[0], vertices[1], colLineTop, lineThickness * Scale(0.5f)); // Top left corner bottom to bottom left. - drawList->AddRectFilledMultiColor({ vertices[0].x - 0.2f, vertices[0].y }, { vertices[6].x + lineThickness - 0.2f, vertices[6].y }, colLineTop, colLineTop, colLineBottom, colLineBottom); + drawList->AddRectFilledMultiColor + ( + { /* X */ vertices[0].x - Scale(0.2f), /* Y */ vertices[0].y }, + { /* X */ vertices[6].x + lineThickness - Scale(0.2f), /* Y */ vertices[6].y }, + colLineTop, + colLineTop, + colLineBottom, + colLineBottom + ); // Top left corner top to top right. drawList->AddLine(vertices[1], vertices[2], colLineTop, lineThickness); - drawList->PushClipRect({ min.x, min.y + 20.0f }, { max.x, max.y - 5.0f }); + drawList->PushClipRect({ min.x, min.y + Scale(20) }, { max.x, max.y - Scale(5) }); } static void DrawSelectionContainer(ImVec2 min, ImVec2 max) { auto drawList = ImGui::GetForegroundDrawList(); - - auto cornerRadius = Scale(10.0f); - ImVec2 v1 = { min.x, min.y + cornerRadius }; - ImVec2 v2 = { min.x + cornerRadius, min.y }; - ImVec2 v3 = { max.x, min.y }; - ImVec2 v4 = { max.x, min.y + cornerRadius }; - ImVec2 v5 = { max.x, max.y - cornerRadius }; - ImVec2 v6 = { max.x - cornerRadius, max.y }; - ImVec2 v7 = { min.x, max.y }; - ImVec2 v8 = { min.x, max.y - cornerRadius }; - ImVec2 vertices[] = { v1, v2, v3, v4, v5, v6, v7, v8 }; + auto vertices = GetPauseContainerVertices(min, max, 10); SetGradient(min, max, IM_COL32(255, 246, 0, 129), IM_COL32(255, 194, 0, 118)); - drawList->AddConvexPolyFilled(vertices, IM_ARRAYSIZE(vertices), IM_COL32(255, 255, 255, 255)); + drawList->AddConvexPolyFilled(vertices.data(), vertices.size(), IM_COL32(255, 255, 255, 255)); ResetGradient(); } static void DrawHeaderContainer(const char* text) { auto drawList = ImGui::GetForegroundDrawList(); + auto fontSize = Scale(26); + auto textSize = m_fntNewRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, text); + auto cornerRadius = 23; + auto textMarginX = Scale(16) + (Scale(cornerRadius) / 2); - ImVec2 min = { Scale(256.0f), Scale(138.0f) }; - ImVec2 max = { Scale(556.0f), Scale(185.0f) }; + ImVec2 min = { Scale(256), Scale(138) }; + ImVec2 max = { min.x + textMarginX * 2 + textSize.x, Scale(185) }; - DrawContainer(min, max, IM_COL32(140, 142, 140, 201), IM_COL32(66, 65, 66, 234), Scale(23.0f)); + DrawContainer(min, max, IM_COL32(140, 142, 140, 201), IM_COL32(66, 65, 66, 234), cornerRadius); drawList->PopClipRect(); - auto textSize = g_fntNewRodin->CalcTextSizeA(Scale(26.0f), FLT_MAX, 0.0f, text); - // TODO: skew this text and apply bevel. - DrawTextWithOutline(g_fntNewRodin, Scale(26.0f), { min.x + Scale(20.0f), min.y + textSize.y / 2.0f - 3.0f }, IM_COL32(255, 255, 255, 255), text, Scale(3), IM_COL32(0, 0, 0, 255)); + DrawTextWithOutline + ( + m_fntNewRodin, + fontSize, + { /* X */ min.x + textMarginX, /* Y */ min.y + textSize.y / Scale(2) + Scale(2.5f) /* 2.5 = container outline thickness */ }, + IM_COL32(255, 255, 255, 255), + text, + 3, + IM_COL32(0, 0, 0, 255) + ); } static void DrawAchievement(int rowIndex, float yOffset, Achievement& achievement, bool isUnlocked) @@ -140,51 +119,99 @@ static void DrawAchievement(int rowIndex, float yOffset, Achievement& achievemen auto clipRectMin = drawList->GetClipRectMin(); auto clipRectMax = drawList->GetClipRectMax(); - auto itemWidth = Scale(708.0f); - auto itemHeight = Scale(94.0f); - auto itemMarginX = Scale(13.0f); - auto imageMarginX = Scale(25.0f); - auto imageMarginY = Scale(18.0f); - auto imageSize = Scale(60.0f); + auto itemWidth = Scale(708); + auto itemHeight = Scale(94); + auto itemMarginX = Scale(13); + auto imageMarginX = Scale(25); + auto imageMarginY = Scale(18); + auto imageSize = Scale(60); ImVec2 min = { itemMarginX + clipRectMin.x, clipRectMin.y + itemHeight * rowIndex + yOffset }; ImVec2 max = { itemMarginX + min.x + itemWidth, min.y + itemHeight }; - auto icon = g_achievementTextures[achievement.ID]; - auto isSelected = rowIndex == g_selectedRowIndex; + auto icon = g_xdbfTextureCache[achievement.ID]; + auto isSelected = rowIndex == m_selectedRowIndex; if (isSelected) DrawSelectionContainer(min, max); - auto alpha = isUnlocked ? 255 : 127; auto desc = isUnlocked ? achievement.UnlockedDesc.c_str() : achievement.LockedDesc.c_str(); - auto fontSize = Scale(24.0f); - auto textSize = g_fntSeurat->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, desc); - auto textX = min.x + imageMarginX + imageSize + itemMarginX * 2.0f; + auto fontSize = Scale(24); + auto textSize = m_fntSeurat->CalcTextSizeA(fontSize, FLT_MAX, 0, desc); + auto textX = min.x + imageMarginX + imageSize + itemMarginX * 2; auto textMarqueeX = min.x + imageMarginX + imageSize; - auto titleTextY = Scale(20.0f); - auto descTextY = Scale(52.0f); - auto cmnShadowOffset = Scale(2.0f); - auto cmnShadowScale = Scale(0.4f); + auto titleTextY = Scale(20); + auto descTextY = Scale(52); // Draw achievement icon. - // TODO: make image greyscale if locked. - drawList->AddImage(icon, { min.x + imageMarginX, min.y + imageMarginY }, { min.x + imageMarginX + imageSize, min.y + imageMarginY + imageSize }, { 0, 0 }, { 1, 1 }, IM_COL32(255, 255, 255, alpha)); + // TODO: make icon greyscale if locked? + drawList->AddImage + ( + icon, + { /* X */ min.x + imageMarginX, /* Y */ min.y + imageMarginY }, + { /* X */ min.x + imageMarginX + imageSize, /* Y */ min.y + imageMarginY + imageSize }, + { /* U */ 0, /* V */ 0 }, + { /* U */ 1, /* V */ 1 }, + IM_COL32(255, 255, 255, 255 * (isUnlocked ? 1 : 0.5f)) + ); drawList->PushClipRect(min, max, true); + auto colLockedText = IM_COL32(60, 60, 60, 29); + + auto colTextShadow = isUnlocked + ? IM_COL32(0, 0, 0, 255) + : IM_COL32(60, 60, 60, 28); + + auto shadowOffset = isUnlocked ? 2 : 1; + // Draw achievement name. - DrawTextWithShadow(g_fntSeurat, fontSize, { textX, min.y + titleTextY }, IM_COL32(252, 243, 5, alpha), achievement.Name.c_str(), cmnShadowOffset, cmnShadowScale, IM_COL32(0, 0, 0, alpha)); + DrawTextWithShadow + ( + m_fntSeurat, + fontSize, + { textX, min.y + titleTextY }, + isUnlocked ? IM_COL32(252, 243, 5, 255) : colLockedText, + achievement.Name.c_str(), + shadowOffset, + 0.4f, + colTextShadow + ); if (isSelected && textX + textSize.x >= max.x) { // Draw achievement description with marquee. - DrawTextWithMarqueeShadow(g_fntSeurat, fontSize, { textX, min.y + descTextY }, { textMarqueeX, min.y }, max, IM_COL32(255, 255, 255, alpha), desc, g_rowSelectionTime, 0.9, 250.0, cmnShadowOffset, cmnShadowScale); + DrawTextWithMarqueeShadow + ( + m_fntSeurat, + fontSize, + { textX, min.y + descTextY }, + { textMarqueeX, min.y }, + max, + isUnlocked ? IM_COL32(255, 255, 255, 255) : colLockedText, + desc, + m_rowSelectionTime, + 0.9, + 250.0, + shadowOffset, + 0.4f, + colTextShadow + ); } else { // Draw achievement description. - DrawTextWithShadow(g_fntSeurat, fontSize, { textX, min.y + descTextY }, IM_COL32(255, 255, 255, alpha), desc, cmnShadowOffset, cmnShadowScale, IM_COL32(0, 0, 0, alpha)); + DrawTextWithShadow + ( + m_fntSeurat, + fontSize, + { textX, min.y + descTextY }, + isUnlocked ? IM_COL32(255, 255, 255, 255) : colLockedText, + desc, + shadowOffset, + 0.4f, + colTextShadow + ); } drawList->PopClipRect(); @@ -194,39 +221,39 @@ static void DrawContentContainer() { auto drawList = ImGui::GetForegroundDrawList(); - ImVec2 min = { Scale(256.0f), Scale(192.0f) }; - ImVec2 max = { Scale(1026.0f), Scale(601.0f) }; + ImVec2 min = { Scale(256), Scale(192) }; + ImVec2 max = { Scale(1026), Scale(601) }; - DrawContainer(min, max, IM_COL32(197, 194, 197, 200), IM_COL32(115, 113, 115, 236), Scale(25.0f)); + DrawContainer(min, max, IM_COL32(197, 194, 197, 200), IM_COL32(115, 113, 115, 236)); auto clipRectMin = drawList->GetClipRectMin(); auto clipRectMax = drawList->GetClipRectMax(); - auto itemHeight = Scale(94.0f); - auto yOffset = -g_firstVisibleRowIndex * itemHeight + Scale(2.0f); + auto itemHeight = Scale(94); + auto yOffset = -m_firstVisibleRowIndex * itemHeight + Scale(2); auto rowCount = 0; // Draw separators. for (int i = 1; i <= 3; i++) { - auto lineMarginLeft = Scale(31.0f); - auto lineMarginRight = Scale(46.0f); - auto lineMarginY = Scale(2.0f); + auto lineMarginLeft = Scale(31); + auto lineMarginRight = Scale(46); + auto lineMarginY = Scale(2); ImVec2 lineMin = { clipRectMin.x + lineMarginLeft, clipRectMin.y + itemHeight * i + lineMarginY }; ImVec2 lineMax = { clipRectMax.x - lineMarginRight, clipRectMin.y + itemHeight * i + lineMarginY }; drawList->AddLine(lineMin, lineMax, IM_COL32(163, 163, 163, 255)); - drawList->AddLine({ lineMin.x, lineMin.y + Scale(1.0f) }, { lineMax.x, lineMax.y + Scale(1.0f) }, IM_COL32(143, 148, 143, 255)); + drawList->AddLine({ lineMin.x, lineMin.y + Scale(1) }, { lineMax.x, lineMax.y + Scale(1) }, IM_COL32(143, 148, 143, 255)); } - for (auto achievement : g_achievements) + for (auto achievement : m_achievements) { if (AchievementData::IsUnlocked(achievement.ID)) DrawAchievement(rowCount++, yOffset, achievement, true); } - for (auto achievement : g_achievements) + for (auto achievement : m_achievements) { if (!AchievementData::IsUnlocked(achievement.ID)) DrawAchievement(rowCount++, yOffset, achievement, false); @@ -240,52 +267,52 @@ static void DrawContentContainer() bool downIsHeld = inputState->GetPadState().IsDown(SWA::eKeyState_DpadDown) || inputState->GetPadState().LeftStickVertical < -0.5f; - bool scrollUp = !g_upWasHeld && upIsHeld; - bool scrollDown = !g_downWasHeld && downIsHeld; + bool scrollUp = !m_upWasHeld && upIsHeld; + bool scrollDown = !m_downWasHeld && downIsHeld; - int prevSelectedRowIndex = g_selectedRowIndex; + int prevSelectedRowIndex = m_selectedRowIndex; if (scrollUp) { - --g_selectedRowIndex; - if (g_selectedRowIndex < 0) - g_selectedRowIndex = rowCount - 1; + --m_selectedRowIndex; + if (m_selectedRowIndex < 0) + m_selectedRowIndex = rowCount - 1; } else if (scrollDown) { - ++g_selectedRowIndex; - if (g_selectedRowIndex >= rowCount) - g_selectedRowIndex = 0; + ++m_selectedRowIndex; + if (m_selectedRowIndex >= rowCount) + m_selectedRowIndex = 0; } if (scrollUp || scrollDown) { - g_rowSelectionTime = ImGui::GetTime(); - g_prevSelectedRowIndex = prevSelectedRowIndex; + m_rowSelectionTime = ImGui::GetTime(); + m_prevSelectedRowIndex = prevSelectedRowIndex; Game_PlaySound("sys_actstg_pausecursor"); } - g_upWasHeld = upIsHeld; - g_downWasHeld = downIsHeld; + m_upWasHeld = upIsHeld; + m_downWasHeld = downIsHeld; int visibleRowCount = int(floor((clipRectMax.y - clipRectMin.y) / itemHeight)); bool disableMoveAnimation = false; - if (g_firstVisibleRowIndex > g_selectedRowIndex) + if (m_firstVisibleRowIndex > m_selectedRowIndex) { - g_firstVisibleRowIndex = g_selectedRowIndex; + m_firstVisibleRowIndex = m_selectedRowIndex; disableMoveAnimation = true; } - if (g_firstVisibleRowIndex + visibleRowCount - 1 < g_selectedRowIndex) + if (m_firstVisibleRowIndex + visibleRowCount - 1 < m_selectedRowIndex) { - g_firstVisibleRowIndex = std::max(0, g_selectedRowIndex - visibleRowCount + 1); + m_firstVisibleRowIndex = std::max(0, m_selectedRowIndex - visibleRowCount + 1); disableMoveAnimation = true; } if (disableMoveAnimation) - g_prevSelectedRowIndex = g_selectedRowIndex; + m_prevSelectedRowIndex = m_selectedRowIndex; // Pop clip rect from DrawContentContainer drawList->PopClipRect(); @@ -296,7 +323,7 @@ static void DrawContentContainer() float cornerRadius = Scale(25.0f); float totalHeight = (clipRectMax.y - clipRectMin.y - cornerRadius) - Scale(3.0f); float heightRatio = float(visibleRowCount) / float(rowCount); - float offsetRatio = float(g_firstVisibleRowIndex) / float(rowCount); + float offsetRatio = float(m_firstVisibleRowIndex) / float(rowCount); float offsetX = clipRectMax.x - Scale(31.0f); float offsetY = offsetRatio * totalHeight + clipRectMin.y + Scale(4.0f); float lineThickness = Scale(1.0f); @@ -306,8 +333,8 @@ static void DrawContentContainer() // Outline drawList->AddRect ( - { offsetX - lineThickness, clipRectMin.y - lineThickness }, - { clipRectMax.x - outerMarginX + lineThickness, max.y - cornerRadius + lineThickness }, + { /* X */ offsetX - lineThickness, /* Y */ clipRectMin.y - lineThickness }, + { /* X */ clipRectMax.x - outerMarginX + lineThickness, /* Y */ max.y - cornerRadius + lineThickness }, IM_COL32(255, 255, 255, 155), Scale(0.5f) ); @@ -315,8 +342,8 @@ static void DrawContentContainer() // Background drawList->AddRectFilledMultiColor ( - { offsetX, clipRectMin.y }, - { clipRectMax.x - outerMarginX, max.y - cornerRadius }, + { /* X */ offsetX, /* Y */ clipRectMin.y }, + { /* X */ clipRectMax.x - outerMarginX, /* Y */ max.y - cornerRadius }, IM_COL32(82, 85, 82, 186), IM_COL32(82, 85, 82, 186), IM_COL32(74, 73, 74, 185), @@ -326,8 +353,8 @@ static void DrawContentContainer() // Scroll Bar Outline drawList->AddRectFilledMultiColor ( - { offsetX + innerMarginX, offsetY - lineThickness }, - { clipRectMax.x - outerMarginX - innerMarginX, offsetY + lineThickness + totalHeight * heightRatio}, + { /* X */ offsetX + innerMarginX, /* Y */ offsetY - lineThickness }, + { /* X */ clipRectMax.x - outerMarginX - innerMarginX, /* Y */ offsetY + lineThickness + totalHeight * heightRatio }, IM_COL32(185, 185, 185, 255), IM_COL32(185, 185, 185, 255), IM_COL32(172, 172, 172, 255), @@ -337,13 +364,25 @@ static void DrawContentContainer() // Scroll Bar drawList->AddRectFilled ( - { offsetX + innerMarginX + lineThickness, offsetY }, - { clipRectMax.x - outerMarginX - innerMarginX - lineThickness, offsetY + totalHeight * heightRatio }, + { /* X */ offsetX + innerMarginX + lineThickness, /* Y */ offsetY }, + { /* X */ clipRectMax.x - outerMarginX - innerMarginX - lineThickness, /* Y */ offsetY + totalHeight * heightRatio }, IM_COL32(255, 255, 255, 255) ); } } +void AchievementMenu::Init() +{ + auto& io = ImGui::GetIO(); + + constexpr float FONT_SCALE = 2.0f; + + m_fntSeurat = io.Fonts->AddFontFromFileTTF("FOT-SeuratPro-M.otf", 24.0f * FONT_SCALE); + m_fntNewRodin = io.Fonts->AddFontFromFileTTF("FOT-NewRodinPro-UB.otf", 20.0f * FONT_SCALE); + + m_achievements = g_xdbfWrapper.GetAchievements((EXDBFLanguage)Config::Language.Value); +} + void AchievementMenu::Draw() { if (!s_isVisible) diff --git a/UnleashedRecomp/ui/achievement_menu.h b/UnleashedRecomp/ui/achievement_menu.h index 3bbfcd05..db8d6d4b 100644 --- a/UnleashedRecomp/ui/achievement_menu.h +++ b/UnleashedRecomp/ui/achievement_menu.h @@ -1,14 +1,12 @@ #pragma once -#include "imgui_view.h" - -class AchievementMenu : ImGuiView +class AchievementMenu { public: inline static bool s_isVisible = false; - void Init() override; - void Draw() override; + static void Init(); + static void Draw(); static void Open(); static void Close(); }; diff --git a/UnleashedRecomp/ui/achievement_overlay.cpp b/UnleashedRecomp/ui/achievement_overlay.cpp index f48a3332..df9d22e0 100644 --- a/UnleashedRecomp/ui/achievement_overlay.cpp +++ b/UnleashedRecomp/ui/achievement_overlay.cpp @@ -1,57 +1,42 @@ #include "achievement_overlay.h" #include "imgui_utils.h" - -#include -#include #include #include +#include #include +#include +#include #include #include -AchievementOverlay m_achievementOverlay; +constexpr double OVERLAY_CONTAINER_COMMON_MOTION_START = 0; +constexpr double OVERLAY_CONTAINER_COMMON_MOTION_END = 11; +constexpr double OVERLAY_CONTAINER_INTRO_FADE_START = 5; +constexpr double OVERLAY_CONTAINER_INTRO_FADE_END = 9; +constexpr double OVERLAY_CONTAINER_OUTRO_FADE_START = 0; +constexpr double OVERLAY_CONTAINER_OUTRO_FADE_END = 5; -constexpr double OVERLAY_CONTAINER_MOTION_START = 0.0; -constexpr double OVERLAY_CONTAINER_MOTION_END = 8.0; -constexpr double OVERLAY_CONTAINER_FADE_IN_START = 5.0; -constexpr double OVERLAY_CONTAINER_FADE_OUT_START = 4.0; -constexpr double OVERLAY_ELEMENTS_FADE_START = 10.0; -constexpr double OVERLAY_ELEMENTS_FADE_END = 12.0; -constexpr double OVERLAY_DURATION = 5.0; +constexpr double OVERLAY_DURATION = 5; -static bool g_isClosing = false; +static bool m_isClosing = false; -static double g_appearTime = 0.0; +static double m_appearTime = 0; -static Achievement g_achievement; -static std::unique_ptr g_upAchievementIcon; +static Achievement m_achievement; -static ImFont* g_fntSeurat; +static ImFont* m_fntSeurat; -void AchievementOverlay::Init() -{ - auto& io = ImGui::GetIO(); - - constexpr float FONT_SCALE = 2.0f; - - g_fntSeurat = io.Fonts->AddFontFromFileTTF("FOT-SeuratPro-M.otf", 26.0f * FONT_SCALE); -} - -static double ComputeMotion(double frameOffset, double frames) -{ - double t = std::clamp((ImGui::GetTime() - g_appearTime - frameOffset / 60.0) / frames * 60.0, 0.0, 1.0); - return sqrt(t); -} - -static void DrawContainer(ImVec2 min, ImVec2 max, float cornerRadius = 25.0f) +static bool DrawContainer(ImVec2 min, ImVec2 max, float cornerRadius = 25) { auto drawList = ImGui::GetForegroundDrawList(); - auto containerMotion = ComputeMotion(OVERLAY_CONTAINER_MOTION_START, OVERLAY_CONTAINER_MOTION_END); - auto centreX = (min.x + max.x) / 2.0f; - auto centreY = (min.y + max.y) / 2.0f; + // Expand/retract animation. + auto containerMotion = ComputeMotion(m_appearTime, OVERLAY_CONTAINER_COMMON_MOTION_START, OVERLAY_CONTAINER_COMMON_MOTION_END); - if (g_isClosing) + auto centreX = (min.x + max.x) / 2; + auto centreY = (min.y + max.y) / 2; + + if (m_isClosing) { min.x = CubicEase(min.x, centreX, containerMotion); max.x = CubicEase(max.x, centreX, containerMotion); @@ -66,40 +51,47 @@ static void DrawContainer(ImVec2 min, ImVec2 max, float cornerRadius = 25.0f) max.y = CubicEase(centreY, max.y, containerMotion); } - ImVec2 v1 = { min.x, min.y + cornerRadius }; - ImVec2 v2 = { min.x + cornerRadius, min.y }; - ImVec2 v3 = { max.x, min.y }; - ImVec2 v4 = { max.x, min.y + cornerRadius }; - ImVec2 v5 = { max.x, max.y - cornerRadius }; - ImVec2 v6 = { max.x - cornerRadius, max.y }; - ImVec2 v7 = { min.x, max.y }; - ImVec2 v8 = { min.x, max.y - cornerRadius }; - ImVec2 vertices[] = { v1, v2, v3, v4, v5, v6, v7, v8 }; + auto vertices = GetPauseContainerVertices(min, max, cornerRadius); - auto colourMotion = ComputeMotion(g_isClosing ? OVERLAY_CONTAINER_MOTION_START : OVERLAY_CONTAINER_FADE_IN_START, - g_isClosing ? OVERLAY_CONTAINER_FADE_OUT_START : OVERLAY_CONTAINER_MOTION_END); + // Transparency fade animation. + auto colourMotion = m_isClosing + ? ComputeMotion(m_appearTime, OVERLAY_CONTAINER_OUTRO_FADE_START, OVERLAY_CONTAINER_OUTRO_FADE_END) + : ComputeMotion(m_appearTime, OVERLAY_CONTAINER_INTRO_FADE_START, OVERLAY_CONTAINER_INTRO_FADE_END); - auto colShadow = IM_COL32(0, 0, 0, (int)CubicEase(g_isClosing ? 156 : 0, g_isClosing ? 0 : 156, colourMotion)); - auto colGradientTop = IM_COL32(197, 194, 197, (int)CubicEase(g_isClosing ? 200 : 0, g_isClosing ? 0 : 200, colourMotion)); - auto colGradientBottom = IM_COL32(115, 113, 115, (int)CubicEase(g_isClosing ? 236 : 0, g_isClosing ? 0 : 236, colourMotion)); + auto alpha = m_isClosing + ? CubicEase(1, 0, colourMotion) + : CubicEase(0, 1, colourMotion); + + auto colShadow = IM_COL32(0, 0, 0, 156 * alpha); + auto colGradientTop = IM_COL32(197, 194, 197, 200 * alpha); + auto colGradientBottom = IM_COL32(115, 113, 115, 236 * alpha); // TODO: add a drop shadow. + // Draw vertices with gradient. SetGradient(min, max, colGradientTop, colGradientBottom); - drawList->AddConvexPolyFilled(vertices, IM_ARRAYSIZE(vertices), IM_COL32(255, 255, 255, 255)); + drawList->AddConvexPolyFilled(vertices.data(), vertices.size(), IM_COL32(255, 255, 255, 255 * alpha)); ResetGradient(); - drawList->AddPolyline(vertices, IM_ARRAYSIZE(vertices), IM_COL32(247, 247, 247, (int)CubicEase(g_isClosing ? 255 : 0, g_isClosing ? 0 : 255, colourMotion)), true, Scale(2.5f)); + // Draw outline. + drawList->AddPolyline + ( + vertices.data(), + vertices.size(), + IM_COL32(247, 247, 247, 255 * alpha), + true, + Scale(2.5f) + ); - for (int i = 0; i < IM_ARRAYSIZE(vertices); i++) + // Offset vertices to draw 3D effect lines. + for (int i = 0; i < vertices.size(); i++) { - vertices[i].x -= 0.4f; - vertices[i].y -= 0.2f; + vertices[i].x -= Scale(0.4f); + vertices[i].y -= Scale(0.2f); } - auto lineAlpha = (int)CubicEase(g_isClosing ? 230 : 0, g_isClosing ? 0 : 230, colourMotion); - auto colLineTop = IM_COL32(165, 170, 165, lineAlpha); - auto colLineBottom = IM_COL32(190, 190, 190, lineAlpha); + auto colLineTop = IM_COL32(165, 170, 165, 230 * alpha); + auto colLineBottom = IM_COL32(190, 190, 190, 230 * alpha); auto lineThickness = Scale(1.0f); // Top left corner bottom to top left corner top. @@ -112,76 +104,115 @@ static void DrawContainer(ImVec2 min, ImVec2 max, float cornerRadius = 25.0f) drawList->AddLine(vertices[1], vertices[2], colLineTop, lineThickness); drawList->PushClipRect(min, max); + + return containerMotion >= 1.0f; +} + +void AchievementOverlay::Init() +{ + auto& io = ImGui::GetIO(); + + constexpr float FONT_SCALE = 2.0f; + + m_fntSeurat = io.Fonts->AddFontFromFileTTF("FOT-SeuratPro-M.otf", 24.0f * FONT_SCALE); } void AchievementOverlay::Draw() { if (!s_isVisible) return; - - if (ImGui::GetTime() - g_appearTime >= OVERLAY_DURATION) + + if (ImGui::GetTime() - m_appearTime >= OVERLAY_DURATION) AchievementOverlay::Close(); auto drawList = ImGui::GetForegroundDrawList(); auto& res = ImGui::GetIO().DisplaySize; auto strAchievementUnlocked = Localise("Achievements_Unlock").c_str(); - auto strAchievementName = g_achievement.Name.c_str(); + auto strAchievementName = m_achievement.Name.c_str(); - auto fontSize = 30.0f; - auto headerTextSize = g_fntSeurat->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, strAchievementUnlocked); - auto bodyTextSize = g_fntSeurat->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, strAchievementName); - auto longestTextSize = std::max(headerTextSize.x, bodyTextSize.x); + // Calculate text sizes. + auto fontSize = Scale(24); + auto headerSize = m_fntSeurat->CalcTextSizeA(fontSize, FLT_MAX, 0, strAchievementUnlocked); + auto bodySize = m_fntSeurat->CalcTextSizeA(fontSize, FLT_MAX, 0, strAchievementName); + auto maxSize = std::max(headerSize.x, bodySize.x); - constexpr auto imageX = 25.0f; - constexpr auto imageY = 20.0f; - constexpr auto imageSize = 90.0f; - constexpr auto textX = 140.0f; + // Calculate image margins. + auto imageMarginX = Scale(20); + auto imageMarginY = Scale(20); + auto imageSize = Scale(60); - auto width = textX + longestTextSize + 30.0f; + // Calculate text margins. + auto textMarginX = imageMarginX * 2 + imageSize; + auto textMarginY = imageMarginY + Scale(2); - ImVec2 min = { (res.x / 2.0f) - (width / 2.0f), 50.0f }; - ImVec2 max = { min.x + width, min.y + 125.0f }; + auto containerWidth = imageMarginX + textMarginX + maxSize; - DrawContainer(min, max); + ImVec2 min = { (res.x / 2) - (containerWidth / 2), Scale(50) }; + ImVec2 max = { min.x + containerWidth, min.y + Scale(100) }; - auto colourMotion = ComputeMotion(g_isClosing ? OVERLAY_CONTAINER_MOTION_START : OVERLAY_ELEMENTS_FADE_START, - g_isClosing ? OVERLAY_CONTAINER_FADE_OUT_START : OVERLAY_ELEMENTS_FADE_END); + if (DrawContainer(min, max)) + { + // Draw achievement icon. + drawList->AddImage + ( + g_xdbfTextureCache[m_achievement.ID], // user_texture_id + { /* X */ min.x + imageMarginX, /* Y */ min.y + imageMarginY }, // p_min + { /* X */ min.x + imageMarginX + imageSize, /* Y */ min.y + imageMarginY + imageSize }, // p_max + { 0, 0 }, // uv_min + { 1, 1 }, // uv_max + IM_COL32(255, 255, 255, 255) // col + ); - auto alpha = (int)CubicEase(g_isClosing ? 255 : 0, g_isClosing ? 0 : 255, colourMotion); + // Draw header text. + DrawTextWithShadow + ( + m_fntSeurat, // font + fontSize, // fontSize + { /* X */ min.x + textMarginX + (maxSize - headerSize.x) / 2, /* Y */ min.y + textMarginY }, // pos + IM_COL32(252, 243, 5, 255), // colour + strAchievementUnlocked, // text + 2, // offset + 0.4f, // radius + IM_COL32(0, 0, 0, 255) // shadowColour + ); - drawList->AddImage(g_upAchievementIcon.get(), { min.x + imageX, min.y + imageY }, { min.x + imageX + imageSize, min.y + imageY + imageSize }, { 0, 0 }, { 1, 1 }, IM_COL32(255, 255, 255, alpha)); + // Draw achievement name. + DrawTextWithShadow + ( + m_fntSeurat, // font + fontSize, // fontSize + { /* X */ min.x + textMarginX + (maxSize - bodySize.x) / 2, /* Y */ min.y + textMarginY + bodySize.y + Scale(6) }, // pos + IM_COL32(255, 255, 255, 255), // colour + strAchievementName, // text + 2, // offset + 0.4f, // radius + IM_COL32(0, 0, 0, 255) // shadowColour + ); - auto cmnShadowOffset = Scale(2.0f); - auto cmnShadowScale = Scale(0.4f); - - DrawTextWithShadow(g_fntSeurat, fontSize, { min.x + textX + (longestTextSize - headerTextSize.x) / 2.0f, min.y + 30.0f}, IM_COL32(252, 243, 5, alpha), strAchievementUnlocked, cmnShadowOffset, cmnShadowScale, IM_COL32(0, 0, 0, alpha)); - DrawTextWithShadow(g_fntSeurat, fontSize, { min.x + textX + (longestTextSize - bodyTextSize.x) / 2.0f, min.y + 68.0f}, IM_COL32(255, 255, 255, alpha), strAchievementName, cmnShadowOffset, cmnShadowScale, IM_COL32(0, 0, 0, alpha)); - - // Pop clip rect from DrawContainer - drawList->PopClipRect(); + // Pop clip rect from DrawContainer. + drawList->PopClipRect(); + } } void AchievementOverlay::Open(int id) { s_isVisible = true; - g_isClosing = false; - g_appearTime = ImGui::GetTime(); - - g_achievement = g_xdbf.GetAchievement((EXDBFLanguage)Config::Language.Value, id); - g_upAchievementIcon = LoadTexture((uint8_t*)g_achievement.pImageBuffer, g_achievement.ImageBufferSize); + m_isClosing = false; + m_appearTime = ImGui::GetTime(); + m_achievement = g_xdbfWrapper.GetAchievement((EXDBFLanguage)Config::Language.Value, id); Game_PlaySound("obj_navi_appear"); } void AchievementOverlay::Close() { - if (!g_isClosing) + if (!m_isClosing) { - g_appearTime = ImGui::GetTime(); - g_isClosing = true; + m_appearTime = ImGui::GetTime(); + m_isClosing = true; } - if (ImGui::GetTime() - g_appearTime >= OVERLAY_ELEMENTS_FADE_END) + if (ImGui::GetTime() - m_appearTime >= OVERLAY_CONTAINER_COMMON_MOTION_END) s_isVisible = false; } diff --git a/UnleashedRecomp/ui/achievement_overlay.h b/UnleashedRecomp/ui/achievement_overlay.h index 02d80011..697c0bcb 100644 --- a/UnleashedRecomp/ui/achievement_overlay.h +++ b/UnleashedRecomp/ui/achievement_overlay.h @@ -1,14 +1,12 @@ #pragma once -#include "imgui_view.h" - -class AchievementOverlay : ImGuiView +class AchievementOverlay { public: inline static bool s_isVisible = false; - void Init() override; - void Draw() override; + static void Init(); + static void Draw(); static void Open(int id); static void Close(); }; diff --git a/UnleashedRecomp/ui/imgui_utils.h b/UnleashedRecomp/ui/imgui_utils.h index 974166db..4e96f902 100644 --- a/UnleashedRecomp/ui/imgui_utils.h +++ b/UnleashedRecomp/ui/imgui_utils.h @@ -1,6 +1,7 @@ #pragma once #include +#include static std::vector> g_callbackData; static uint32_t g_callbackDataIndex = 0; @@ -66,11 +67,33 @@ static float ScaleY(float y) return y * io.DisplaySize.y / 720.0f; } +static double ComputeMotion(double duration, double offset, double total) +{ + return sqrt(std::clamp((ImGui::GetTime() - duration - offset / 60.0) / total * 60.0, 0.0, 1.0)); +} + +static std::vector GetPauseContainerVertices(ImVec2 min, ImVec2 max, float cornerRadius = 25) +{ + cornerRadius = Scale(cornerRadius); + + return + { + { min.x, min.y + cornerRadius }, + { min.x + cornerRadius, min.y }, + { max.x, min.y }, + { max.x, min.y + cornerRadius }, + { max.x, max.y - cornerRadius }, + { max.x - cornerRadius, max.y }, + { min.x, max.y }, + { min.x, max.y - cornerRadius } + }; +} + static void DrawTextWithMarquee(const ImFont* font, float fontSize, const ImVec2& pos, const ImVec2& min, const ImVec2& max, ImU32 color, const char* text, double time, double delay, double speed) { auto drawList = ImGui::GetForegroundDrawList(); auto rectWidth = max.x - min.x; - auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, text); + auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, text); auto textX = pos.x - fmodf(std::max(0.0, ImGui::GetTime() - (time + delay)) * speed, textSize.x + rectWidth); drawList->PushClipRect(min, max, true); @@ -89,6 +112,8 @@ static void DrawTextWithOutline(const ImFont* font, float fontSize, const ImVec2 { auto drawList = ImGui::GetForegroundDrawList(); + outlineSize = Scale(outlineSize); + if constexpr (std::is_same_v || std::is_same_v) { // TODO: This is still very inefficient! @@ -120,7 +145,11 @@ static void DrawTextWithShadow(const ImFont* font, float fontSize, const ImVec2& { auto drawList = ImGui::GetForegroundDrawList(); + offset = Scale(offset); + radius = Scale(radius); + DrawTextWithOutline(font, fontSize, { pos.x + offset, pos.y + offset }, shadowColour, text, radius, shadowColour); + drawList->AddText(font, fontSize, pos, colour, text); } @@ -128,7 +157,7 @@ static void DrawTextWithMarqueeShadow(const ImFont* font, float fontSize, const { auto drawList = ImGui::GetForegroundDrawList(); auto rectWidth = max.x - min.x; - auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, text); + auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, text); auto textX = pos.x - fmodf(std::max(0.0, ImGui::GetTime() - (time + delay)) * speed, textSize.x + rectWidth); drawList->PushClipRect(min, max, true); diff --git a/UnleashedRecomp/ui/imgui_view.cpp b/UnleashedRecomp/ui/imgui_view.cpp deleted file mode 100644 index 02c45831..00000000 --- a/UnleashedRecomp/ui/imgui_view.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "imgui_view.h" - -std::vector& GetImGuiViews() -{ - static std::vector g_imGuiViews; - return g_imGuiViews; -} diff --git a/UnleashedRecomp/ui/imgui_view.h b/UnleashedRecomp/ui/imgui_view.h deleted file mode 100644 index d6441291..00000000 --- a/UnleashedRecomp/ui/imgui_view.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -class IImGuiView -{ -public: - virtual ~IImGuiView() = default; - virtual void Init() = 0; - virtual void Draw() = 0; -}; - -std::vector& GetImGuiViews(); - -class ImGuiView : public IImGuiView -{ -public: - ImGuiView() - { - GetImGuiViews().emplace_back(this); - } - - void Init() override {} - void Draw() override {} -}; diff --git a/UnleashedRecomp/ui/options_menu.cpp b/UnleashedRecomp/ui/options_menu.cpp index cde57d3e..ac493865 100644 --- a/UnleashedRecomp/ui/options_menu.cpp +++ b/UnleashedRecomp/ui/options_menu.cpp @@ -12,34 +12,59 @@ #include -OptionsMenu m_optionsMenu; +static constexpr double CONTAINER_LINE_ANIMATION_DURATION = 8.0; + +static constexpr double CONTAINER_OUTER_TIME = CONTAINER_LINE_ANIMATION_DURATION + 8.0; // 8 frame delay +static constexpr double CONTAINER_OUTER_DURATION = 8.0; + +static constexpr double CONTAINER_INNER_TIME = CONTAINER_OUTER_TIME + CONTAINER_OUTER_DURATION + 8.0; // 8 frame delay +static constexpr double CONTAINER_INNER_DURATION = 8.0; + +static constexpr double CONTAINER_BACKGROUND_TIME = CONTAINER_INNER_TIME + CONTAINER_INNER_DURATION + 8.0; // 8 frame delay +static constexpr double CONTAINER_BACKGROUND_DURATION = 12.0; + +static constexpr double CONTAINER_FULL_DURATION = CONTAINER_BACKGROUND_TIME + CONTAINER_BACKGROUND_DURATION; + +static constexpr double CONTAINER_CATEGORY_TIME = (CONTAINER_INNER_TIME + CONTAINER_BACKGROUND_TIME) / 2.0; +static constexpr double CONTAINER_CATEGORY_DURATION = 12.0; constexpr float COMMON_PADDING_POS_Y = 118.0f; constexpr float COMMON_PADDING_POS_X = 30.0f; constexpr float INFO_CONTAINER_POS_X = 870.0f; -static ImFont* g_seuratFont; -static ImFont* g_dfsogeistdFont; -static ImFont* g_newRodinFont; +static constexpr int32_t m_categoryCount = 4; +static int32_t m_categoryIndex; +static ImVec2 m_categoryAnimMin; +static ImVec2 m_categoryAnimMax; -static const IConfigDef* g_selectedItem; +static int32_t m_firstVisibleRowIndex; +static int32_t m_prevSelectedRowIndex; +static int32_t m_selectedRowIndex; +static double m_rowSelectionTime; -static std::string* g_inaccessibleReason; +static bool m_leftWasHeld; +static bool m_upWasHeld; +static bool m_rightWasHeld; +static bool m_downWasHeld; -static bool g_isEnterKeyBuffered = false; +static bool m_lockedOnOption; +static double m_lastTappedTime; +static double m_lastIncrementTime; +static double m_lastIncrementSoundTime; -static double g_appearTime = 0.0; +static constexpr size_t GRID_SIZE = 9; -void OptionsMenu::Init() -{ - auto& io = ImGui::GetIO(); +static ImFont* m_seuratFont; +static ImFont* m_dfsogeistdFont; +static ImFont* m_newRodinFont; - constexpr float FONT_SCALE = 2.0f; +static const IConfigDef* m_selectedItem; - g_seuratFont = io.Fonts->AddFontFromFileTTF("FOT-SeuratPro-M.otf", 26.0f * FONT_SCALE); - g_dfsogeistdFont = io.Fonts->AddFontFromFileTTF("DFSoGeiStd-W7.otf", 48.0f * FONT_SCALE); - g_newRodinFont = io.Fonts->AddFontFromFileTTF("FOT-NewRodinPro-DB.otf", 20.0f * FONT_SCALE); -} +static std::string* m_inaccessibleReason; + +static bool m_isEnterKeyBuffered = false; + +static double m_appearTime = 0.0; static void DrawScanlineBars() { @@ -57,95 +82,85 @@ static void DrawScanlineBars() if (OptionsMenu::s_pauseMenuType != SWA::eMenuType_WorldMap) { // Top bar fade - drawList->AddRectFilledMultiColor( + drawList->AddRectFilledMultiColor + ( { 0.0f, 0.0f }, { res.x, height }, FADE_COLOR0, FADE_COLOR0, FADE_COLOR1, - FADE_COLOR1); + FADE_COLOR1 + ); // Bottom bar fade - drawList->AddRectFilledMultiColor( + drawList->AddRectFilledMultiColor + ( { res.x, res.y }, { 0.0f, res.y - height }, FADE_COLOR0, FADE_COLOR0, FADE_COLOR1, - FADE_COLOR1); + FADE_COLOR1 + ); } SetShaderModifier(IMGUI_SHADER_MODIFIER_SCANLINE); // Top bar - drawList->AddRectFilledMultiColor( + drawList->AddRectFilledMultiColor + ( { 0.0f, 0.0f }, { res.x, height }, COLOR0, COLOR0, COLOR1, - COLOR1); + COLOR1 + ); // Bottom bar - drawList->AddRectFilledMultiColor( + drawList->AddRectFilledMultiColor + ( { res.x, res.y }, { 0.0f, res.y - height }, COLOR0, COLOR0, COLOR1, - COLOR1); + COLOR1 + ); SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); // Options text // TODO: localise this. - DrawTextWithOutline(g_dfsogeistdFont, Scale(48.0f), { Scale(122.0f), Scale(56.0f) }, IM_COL32(255, 195, 0, 255), "OPTIONS", Scale(4), IM_COL32_BLACK); + DrawTextWithOutline(m_dfsogeistdFont, Scale(48.0f), { Scale(122.0f), Scale(56.0f) }, IM_COL32(255, 195, 0, 255), "OPTIONS", 4, IM_COL32_BLACK); // Top bar line - drawList->AddLine( + drawList->AddLine + ( { 0.0f, height }, { res.x, height }, OUTLINE_COLOR, - Scale(1)); + Scale(1) + ); // Bottom bar line - drawList->AddLine( + drawList->AddLine + ( { 0.0f, res.y - height }, { res.x, res.y - height }, OUTLINE_COLOR, - Scale(1)); + Scale(1) + ); } -static constexpr size_t GRID_SIZE = 9; - static float AlignToNextGrid(float value) { return floor(value / GRID_SIZE) * GRID_SIZE; } -static double ComputeMotion(double frameOffset, double frames) -{ - double t = std::clamp((ImGui::GetTime() - g_appearTime - frameOffset / 60.0) / frames * 60.0, 0.0, 1.0); - return sqrt(t); -} - -// all in 60 FPS -static constexpr double CONTAINER_LINE_ANIMATION_DURATION = 8.0; - -static constexpr double CONTAINER_OUTER_TIME = CONTAINER_LINE_ANIMATION_DURATION + 8.0; // 8 frame delay -static constexpr double CONTAINER_OUTER_DURATION = 8.0; - -static constexpr double CONTAINER_INNER_TIME = CONTAINER_OUTER_TIME + CONTAINER_OUTER_DURATION + 8.0; // 8 frame delay -static constexpr double CONTAINER_INNER_DURATION = 8.0; - -static constexpr double CONTAINER_BACKGROUND_TIME = CONTAINER_INNER_TIME + CONTAINER_INNER_DURATION + 8.0; // 8 frame delay -static constexpr double CONTAINER_BACKGROUND_DURATION = 12.0; - -static constexpr double CONTAINER_FULL_DURATION = CONTAINER_BACKGROUND_TIME + CONTAINER_BACKGROUND_DURATION; - static void DrawContainer(ImVec2 min, ImVec2 max) { - double containerHeight = ComputeMotion(0.0, CONTAINER_LINE_ANIMATION_DURATION); + double containerHeight = ComputeMotion(m_appearTime, 0.0, CONTAINER_LINE_ANIMATION_DURATION); float center = (min.y + max.y) / 2.0f; min.y = Lerp(center, min.y, containerHeight); @@ -154,9 +169,9 @@ static void DrawContainer(ImVec2 min, ImVec2 max) auto& res = ImGui::GetIO().DisplaySize; auto drawList = ImGui::GetForegroundDrawList(); - double outerAlpha = ComputeMotion(CONTAINER_OUTER_TIME, CONTAINER_OUTER_DURATION); - double innerAlpha = ComputeMotion(CONTAINER_INNER_TIME, CONTAINER_INNER_DURATION); - double backgroundAlpha = ComputeMotion(CONTAINER_BACKGROUND_TIME, CONTAINER_BACKGROUND_DURATION); + double outerAlpha = ComputeMotion(m_appearTime, CONTAINER_OUTER_TIME, CONTAINER_OUTER_DURATION); + double innerAlpha = ComputeMotion(m_appearTime, CONTAINER_INNER_TIME, CONTAINER_INNER_DURATION); + double backgroundAlpha = ComputeMotion(m_appearTime, CONTAINER_BACKGROUND_TIME, CONTAINER_BACKGROUND_DURATION); const uint32_t lineColor = IM_COL32(0, 89, 0, 255 * containerHeight); const uint32_t outerColor = IM_COL32(0, 49, 0, 255 * outerAlpha); @@ -194,11 +209,6 @@ static void DrawContainer(ImVec2 min, ImVec2 max) drawList->PushClipRect({ min.x + gridSize * 2.0f, min.y + gridSize * 2.0f }, { max.x - gridSize * 2.0f + 1.0f, max.y - gridSize * 2.0f + 1.0f }); } -static constexpr int32_t g_categoryCount = 4; -static int32_t g_categoryIndex; -static ImVec2 g_categoryAnimMin; -static ImVec2 g_categoryAnimMax; - static std::string& GetCategory(int index) { // TODO: Don't use raw numbers here! @@ -213,61 +223,44 @@ static std::string& GetCategory(int index) return g_localeMissing; } -static int32_t g_firstVisibleRowIndex; -static int32_t g_prevSelectedRowIndex; -static int32_t g_selectedRowIndex; -static double g_rowSelectionTime; - -static bool g_leftWasHeld; -static bool g_upWasHeld; -static bool g_rightWasHeld; -static bool g_downWasHeld; - -static bool g_lockedOnOption; -static double g_lastTappedTime; -static double g_lastIncrementTime; -static double g_lastIncrementSoundTime; - static void ResetSelection() { - g_firstVisibleRowIndex = 0; - g_selectedRowIndex = 0; - g_prevSelectedRowIndex = 0; - g_rowSelectionTime = ImGui::GetTime(); - g_leftWasHeld = false; - g_upWasHeld = false; - g_rightWasHeld = false; - g_downWasHeld = false; + m_firstVisibleRowIndex = 0; + m_selectedRowIndex = 0; + m_prevSelectedRowIndex = 0; + m_rowSelectionTime = ImGui::GetTime(); + m_leftWasHeld = false; + m_upWasHeld = false; + m_rightWasHeld = false; + m_downWasHeld = false; } -static constexpr double CONTAINER_CATEGORY_TIME = (CONTAINER_INNER_TIME + CONTAINER_BACKGROUND_TIME) / 2.0; -static constexpr double CONTAINER_CATEGORY_DURATION = 12.0; - static bool DrawCategories() { - double motion = ComputeMotion(CONTAINER_CATEGORY_TIME, CONTAINER_CATEGORY_DURATION); + double motion = ComputeMotion(m_appearTime, CONTAINER_CATEGORY_TIME, CONTAINER_CATEGORY_DURATION); + if (motion == 0.0) return false; auto inputState = SWA::CInputState::GetInstance(); - bool moveLeft = !g_lockedOnOption && (inputState->GetPadState().IsTapped(SWA::eKeyState_LeftBumper) || + bool moveLeft = !m_lockedOnOption && (inputState->GetPadState().IsTapped(SWA::eKeyState_LeftBumper) || inputState->GetPadState().IsTapped(SWA::eKeyState_LeftTrigger)); - bool moveRight = !g_lockedOnOption && (inputState->GetPadState().IsTapped(SWA::eKeyState_RightBumper) || + bool moveRight = !m_lockedOnOption && (inputState->GetPadState().IsTapped(SWA::eKeyState_RightBumper) || inputState->GetPadState().IsTapped(SWA::eKeyState_RightTrigger)); if (moveLeft) { - --g_categoryIndex; - if (g_categoryIndex < 0) - g_categoryIndex = g_categoryCount - 1; + --m_categoryIndex; + if (m_categoryIndex < 0) + m_categoryIndex = m_categoryCount - 1; } else if (moveRight) { - ++g_categoryIndex; - if (g_categoryIndex >= g_categoryCount) - g_categoryIndex = 0; + ++m_categoryIndex; + if (m_categoryIndex >= m_categoryCount) + m_categoryIndex = 0; } if (moveLeft || moveRight) @@ -285,22 +278,22 @@ static bool DrawCategories() float tabPadding = gridSize; float size = Scale(32.0f); - ImVec2 textSizes[g_categoryCount]; + ImVec2 textSizes[m_categoryCount]; float tabWidthSum = 0.0f; - for (size_t i = 0; i < g_categoryCount; i++) + for (size_t i = 0; i < m_categoryCount; i++) { - textSizes[i] = g_dfsogeistdFont->CalcTextSizeA(size, FLT_MAX, 0.0f, GetCategory(i).c_str()); + textSizes[i] = m_dfsogeistdFont->CalcTextSizeA(size, FLT_MAX, 0.0f, GetCategory(i).c_str()); tabWidthSum += textSizes[i].x + textPadding * 2.0f; } - tabWidthSum += (g_categoryCount - 1) * tabPadding; + tabWidthSum += (m_categoryCount - 1) * tabPadding; float tabHeight = gridSize * 4.0f; float xOffset = ((clipRectMax.x - clipRectMin.x) - tabWidthSum) / 2.0f; xOffset -= (1.0 - motion) * gridSize * 4.0; - ImVec2 minVec[g_categoryCount]; + ImVec2 minVec[m_categoryCount]; - for (size_t i = 0; i < g_categoryCount; i++) + for (size_t i = 0; i < m_categoryCount; i++) { ImVec2 min = { clipRectMin.x + xOffset, clipRectMin.y }; @@ -308,58 +301,64 @@ static bool DrawCategories() ImVec2 max = { clipRectMin.x + xOffset, clipRectMin.y + tabHeight }; xOffset += tabPadding; - if (g_categoryIndex == i) + if (m_categoryIndex == i) { // Animation interrupted by entering/exiting or resizing the options menu - if (motion < 1.0 || abs(g_categoryAnimMin.y - min.y) > 0.01f || abs(g_categoryAnimMax.y - max.y) > 0.01f) + if (motion < 1.0 || abs(m_categoryAnimMin.y - min.y) > 0.01f || abs(m_categoryAnimMax.y - max.y) > 0.01f) { - g_categoryAnimMin = min; - g_categoryAnimMax = max; + m_categoryAnimMin = min; + m_categoryAnimMax = max; } else { - float animWidth = g_categoryAnimMax.x - g_categoryAnimMin.x; + float animWidth = m_categoryAnimMax.x - m_categoryAnimMin.x; float width = max.x - min.x; float height = max.y - min.y; animWidth = Lerp(animWidth, width, 1.0f - exp(-64.0f * ImGui::GetIO().DeltaTime)); auto center = Lerp(min, max, 0.5f); - auto animCenter = Lerp(g_categoryAnimMin, g_categoryAnimMax, 0.5f); + auto animCenter = Lerp(m_categoryAnimMin, m_categoryAnimMax, 0.5f); auto animatedCenter = Lerp(animCenter, center, 1.0f - exp(-16.0f * ImGui::GetIO().DeltaTime)); float widthHalfExtent = width / 2.0f; float heightHalfExtent = height / 2.0f; - g_categoryAnimMin = { animatedCenter.x - widthHalfExtent, animatedCenter.y - heightHalfExtent }; - g_categoryAnimMax = { animatedCenter.x + widthHalfExtent, animatedCenter.y + heightHalfExtent }; + m_categoryAnimMin = { animatedCenter.x - widthHalfExtent, animatedCenter.y - heightHalfExtent }; + m_categoryAnimMax = { animatedCenter.x + widthHalfExtent, animatedCenter.y + heightHalfExtent }; } SetShaderModifier(IMGUI_SHADER_MODIFIER_SCANLINE_BUTTON); - drawList->AddRectFilledMultiColor( - g_categoryAnimMin, - g_categoryAnimMax, + drawList->AddRectFilledMultiColor + ( + m_categoryAnimMin, + m_categoryAnimMax, IM_COL32(0, 130, 0, 223 * motion), IM_COL32(0, 130, 0, 178 * motion), IM_COL32(0, 130, 0, 223 * motion), - IM_COL32(0, 130, 0, 178 * motion)); + IM_COL32(0, 130, 0, 178 * motion) + ); - drawList->AddRectFilledMultiColor( - g_categoryAnimMin, - g_categoryAnimMax, + drawList->AddRectFilledMultiColor + ( + m_categoryAnimMin, + m_categoryAnimMax, IM_COL32(0, 0, 0, 13 * motion), IM_COL32(0, 0, 0, 0), IM_COL32(0, 0, 0, 55 * motion), - IM_COL32(0, 0, 0, 6)); + IM_COL32(0, 0, 0, 6) + ); - drawList->AddRectFilledMultiColor( - g_categoryAnimMin, - g_categoryAnimMax, + drawList->AddRectFilledMultiColor + ( + m_categoryAnimMin, + m_categoryAnimMax, IM_COL32(0, 130, 0, 13 * motion), IM_COL32(0, 130, 0, 111 * motion), IM_COL32(0, 130, 0, 0), - IM_COL32(0, 130, 0, 55 * motion)); + IM_COL32(0, 130, 0, 55 * motion) + ); SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); } @@ -370,30 +369,34 @@ static bool DrawCategories() minVec[i] = min; } - for (size_t i = 0; i < g_categoryCount; i++) + for (size_t i = 0; i < m_categoryCount; i++) { auto& min = minVec[i]; - uint8_t alpha = (i == g_categoryIndex ? 235 : 128) * motion; + uint8_t alpha = (i == m_categoryIndex ? 235 : 128) * motion; - SetGradient( + SetGradient + ( min, { min.x + textSizes[i].x, min.y + textSizes[i].y }, IM_COL32(128, 255, 0, alpha), - IM_COL32(255, 192, 0, alpha)); + IM_COL32(255, 192, 0, alpha) + ); - DrawTextWithOutline( - g_dfsogeistdFont, + DrawTextWithOutline + ( + m_dfsogeistdFont, size, min, IM_COL32_WHITE, GetCategory(i).c_str(), - Scale(3), - IM_COL32_BLACK); + 3, + IM_COL32_BLACK + ); ResetGradient(); } - if ((ImGui::GetTime() - g_appearTime) >= (CONTAINER_FULL_DURATION / 60.0)) + if ((ImGui::GetTime() - m_appearTime) >= (CONTAINER_FULL_DURATION / 60.0)) { drawList->PushClipRect({ clipRectMin.x, clipRectMin.y + gridSize * 6.0f }, { clipRectMax.x - gridSize, clipRectMax.y - gridSize }); return true; @@ -427,18 +430,18 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf auto configName = config->GetNameLocalised(); auto size = Scale(26.0f); - auto textSize = g_seuratFont->CalcTextSizeA(size, FLT_MAX, 0.0f, configName.c_str()); + auto textSize = m_seuratFont->CalcTextSizeA(size, FLT_MAX, 0.0f, configName.c_str()); ImVec2 textPos = { min.x + gridSize, min.y + (optionHeight - textSize.y) / 2.0f }; ImVec4 textClipRect = { min.x, min.y, max.x, max.y }; bool lockedOnOption = false; - if (g_selectedRowIndex == rowIndex) + if (m_selectedRowIndex == rowIndex) { - g_selectedItem = config; - g_inaccessibleReason = isAccessible ? nullptr : inaccessibleReason; + m_selectedItem = config; + m_inaccessibleReason = isAccessible ? nullptr : inaccessibleReason; - if (!g_isEnterKeyBuffered) + if (!m_isEnterKeyBuffered) { if (isAccessible) { @@ -462,12 +465,12 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf if (padState.IsTapped(SWA::eKeyState_A)) { - g_lockedOnOption ^= true; + m_lockedOnOption ^= true; - if (g_lockedOnOption) + if (m_lockedOnOption) { - g_leftWasHeld = false; - g_rightWasHeld = false; + m_leftWasHeld = false; + m_rightWasHeld = false; // remember value s_oldValue = config->Value; @@ -486,12 +489,12 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf { // released lock, restore old value config->Value = s_oldValue; - g_lockedOnOption = false; + m_lockedOnOption = false; Game_PlaySound("sys_worldmap_cansel"); } - lockedOnOption = g_lockedOnOption; + lockedOnOption = m_lockedOnOption; } } else @@ -502,14 +505,14 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf } } - auto fadedOut = (g_lockedOnOption && g_selectedItem != config) || !isAccessible; + auto fadedOut = (m_lockedOnOption && m_selectedItem != config) || !isAccessible; auto alpha = fadedOut ? 0.5f : 1.0f; auto textColour = IM_COL32(255, 255, 255, 255 * alpha); - if (g_selectedItem == config) + if (m_selectedItem == config) { - float prevItemOffset = (g_prevSelectedRowIndex - g_selectedRowIndex) * (optionHeight + optionPadding); - double animRatio = std::clamp((ImGui::GetTime() - g_rowSelectionTime) * 60.0 / 8.0, 0.0, 1.0); + float prevItemOffset = (m_prevSelectedRowIndex - m_selectedRowIndex) * (optionHeight + optionPadding); + double animRatio = std::clamp((ImGui::GetTime() - m_rowSelectionTime) * 60.0 / 8.0, 0.0, 1.0); prevItemOffset *= pow(1.0 - animRatio, 3.0); auto c0 = IM_COL32(0xE2, 0x71, 0x22, isAccessible ? 0x80 : 0x30); @@ -517,11 +520,11 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf drawList->AddRectFilledMultiColor({ min.x, min.y + prevItemOffset }, { max.x, max.y + prevItemOffset }, c0, c0, c1, c1); - DrawTextWithMarquee(g_seuratFont, size, textPos, min, max, textColour, configName.c_str(), g_rowSelectionTime, 0.9, 250.0); + DrawTextWithMarquee(m_seuratFont, size, textPos, min, max, textColour, configName.c_str(), m_rowSelectionTime, 0.9, 250.0); } else { - drawList->AddText(g_seuratFont, size, textPos, textColour, configName.c_str(), 0, 0.0f, &textClipRect); + drawList->AddText(m_seuratFont, size, textPos, textColour, configName.c_str(), 0, 0.0f, &textClipRect); } // Right side @@ -543,13 +546,15 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf float xPadding = Scale(6.0f); float yPadding = Scale(3.0f); - drawList->AddRectFilledMultiColor( + drawList->AddRectFilledMultiColor + ( { min.x + xPadding, min.y + yPadding }, { max.x - xPadding, max.y - yPadding }, innerColor0, innerColor0, innerColor1, - innerColor1); + innerColor1 + ); // The actual slider const uint32_t sliderColor0 = IM_COL32(57, 241, 0, 255 * alpha); @@ -580,34 +585,38 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf constexpr uint32_t COLOR = IM_COL32(0, 97, 0, 255); // Left - drawList->AddTriangleFilled( + drawList->AddTriangleFilled + ( { min.x - trianglePadding, min.y }, { min.x - trianglePadding, max.y }, { min.x - trianglePadding - triangleWidth, (min.y + max.y) / 2.0f }, - COLOR); + COLOR + ); // Right - drawList->AddTriangleFilled( + drawList->AddTriangleFilled + ( { max.x + trianglePadding, max.y }, { max.x + trianglePadding, min.y }, { max.x + trianglePadding + triangleWidth, (min.y + max.y) / 2.0f }, - COLOR); + COLOR + ); bool leftIsHeld = padState.IsDown(SWA::eKeyState_DpadLeft) || padState.LeftStickHorizontal < -0.5f; bool rightIsHeld = padState.IsDown(SWA::eKeyState_DpadRight) || padState.LeftStickHorizontal > 0.5f; - bool leftTapped = !g_leftWasHeld && leftIsHeld; - bool rightTapped = !g_rightWasHeld && rightIsHeld; + bool leftTapped = !m_leftWasHeld && leftIsHeld; + bool rightTapped = !m_rightWasHeld && rightIsHeld; double time = ImGui::GetTime(); if (leftTapped || rightTapped) - g_lastTappedTime = time; + m_lastTappedTime = time; bool decrement = leftTapped; bool increment = rightTapped; - bool fastIncrement = (time - g_lastTappedTime) > 0.5; + bool fastIncrement = (time - m_lastTappedTime) > 0.5; constexpr double INCREMENT_TIME = 1.0 / 120.0; constexpr double INCREMENT_SOUND_TIME = 1.0 / 7.5; @@ -615,12 +624,12 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf if (fastIncrement) { - isPlayIncrementSound = (time - g_lastIncrementSoundTime) > INCREMENT_SOUND_TIME; + isPlayIncrementSound = (time - m_lastIncrementSoundTime) > INCREMENT_SOUND_TIME; - if ((time - g_lastIncrementTime) < INCREMENT_TIME) + if ((time - m_lastIncrementTime) < INCREMENT_TIME) fastIncrement = false; else - g_lastIncrementTime = time; + m_lastIncrementTime = time; } if (fastIncrement) @@ -629,8 +638,8 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf increment = rightIsHeld; } - g_leftWasHeld = leftIsHeld; - g_rightWasHeld = rightIsHeld; + m_leftWasHeld = leftIsHeld; + m_rightWasHeld = rightIsHeld; if constexpr (std::is_enum_v) { @@ -678,13 +687,14 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf } deltaTime -= INCREMENT_TIME; - } while (fastIncrement && deltaTime > 0.0f); + } + while (fastIncrement && deltaTime > 0.0f); bool isConfigValueInBounds = config->Value >= valueMin && config->Value <= valueMax; if ((increment || decrement) && isConfigValueInBounds && isPlayIncrementSound) { - g_lastIncrementSoundTime = time; + m_lastIncrementSoundTime = time; Game_PlaySound("sys_actstg_twn_speechbutton"); } @@ -697,33 +707,42 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf std::string valueText; if constexpr (std::is_same_v) + { valueText = std::format("{}%", int32_t(round(config->Value * 100.0f))); + } else if constexpr (std::is_same_v) + { valueText = config->Value >= valueMax ? Localise("Options_Value_Max") : std::format("{}", config->Value); + } else + { valueText = config->GetValueLocalised(); + } size = Scale(20.0f); - textSize = g_newRodinFont->CalcTextSizeA(size, FLT_MAX, 0.0f, valueText.data()); + textSize = m_newRodinFont->CalcTextSizeA(size, FLT_MAX, 0.0f, valueText.data()); min.x += ((max.x - min.x) - textSize.x) / 2.0f; min.y += ((max.y - min.y) - textSize.y) / 2.0f; - SetGradient( + SetGradient + ( min, { min.x + textSize.x, min.y + textSize.y }, IM_COL32(192, 255, 0, 255), IM_COL32(128, 170, 0, 255) ); - DrawTextWithOutline( - g_newRodinFont, + DrawTextWithOutline + ( + m_newRodinFont, size, min, IM_COL32(255, 255, 255, 255 * alpha), valueText.data(), - Scale(2), - IM_COL32(0, 0, 0, 255 * alpha)); + 2, + IM_COL32(0, 0, 0, 255 * alpha) + ); ResetGradient(); } @@ -734,11 +753,11 @@ static void DrawConfigOptions() auto clipRectMin = drawList->GetClipRectMin(); auto clipRectMax = drawList->GetClipRectMax(); - g_selectedItem = nullptr; + m_selectedItem = nullptr; float gridSize = Scale(GRID_SIZE); float optionHeightWithPadding = gridSize * 6.0f; - float yOffset = -g_firstVisibleRowIndex * optionHeightWithPadding; + float yOffset = -m_firstVisibleRowIndex * optionHeightWithPadding; int32_t rowCount = 0; @@ -746,7 +765,7 @@ static void DrawConfigOptions() auto cmnReason = &Localise("Options_Desc_NotAvailable"); // TODO: Don't use raw numbers here! - switch (g_categoryIndex) + switch (m_categoryIndex) { case 0: // SYSTEM DrawConfigOption(rowCount++, yOffset, &Config::Language, !OptionsMenu::s_isPause, cmnReason); @@ -794,58 +813,58 @@ static void DrawConfigOptions() auto inputState = SWA::CInputState::GetInstance(); - bool upIsHeld = !g_lockedOnOption && (inputState->GetPadState().IsDown(SWA::eKeyState_DpadUp) || + bool upIsHeld = !m_lockedOnOption && (inputState->GetPadState().IsDown(SWA::eKeyState_DpadUp) || inputState->GetPadState().LeftStickVertical > 0.5f); - bool downIsHeld = !g_lockedOnOption && (inputState->GetPadState().IsDown(SWA::eKeyState_DpadDown) || + bool downIsHeld = !m_lockedOnOption && (inputState->GetPadState().IsDown(SWA::eKeyState_DpadDown) || inputState->GetPadState().LeftStickVertical < -0.5f); - bool scrollUp = !g_upWasHeld && upIsHeld; - bool scrollDown = !g_downWasHeld && downIsHeld; + bool scrollUp = !m_upWasHeld && upIsHeld; + bool scrollDown = !m_downWasHeld && downIsHeld; - int32_t prevSelectedRowIndex = g_selectedRowIndex; + int32_t prevSelectedRowIndex = m_selectedRowIndex; if (scrollUp) { - --g_selectedRowIndex; - if (g_selectedRowIndex < 0) - g_selectedRowIndex = rowCount - 1; + --m_selectedRowIndex; + if (m_selectedRowIndex < 0) + m_selectedRowIndex = rowCount - 1; } else if (scrollDown) { - ++g_selectedRowIndex; - if (g_selectedRowIndex >= rowCount) - g_selectedRowIndex = 0; + ++m_selectedRowIndex; + if (m_selectedRowIndex >= rowCount) + m_selectedRowIndex = 0; } if (scrollUp || scrollDown) { - g_rowSelectionTime = ImGui::GetTime(); - g_prevSelectedRowIndex = prevSelectedRowIndex; + m_rowSelectionTime = ImGui::GetTime(); + m_prevSelectedRowIndex = prevSelectedRowIndex; Game_PlaySound("sys_worldmap_cursor"); } - g_upWasHeld = upIsHeld; - g_downWasHeld = downIsHeld; + m_upWasHeld = upIsHeld; + m_downWasHeld = downIsHeld; int32_t visibleRowCount = int32_t(floor((clipRectMax.y - clipRectMin.y) / optionHeightWithPadding)); bool disableMoveAnimation = false; - if (g_firstVisibleRowIndex > g_selectedRowIndex) + if (m_firstVisibleRowIndex > m_selectedRowIndex) { - g_firstVisibleRowIndex = g_selectedRowIndex; + m_firstVisibleRowIndex = m_selectedRowIndex; disableMoveAnimation = true; } - if (g_firstVisibleRowIndex + visibleRowCount - 1 < g_selectedRowIndex) + if (m_firstVisibleRowIndex + visibleRowCount - 1 < m_selectedRowIndex) { - g_firstVisibleRowIndex = std::max(0, g_selectedRowIndex - visibleRowCount + 1); + m_firstVisibleRowIndex = std::max(0, m_selectedRowIndex - visibleRowCount + 1); disableMoveAnimation = true; } if (disableMoveAnimation) - g_prevSelectedRowIndex = g_selectedRowIndex; + m_prevSelectedRowIndex = m_selectedRowIndex; // Pop clip rect from DrawCategories drawList->PopClipRect(); @@ -855,10 +874,11 @@ static void DrawConfigOptions() { float totalHeight = (clipRectMax.y - clipRectMin.y); float heightRatio = float(visibleRowCount) / float(rowCount); - float offsetRatio = float(g_firstVisibleRowIndex) / float(rowCount); + float offsetRatio = float(m_firstVisibleRowIndex) / float(rowCount); float minY = offsetRatio * totalHeight + clipRectMin.y; - drawList->AddRectFilled( + drawList->AddRectFilled + ( { clipRectMax.x, minY }, { clipRectMax.x + gridSize, minY + totalHeight * heightRatio }, IM_COL32(0, 128, 0, 255) @@ -876,9 +896,13 @@ static void DrawSettingsPanel() DrawContainer(settingsMin, settingsMax); if (DrawCategories()) + { DrawConfigOptions(); + } else + { ResetSelection(); + } // Pop clip rect from DrawContainer drawList->PopClipRect(); @@ -901,21 +925,21 @@ static void DrawInfoPanel() // Thumbnail box drawList->AddRectFilled(clipRectMin, thumbnailMax, IM_COL32(0, 0, 0, 255)); - if (g_selectedItem) + if (m_selectedItem) { - auto desc = g_selectedItem->GetDescription(); + auto desc = m_selectedItem->GetDescription(); - if (g_inaccessibleReason) + if (m_inaccessibleReason) { - desc = *g_inaccessibleReason; + desc = *m_inaccessibleReason; } else { // Specialised description for resolution scale - if (g_selectedItem->GetName() == "ResolutionScale") + if (m_selectedItem->GetName() == "ResolutionScale") { char buf[100]; - auto resScale = round(*(float*)g_selectedItem->GetValue() * 1000) / 1000; + auto resScale = round(*(float*)m_selectedItem->GetValue() * 1000) / 1000; std::snprintf(buf, sizeof(buf), desc.c_str(), (int)((float)Window::s_width * resScale), @@ -924,13 +948,14 @@ static void DrawInfoPanel() desc = buf; } - desc += "\n\n" + g_selectedItem->GetValueDescription(); + desc += "\n\n" + m_selectedItem->GetValueDescription(); } auto size = Scale(26.0f); - drawList->AddText( - g_seuratFont, + drawList->AddText + ( + m_seuratFont, size, { clipRectMin.x, thumbnailMax.y + size - 5.0f }, IM_COL32_WHITE, @@ -944,6 +969,17 @@ static void DrawInfoPanel() drawList->PopClipRect(); } +void OptionsMenu::Init() +{ + auto& io = ImGui::GetIO(); + + constexpr float FONT_SCALE = 2.0f; + + m_seuratFont = io.Fonts->AddFontFromFileTTF("FOT-SeuratPro-M.otf", 26.0f * FONT_SCALE); + m_dfsogeistdFont = io.Fonts->AddFontFromFileTTF("DFSoGeiStd-W7.otf", 48.0f * FONT_SCALE); + m_newRodinFont = io.Fonts->AddFontFromFileTTF("FOT-NewRodinPro-DB.otf", 20.0f * FONT_SCALE); +} + void OptionsMenu::Draw() { auto pInputState = SWA::CInputState::GetInstance(); @@ -953,7 +989,7 @@ void OptionsMenu::Draw() // We've entered the menu now, no need to check this. if (pInputState->GetPadState().IsReleased(SWA::eKeyState_A)) - g_isEnterKeyBuffered = false; + m_isEnterKeyBuffered = false; g_callbackDataIndex = 0; @@ -975,16 +1011,16 @@ void OptionsMenu::Open(bool isPause, SWA::EMenuType pauseMenuType) s_pauseMenuType = pauseMenuType; - g_appearTime = ImGui::GetTime(); - g_categoryIndex = 0; - g_categoryAnimMin = { 0.0f, 0.0f }; - g_categoryAnimMax = { 0.0f, 0.0f }; - g_selectedItem = nullptr; + m_appearTime = ImGui::GetTime(); + m_categoryIndex = 0; + m_categoryAnimMin = { 0.0f, 0.0f }; + m_categoryAnimMax = { 0.0f, 0.0f }; + m_selectedItem = nullptr; /* Store button state so we can track it later and prevent the first item being selected. */ if (SWA::CInputState::GetInstance()->GetPadState().IsDown(SWA::eKeyState_A)) - g_isEnterKeyBuffered = true; + m_isEnterKeyBuffered = true; ResetSelection(); @@ -1006,5 +1042,5 @@ void OptionsMenu::Close() bool OptionsMenu::CanClose() { - return !g_lockedOnOption; + return !m_lockedOnOption; } diff --git a/UnleashedRecomp/ui/options_menu.h b/UnleashedRecomp/ui/options_menu.h index 90cdbd42..6ea404b5 100644 --- a/UnleashedRecomp/ui/options_menu.h +++ b/UnleashedRecomp/ui/options_menu.h @@ -1,9 +1,8 @@ #pragma once -#include "imgui_view.h" #include -struct OptionsMenu : ImGuiView +struct OptionsMenu { public: inline static bool s_isVisible = false; @@ -11,8 +10,8 @@ public: inline static SWA::EMenuType s_pauseMenuType; - void Init() override; - void Draw() override; + static void Init(); + static void Draw(); static void Open(bool isPause = false, SWA::EMenuType pauseMenuType = SWA::eMenuType_WorldMap); static void Close();