From e24fa84e166c0eeb2ea6da7c812a88feb1eaf951 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Thu, 2 Jan 2025 22:07:27 +0000 Subject: [PATCH] options_menu: implemented Miles Electric transition --- UnleashedRecomp/CMakeLists.txt | 1 + UnleashedRecomp/gpu/video.cpp | 2 +- UnleashedRecomp/ui/button_guide.h | 2 + UnleashedRecomp/ui/fader.cpp | 11 +- UnleashedRecomp/ui/message_window.cpp | 4 +- UnleashedRecomp/ui/options_menu.cpp | 168 +++++++++++++++++++++----- 6 files changed, 154 insertions(+), 34 deletions(-) diff --git a/UnleashedRecomp/CMakeLists.txt b/UnleashedRecomp/CMakeLists.txt index 4bae402..5f9e5fd 100644 --- a/UnleashedRecomp/CMakeLists.txt +++ b/UnleashedRecomp/CMakeLists.txt @@ -487,6 +487,7 @@ BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/op BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/window_size.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/window_size.dds" ARRAY_NAME "g_window_size" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/xbox_color_correction_false.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/xbox_color_correction_false.dds" ARRAY_NAME "g_xbox_color_correction_false" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/xbox_color_correction_true.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/xbox_color_correction_true.dds" ARRAY_NAME "g_xbox_color_correction_true" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/miles_electric.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/miles_electric.dds" ARRAY_NAME "g_miles_electric" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/game_icon.bmp" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/game_icon.bmp" ARRAY_NAME "g_game_icon") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/game_icon_night.bmp" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/game_icon_night.bmp" ARRAY_NAME "g_game_icon_night") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_worldmap_cursor.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_worldmap_cursor.ogg" ARRAY_NAME "g_sys_worldmap_cursor") diff --git a/UnleashedRecomp/gpu/video.cpp b/UnleashedRecomp/gpu/video.cpp index af9badc..e9e1fc9 100644 --- a/UnleashedRecomp/gpu/video.cpp +++ b/UnleashedRecomp/gpu/video.cpp @@ -2032,9 +2032,9 @@ static void DrawImGui() OptionsMenu::Draw(); AchievementOverlay::Draw(); InstallerWizard::Draw(); - Fader::Draw(); MessageWindow::Draw(); ButtonGuide::Draw(); + Fader::Draw(); DrawProfiler(); diff --git a/UnleashedRecomp/ui/button_guide.h b/UnleashedRecomp/ui/button_guide.h index d6c375c..50d26ef 100644 --- a/UnleashedRecomp/ui/button_guide.h +++ b/UnleashedRecomp/ui/button_guide.h @@ -47,6 +47,8 @@ public: Button(std::string name, EButtonIcon icon, EButtonAlignment alignment, EFontQuality fontQuality = EFontQuality::High, bool* visibility = nullptr) : Name(name), Icon(icon), Alignment(alignment), FontQuality(fontQuality), Visibility(visibility) {} + Button(std::string name, EButtonIcon icon, EButtonAlignment alignment, bool* visibility) : Name(name), Icon(icon), Alignment(alignment), Visibility(visibility) {} + Button(std::string name, EButtonIcon icon, bool* visibility) : Name(name), Icon(icon), Visibility(visibility) {} Button(std::string name, EButtonIcon icon) : Name(name), Icon(icon) {} diff --git a/UnleashedRecomp/ui/fader.cpp b/UnleashedRecomp/ui/fader.cpp index 18a37aa..a186e45 100644 --- a/UnleashedRecomp/ui/fader.cpp +++ b/UnleashedRecomp/ui/fader.cpp @@ -8,7 +8,7 @@ static bool g_isFadeIn; static float g_startTime; static float g_duration; -static ImU32 g_colour = IM_COL32(0, 0, 0, 255); +static ImU32 g_colour = IM_COL32_BLACK; static std::function g_endCallback; static float g_endCallbackDelay; @@ -25,7 +25,10 @@ void Fader::Draw() if (time >= g_duration + g_endCallbackDelay) { if (g_endCallback) + { g_endCallback(); + g_endCallback = nullptr; + } g_isFading = false; } @@ -37,6 +40,9 @@ void Fader::Draw() : Lerp(0, 1, time); } + if (g_isFadeIn && !g_isFading) + return; + auto colour = IM_COL32(g_colour & 0xFF, (g_colour >> 8) & 0xFF, (g_colour >> 16) & 0xFF, 255 * alpha); ImGui::GetForegroundDrawList()->AddRectFilled({ 0, 0 }, ImGui::GetIO().DisplaySize, colour); @@ -47,13 +53,14 @@ static void DoFade(bool isFadeIn, float duration, std::function endCallb if (g_isFading) return; - Fader::s_isVisible = true; g_isFading = true; g_isFadeIn = isFadeIn; g_startTime = ImGui::GetTime(); g_duration = duration; g_endCallback = endCallback; g_endCallbackDelay = endCallbackDelay; + + Fader::s_isVisible = true; } void Fader::SetFadeColour(ImU32 colour) diff --git a/UnleashedRecomp/ui/message_window.cpp b/UnleashedRecomp/ui/message_window.cpp index 94b468c..d82bab3 100644 --- a/UnleashedRecomp/ui/message_window.cpp +++ b/UnleashedRecomp/ui/message_window.cpp @@ -417,8 +417,8 @@ void MessageWindow::Draw() for (int i = 0; i < rowCount; i++) { - ImVec2 itemMin = { clipRectMin.x + windowMarginX, clipRectMin.y + windowMarginY + itemHeight * i }; - ImVec2 itemMax = { clipRectMax.x - windowMarginX, clipRectMin.y + windowMarginY + itemHeight * i + itemHeight }; + ImVec2 itemMin = { listMin.x, listMin.y + itemHeight * i }; + ImVec2 itemMax = { listMax.x, clipRectMin.y + windowMarginY + itemHeight * i + itemHeight }; if (ImGui::IsMouseHoveringRect(itemMin, itemMax, false)) { diff --git a/UnleashedRecomp/ui/options_menu.cpp b/UnleashedRecomp/ui/options_menu.cpp index 66da4bd..8f86685 100644 --- a/UnleashedRecomp/ui/options_menu.cpp +++ b/UnleashedRecomp/ui/options_menu.cpp @@ -13,9 +13,18 @@ #include #include #include +#include #include +#include + +static constexpr double MILES_ELECTRIC_SCALE_DURATION = 16.0; +static constexpr double MILES_ELECTRIC_BACKGROUND_DURATION = 16.0; +static constexpr double MILES_ELECTRIC_FOREGROUND_FADE_DURATION = 6.0; +static constexpr double MILES_ELECTRIC_FOREGROUND_FADE_IN_TIME = MILES_ELECTRIC_SCALE_DURATION - 2.0; +static constexpr double MILES_ELECTRIC_FOREGROUND_FADE_OUT_TIME = MILES_ELECTRIC_FOREGROUND_FADE_IN_TIME + MILES_ELECTRIC_FOREGROUND_FADE_DURATION; + static constexpr double CONTAINER_LINE_ANIMATION_DURATION = 8.0; static constexpr double CONTAINER_OUTER_TIME = CONTAINER_LINE_ANIMATION_DURATION + 8.0; // 8 frame delay @@ -66,12 +75,17 @@ static const IConfigDef* g_selectedItem; static std::string* g_inaccessibleReason; +static bool g_isStage = false; +static bool g_isClosing = false; +static bool g_isControlsVisible = false; static bool g_isEnterKeyBuffered = false; static bool g_canReset = false; static bool g_isLanguageOptionChanged = false; static double g_appearTime = 0.0; +static std::unique_ptr g_upMilesElectric; + static void DrawScanlineBars() { constexpr uint32_t COLOR0 = IM_COL32(203, 255, 0, 0); @@ -174,7 +188,7 @@ static float AlignToNextGrid(float value) static void DrawContainer(ImVec2 min, ImVec2 max) { - double containerHeight = ComputeMotion(g_appearTime, 0.0, CONTAINER_LINE_ANIMATION_DURATION); + double containerHeight = g_isStage ? 1.0 : ComputeMotion(g_appearTime, 0.0, CONTAINER_LINE_ANIMATION_DURATION); float center = (min.y + max.y) / 2.0f; min.y = Lerp(center, min.y, containerHeight); @@ -183,9 +197,9 @@ static void DrawContainer(ImVec2 min, ImVec2 max) auto& res = ImGui::GetIO().DisplaySize; auto drawList = ImGui::GetForegroundDrawList(); - double outerAlpha = ComputeMotion(g_appearTime, CONTAINER_OUTER_TIME, CONTAINER_OUTER_DURATION); - double innerAlpha = ComputeMotion(g_appearTime, CONTAINER_INNER_TIME, CONTAINER_INNER_DURATION); - double backgroundAlpha = ComputeMotion(g_appearTime, CONTAINER_BACKGROUND_TIME, CONTAINER_BACKGROUND_DURATION); + double outerAlpha = g_isStage ? 1.0 : ComputeMotion(g_appearTime, CONTAINER_OUTER_TIME, CONTAINER_OUTER_DURATION); + double innerAlpha = g_isStage ? 1.0 : ComputeMotion(g_appearTime, CONTAINER_INNER_TIME, CONTAINER_INNER_DURATION); + double backgroundAlpha = g_isStage ? 1.0 : ComputeMotion(g_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); @@ -252,7 +266,7 @@ static void ResetSelection() static bool DrawCategories() { - double motion = ComputeMotion(g_appearTime, CONTAINER_CATEGORY_TIME, CONTAINER_CATEGORY_DURATION); + double motion = g_isStage ? 1.0 : ComputeMotion(g_appearTime, CONTAINER_CATEGORY_TIME, CONTAINER_CATEGORY_DURATION); if (motion == 0.0) return false; @@ -412,7 +426,7 @@ static bool DrawCategories() ResetGradient(); } - if ((ImGui::GetTime() - g_appearTime) >= (CONTAINER_FULL_DURATION / 60.0)) + if (g_isStage || (ImGui::GetTime() - g_appearTime) >= (CONTAINER_FULL_DURATION / 60.0)) { drawList->PushClipRect({ clipRectMin.x, clipRectMin.y + gridSize * 6.0f }, { clipRectMax.x - gridSize, clipRectMax.y - gridSize }); return true; @@ -555,7 +569,7 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf DrawTextWithMarquee(g_seuratFont, size, textPos, min, max, textColour, configName.c_str(), g_rowSelectionTime, 0.9, Scale(250.0)); // Show reset button if this option is accessible or not a language option. - g_canReset = g_selectedItem->GetName().find("Language") == std::string::npos && isAccessible; + g_canReset = !g_lockedOnOption && g_selectedItem->GetName().find("Language") == std::string::npos && isAccessible; } else { @@ -1026,6 +1040,78 @@ static void DrawInfoPanel() drawList->PopClipRect(); } +static bool DrawMilesElectric() +{ + auto drawList = ImGui::GetForegroundDrawList(); + auto& res = ImGui::GetIO().DisplaySize; + + auto scaleMotion = ComputeMotion(g_appearTime, 0, MILES_ELECTRIC_SCALE_DURATION); + + if (scaleMotion >= 1.0) + { + if (g_isClosing) + OptionsMenu::s_isVisible = false; + + return true; + } + + auto bgAlphaMotion = ComputeMotion(g_appearTime, 0, MILES_ELECTRIC_BACKGROUND_DURATION); + auto bgAlpha = g_isClosing + ? Hermite(255, 0, bgAlphaMotion) + : Hermite(0, 255, bgAlphaMotion); + + drawList->AddRectFilled({ 0, 0 }, res, IM_COL32(64, 64, 64, bgAlpha)); + + auto y = g_isClosing + ? Hermite(140, 0, scaleMotion) + : Hermite(0, 140, scaleMotion); + + auto scale = g_isClosing + ? Hermite(Scale(1400), Scale(64), scaleMotion) + : Hermite(Scale(64), Scale(1400), scaleMotion); + + ImVec2 centre = { res.x / 2, res.y / 2 - Scale(y) }; + + auto alpha = g_isClosing + ? Hermite(255, 0, scaleMotion) + : Hermite(255, 127, scaleMotion); + + drawList->AddImage + ( + g_upMilesElectric.get(), + { centre.x - scale, centre.y - scale }, + { centre.x + scale, centre.y + scale }, + { 0, 0 }, + { 1, 1 }, + IM_COL32(255, 255, 255, alpha) + ); + + return false; +} + +static bool DrawFadeTransition() +{ + auto drawList = ImGui::GetForegroundDrawList(); + auto& res = ImGui::GetIO().DisplaySize; + + auto scaleMotion = ComputeMotion(g_appearTime, 0, MILES_ELECTRIC_SCALE_DURATION); + auto fgAlphaOutMotion = ComputeMotion(g_appearTime, MILES_ELECTRIC_FOREGROUND_FADE_OUT_TIME, MILES_ELECTRIC_FOREGROUND_FADE_DURATION); + + if (scaleMotion < 0.8) + return false; + + if (fgAlphaOutMotion >= 1.0) + { + g_isControlsVisible = true; + } + else + { + drawList->AddRectFilled({ 0, 0 }, res, IM_COL32(0, 0, 0, Lerp(255, 0, fgAlphaOutMotion))); + } + + return fgAlphaOutMotion >= 1.0; +} + void OptionsMenu::Init() { auto& io = ImGui::GetIO(); @@ -1035,27 +1121,48 @@ void OptionsMenu::Init() g_newRodinFont = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-DB.otf"); LoadThumbnails(); + + g_upMilesElectric = LOAD_ZSTD_TEXTURE(g_miles_electric); } void OptionsMenu::Draw() { if (!s_isVisible) + { + g_isControlsVisible = false; return; + } // We've entered the menu now, no need to check this. auto pInputState = SWA::CInputState::GetInstance(); if (pInputState->GetPadState().IsReleased(SWA::eKeyState_A)) g_isEnterKeyBuffered = false; - - auto& res = ImGui::GetIO().DisplaySize; - auto drawList = ImGui::GetForegroundDrawList(); - - if (s_isPause && s_pauseMenuType != SWA::eMenuType_WorldMap) - drawList->AddRectFilled({ 0.0f, 0.0f }, res, IM_COL32(0, 0, 0, 223)); - DrawScanlineBars(); - DrawSettingsPanel(); - DrawInfoPanel(); + if (g_isStage) + { + if (!DrawMilesElectric()) + return; + } + else + { + g_isControlsVisible = true; + } + + if (!g_isClosing) + { + auto drawList = ImGui::GetForegroundDrawList(); + auto& res = ImGui::GetIO().DisplaySize; + + if (g_isStage) + drawList->AddRectFilled({ 0.0f, 0.0f }, res, IM_COL32(0, 0, 0, 223)); + + DrawScanlineBars(); + DrawSettingsPanel(); + DrawInfoPanel(); + + if (g_isStage) + DrawFadeTransition(); + } s_isRestartRequired = Config::Language != App::s_language; } @@ -1063,10 +1170,11 @@ void OptionsMenu::Draw() void OptionsMenu::Open(bool isPause, SWA::EMenuType pauseMenuType) { s_isVisible = true; + g_isClosing = false; s_isPause = isPause; - s_pauseMenuType = pauseMenuType; - + g_isStage = isPause && pauseMenuType != SWA::eMenuType_WorldMap; + g_appearTime = ImGui::GetTime(); g_categoryIndex = 0; g_categoryAnimMin = { 0.0f, 0.0f }; @@ -1079,34 +1187,36 @@ void OptionsMenu::Open(bool isPause, SWA::EMenuType pauseMenuType) g_isEnterKeyBuffered = true; ResetSelection(); - + + // Hide CSD UI. *(bool*)g_memory.Translate(0x8328BB26) = false; std::array buttons = { - Button(Localise("Common_Switch"), EButtonIcon::LBRB, EButtonAlignment::Left), + Button(Localise("Common_Switch"), EButtonIcon::LBRB, EButtonAlignment::Left, &g_isControlsVisible), Button(Localise("Common_Reset"), EButtonIcon::X, &g_canReset), - Button(Localise("Common_Select"), EButtonIcon::A), - Button(Localise("Common_Back"), EButtonIcon::B) + Button(Localise("Common_Select"), EButtonIcon::A, &g_isControlsVisible), + Button(Localise("Common_Back"), EButtonIcon::B, &g_isControlsVisible) }; - - ButtonGuide::Open(buttons); + ButtonGuide::Open(buttons); ButtonGuide::SetSideMargins(250); - - // TODO: animate Miles Electric in if we're in a stage. } void OptionsMenu::Close() { - s_isVisible = false; + g_isClosing = true; + g_appearTime = ImGui::GetTime(); + // Skip Miles Electric animation at main menu. + if (!g_isStage) + s_isVisible = false; + + // Show CSD UI. *(bool*)g_memory.Translate(0x8328BB26) = true; ButtonGuide::Close(); Config::Save(); - - // TODO: animate Miles Electric out if we're in a stage. } bool OptionsMenu::CanClose()