diff --git a/MarathonRecomp/CMakeLists.txt b/MarathonRecomp/CMakeLists.txt index db0600154..341971e35 100644 --- a/MarathonRecomp/CMakeLists.txt +++ b/MarathonRecomp/CMakeLists.txt @@ -143,6 +143,7 @@ set(MARATHON_RECOMP_PATCHES_CXX_SOURCES "patches/misc_patches.cpp" "patches/player_patches.cpp" "patches/SaveDataTask_patches.cpp" + "patches/text_patches.cpp" "patches/TitleTask_patches.cpp" "patches/video_patches.cpp" ) @@ -543,7 +544,9 @@ BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/com BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/select.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/select.dds" ARRAY_NAME "g_select" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/light.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/light.dds" ARRAY_NAME "g_light" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/main_menu1.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/main_menu1.dds" ARRAY_NAME "g_main_menu1" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/arrow.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/arrow.dds" ARRAY_NAME "arrow" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/arrow.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/arrow.dds" ARRAY_NAME "g_arrow" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/window.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/window.dds" ARRAY_NAME "g_window" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/select_arrow.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/select_arrow.dds" ARRAY_NAME "g_select_arrow" COMPRESSION_TYPE "zstd") ## Installer ## BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_001.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_001.dds" ARRAY_NAME "g_install_001" COMPRESSION_TYPE "zstd") diff --git a/MarathonRecomp/api/Sonicteam/SoX/Engine/Doc.h b/MarathonRecomp/api/Sonicteam/SoX/Engine/Doc.h index 435ea12a2..1e7e0903e 100644 --- a/MarathonRecomp/api/Sonicteam/SoX/Engine/Doc.h +++ b/MarathonRecomp/api/Sonicteam/SoX/Engine/Doc.h @@ -10,8 +10,12 @@ namespace Sonicteam::SoX::Engine public: xpointer m_pVftable; MARATHON_INSERT_PADDING(4); - xpointer m_pDocMode; - MARATHON_INSERT_PADDING(0x50); + xpointer m_pDocMode; + xpointer m_pRootTask; + xpointer m_pRootGTask; + MARATHON_INSERT_PADDING(8); + xpointer m_pDocModeExecutor; + MARATHON_INSERT_PADDING(0x3C); template inline T* GetDocMode(); diff --git a/MarathonRecomp/api/Sonicteam/TitleTask.h b/MarathonRecomp/api/Sonicteam/TitleTask.h index 5baccb43f..2aa74a45d 100644 --- a/MarathonRecomp/api/Sonicteam/TitleTask.h +++ b/MarathonRecomp/api/Sonicteam/TitleTask.h @@ -8,7 +8,27 @@ namespace Sonicteam class TitleTask : public SoX::Engine::Task { public: - MARATHON_INSERT_PADDING(0x30); + static constexpr float ms_DefaultMovieWaitTime = 30.0f; + + enum TitleState : uint32_t + { + TitleState_Open, + TitleState_Wait, + TitleState_PressStart, + TitleState_OptionsOpen = 5, + TitleState_OptionsWait, + TitleState_OptionsProceed = 7, + TitleState_Proceed, + TitleState_Outro = 13 + }; + + be m_State; + MARATHON_INSERT_PADDING(0x0C); + be m_MovieWaitTime; + be m_Field60; + MARATHON_INSERT_PADDING(0x18); be m_SelectedIndex; + MARATHON_INSERT_PADDING(0x20); + be m_CastIndex; }; } diff --git a/MarathonRecomp/exports.cpp b/MarathonRecomp/exports.cpp index ea250b7e7..e11535e20 100644 --- a/MarathonRecomp/exports.cpp +++ b/MarathonRecomp/exports.cpp @@ -1,9 +1,8 @@ +#include "exports.h" #include -#include #include #include -#include -#include +#include void Game_PlaySound(const char* pName) { @@ -11,23 +10,22 @@ void Game_PlaySound(const char* pName) { EmbeddedPlayer::Play(pName); } + else + { + Game_PlaySound("system", pName); + } +} + +void Game_PlaySound(const char* pBankName, const char* pName) +{ + auto pBankNameGuest = g_userHeap.Alloc(strlen(pBankName) + 1); + auto pNameGuest = g_userHeap.Alloc(strlen(pName) + 1); + + strcpy((char*)pBankNameGuest, pBankName); + strcpy((char*)pNameGuest, pName); + + GuestToHostFunction(sub_824C7868, App::s_pApp->m_pDoc->m_pRootTask.get(), pBankNameGuest, pNameGuest); - // TODO: support in-game sounds (see sub_824C7868). - // else - // { - // // Use EVENT category in cutscenes since SYSTEM gets muted by the game. - // uint32_t category = !InspirePatches::s_sceneName.empty() ? 10 : 7; - // - // guest_stack_var soundPlayer; - // GuestToHostFunction(sub_82B4DF50, soundPlayer.get(), ((be*)g_memory.Translate(0x83367900))->get(), category, 0, 0); - // - // auto soundPlayerVtable = (be*)g_memory.Translate(*(be*)soundPlayer->get()); - // uint32_t virtualFunction = *(soundPlayerVtable + 1); - // - // size_t strLen = strlen(pName); - // void *strAllocation = g_userHeap.Alloc(strLen + 1); - // memcpy(strAllocation, pName, strLen + 1); - // GuestToHostFunction(virtualFunction, soundPlayer->get(), strAllocation, 0); - // g_userHeap.Free(strAllocation); - // } + g_userHeap.Free(pBankNameGuest); + g_userHeap.Free(pNameGuest); } diff --git a/MarathonRecomp/exports.h b/MarathonRecomp/exports.h index 5d9eef7d9..d5e6a6138 100644 --- a/MarathonRecomp/exports.h +++ b/MarathonRecomp/exports.h @@ -1,3 +1,4 @@ #pragma once void Game_PlaySound(const char* pName); +void Game_PlaySound(const char* pBankName, const char* pName); diff --git a/MarathonRecomp/gpu/imgui/imgui_font_builder.cpp b/MarathonRecomp/gpu/imgui/imgui_font_builder.cpp index 1ac513130..0b2e28d76 100644 --- a/MarathonRecomp/gpu/imgui/imgui_font_builder.cpp +++ b/MarathonRecomp/gpu/imgui/imgui_font_builder.cpp @@ -223,7 +223,7 @@ static bool FontBuilder_Build(ImFontAtlas* atlas) double spaceAdvance = 0.0; auto& config = atlas->ConfigData[i]; - bool increaseSpacing = strstr(config.Name, "Seurat") != nullptr; + bool increaseSpacing = strstr(config.Name, "Rodin") != nullptr; auto& [index, count] = ranges[i]; for (size_t j = 0; j < count; j++) @@ -238,7 +238,7 @@ static bool FontBuilder_Build(ImFontAtlas* atlas) { if (increaseSpacing) { - advance *= 1.5; + advance *= 1.75; } spaceAdvance = advance; diff --git a/MarathonRecomp/gpu/video.cpp b/MarathonRecomp/gpu/video.cpp index 63d91b808..ea6545835 100644 --- a/MarathonRecomp/gpu/video.cpp +++ b/MarathonRecomp/gpu/video.cpp @@ -2809,8 +2809,8 @@ static void DrawImGui() // OptionsMenu::Draw(); AchievementOverlay::Draw(); InstallerWizard::Draw(); - MessageWindow::Draw(); ButtonGuide::Draw(); + MessageWindow::Draw(); Fader::Draw(); BlackBar::Draw(); diff --git a/MarathonRecomp/locale/locale.cpp b/MarathonRecomp/locale/locale.cpp index d89bd7724..4e554e9b4 100644 --- a/MarathonRecomp/locale/locale.cpp +++ b/MarathonRecomp/locale/locale.cpp @@ -525,36 +525,36 @@ std::unordered_map> // Notes: message appears when user chooses "Quit" on the first available installation screen. "Installer_Message_Quit", { - { ELanguage::English, "Are you sure you want to quit?" }, - { ELanguage::Japanese, "[終了:しゅうりょう]してもよろしいですか?" }, - { ELanguage::German, "Installation verlassen?" }, - { ELanguage::French, "Êtes-vous sûr de vouloir quitter ?" }, - { ELanguage::Spanish, "¿Estás seguro de que quieres\nsalir?" }, - { ELanguage::Italian, "Sicuro di voler uscire?" } + { ELanguage::English, "Exit the installer.\nOK?" }, + { ELanguage::Japanese, "" }, + { ELanguage::German, "" }, + { ELanguage::French, "" }, + { ELanguage::Spanish, "" }, + { ELanguage::Italian, "" } } }, { // Notes: message appears when user chooses "Cancel" during installation. "Installer_Message_Cancel", { - { ELanguage::English, "Are you sure you want to cancel the installation?" }, - { ELanguage::Japanese, "インストールをキャンセルしてもよろしいですか?" }, - { ELanguage::German, "Bist du dir sicher, dass du die Installation abbrechen möchtest?" }, - { ELanguage::French, "Êtes-vous sûr de vouloir annuler l'installation ?" }, - { ELanguage::Spanish, "¿Estás seguro de que quieres cancelar la instalación?" }, - { ELanguage::Italian, "Sicuro di voler annullare l'installazione?" } + { ELanguage::English, "Cancel the installation.\nOK?" }, + { ELanguage::Japanese, "" }, + { ELanguage::German, "" }, + { ELanguage::French, "" }, + { ELanguage::Spanish, "" }, + { ELanguage::Italian, "" } } }, { // Notes: message appears when pressing B at the title screen. "Title_Message_Quit", { - { ELanguage::English, "Are you sure you want to quit?" }, - { ELanguage::Japanese, "[終了:しゅうりょう]してもよろしいですか?" }, - { ELanguage::German, "Spiel verlassen?" }, - { ELanguage::French, "Êtes-vous sûr de vouloir quitter ?" }, - { ELanguage::Spanish, "¿Estás seguro de que quieres\nsalir?" }, - { ELanguage::Italian, "Sicuro di voler uscire ?" } + { ELanguage::English, "Exit the game.\nOK?" }, + { ELanguage::Japanese, "" }, + { ELanguage::German, "" }, + { ELanguage::French, "" }, + { ELanguage::Spanish, "" }, + { ELanguage::Italian, "" } } }, { @@ -663,6 +663,17 @@ std::unordered_map> { ELanguage::Italian, "La verifica dei file d'installazione non è andata a buon fine.\n\nErrore: %s\n\nIl gioco si chiuderà. Prova a reinstallare il gioco utilizzando l'argomento di avvio --install." } } }, + { + "Common_OK", + { + { ELanguage::English, "OK" }, + { ELanguage::Japanese, "OK" }, + { ELanguage::German, "OK" }, + { ELanguage::French, "OK" }, + { ELanguage::Spanish, "OK" }, + { ELanguage::Italian, "OK" } + } + }, { "Common_On", { diff --git a/MarathonRecomp/patches/SaveDataTask_patches.cpp b/MarathonRecomp/patches/SaveDataTask_patches.cpp index f1023ce60..0e9c5cffe 100644 --- a/MarathonRecomp/patches/SaveDataTask_patches.cpp +++ b/MarathonRecomp/patches/SaveDataTask_patches.cpp @@ -46,29 +46,3 @@ void SaveAlertThreeOptionRemoveDeviceSelect(PPCRegister& r5) // second index for the three option alert windows. options[2] = 0; } - -// Load system message. -PPC_FUNC_IMPL(__imp__sub_825ECB48); -PPC_FUNC(sub_825ECB48) -{ - auto* pMessage = (const char*)(base + ctx.r5.u32); - void* pNewMessage = nullptr; - - if (strcmp(pMessage, "msg_deviceselect") == 0) - { - constexpr const char* MSG_RETRY = "msg_retry"; - - pNewMessage = g_userHeap.Alloc(strlen(MSG_RETRY) + 1); - - strcpy((char*)pNewMessage, MSG_RETRY); - - ctx.r5.u32 = g_memory.MapVirtual(pNewMessage); - } - - __imp__sub_825ECB48(ctx, base); - - if (!pNewMessage) - return; - - g_userHeap.Free(pNewMessage); -} diff --git a/MarathonRecomp/patches/TitleTask_patches.cpp b/MarathonRecomp/patches/TitleTask_patches.cpp index e6619bb01..0fe1c5df2 100644 --- a/MarathonRecomp/patches/TitleTask_patches.cpp +++ b/MarathonRecomp/patches/TitleTask_patches.cpp @@ -1,32 +1,166 @@ #include +#include +#include #include #include +#include +#include -void RestoreTitleButtons(PPCRegister& val) +constexpr auto TITLE_OPTION_OUTRO_FRAMES = (1.0 / 60.0) * 45.0; +constexpr auto TITLE_OUTRO_FRAMES = (1.0 / 60.0) * 25.0; +constexpr auto TITLE_OPTION_OUTRO_TOTAL_FRAMES = TITLE_OPTION_OUTRO_FRAMES + TITLE_OUTRO_FRAMES; + +static double g_titleProceedOutroTime{}; +static double g_titleExitOutroTime{}; + +static bool g_quitMessageOpen{}; +static bool g_saveDataExists{}; + +bool ProcessQuitMessage(Sonicteam::TitleTask* pTitleTask) { - val.u32 = 3; + static int s_quitMessageResult = -1; + static std::atomic s_faderBegun = false; + + if (!g_quitMessageOpen) + return false; + + if ((App::s_time - g_titleExitOutroTime) < TITLE_OUTRO_FRAMES) + return true; + + static std::array s_options = { Localise("Common_Yes"), Localise("Common_No") }; + + if (MessageWindow::Open(Localise("Title_Message_Quit"), &s_quitMessageResult, s_options, 1) == MSG_CLOSED) + { + if (s_quitMessageResult == 0) + { + Fader::FadeOut(1, []() { App::Exit(); }); + s_faderBegun = true; + } + else + { + // Play Title_Open. + pTitleTask->m_MovieWaitTime = Sonicteam::TitleTask::ms_DefaultMovieWaitTime; + GuestToHostFunction(sub_825119D8, pTitleTask, 0, 0); + } + + g_quitMessageOpen = false; + s_quitMessageResult = -1; + } + + return true; } -void RestoreTitleButtons2(PPCRegister& val) +void TitleTask_SetDefaultOption(PPCRegister& r3, PPCRegister& r4) { - val.u32 = 5; + if (!g_saveDataExists) + return; + + // Set default option to CONTINUE if save data exists. + reinterpret_cast(g_memory.Translate(r3.u32))->m_SelectedIndex = 1; + r4.u32 = 5; } -void SetDefaultTitleTaskSelection(PPCRegister& pThis, PPCRegister& csdIndex) +bool TitleTask_RedirectStateTransitionToOutroAnim(PPCRegister& r31) { - auto pTitleTask = (Sonicteam::TitleTask*)g_memory.Translate(pThis.u32); - auto saveFilePath = GetSaveFilePath(false); + if (!g_saveDataExists || Config::DisableTitleInputDelay) + return false; - if (!std::filesystem::exists(saveFilePath)) - return; + // Play Title_Close_02. + GuestToHostFunction(sub_825119D8, r31.u32, 8, 0); - csdIndex.u32 = 5; - pTitleTask->m_SelectedIndex = 1; + return true; } -bool DisableTitleTaskStartWait() +// Sonicteam::TitleTask::Update +PPC_FUNC_IMPL(__imp__sub_825126A0); +PPC_FUNC(sub_825126A0) { - return Config::DisableTitleInputDelay; + auto pTitleTask = (Sonicteam::TitleTask*)(base + ctx.r3.u32); + auto deltaTime = ctx.f1.f64; + + switch (pTitleTask->m_State) + { + case Sonicteam::TitleTask::TitleState_Open: + { + if (!Config::DisableTitleInputDelay) + break; + + // Skip open animation. + GuestToHostFunction(sub_82511CA0, pTitleTask, (int)Sonicteam::TitleTask::TitleState_Wait); + + break; + } + + case Sonicteam::TitleTask::TitleState_Proceed: + { + g_saveDataExists = std::filesystem::exists(GetSaveFilePath(false)); + + // Redirect PRESS START proceed to options open. + if (g_saveDataExists) + GuestToHostFunction(sub_82511CA0, pTitleTask, (int)Sonicteam::TitleTask::TitleState_OptionsOpen); + + break; + } + + case Sonicteam::TitleTask::TitleState_OptionsProceed: + { + if (Config::DisableTitleInputDelay) + break; + + g_titleProceedOutroTime += deltaTime; + + // Wait for outro animation to complete before entering menu. + if (g_titleProceedOutroTime > TITLE_OPTION_OUTRO_TOTAL_FRAMES) + GuestToHostFunction(sub_82511CA0, pTitleTask, 9); + + break; + } + } + + if (pTitleTask->m_State != Sonicteam::TitleTask::TitleState_OptionsProceed) + g_titleProceedOutroTime = 0.0; + + auto skipStateUpdate = ProcessQuitMessage(pTitleTask); + + if (skipStateUpdate) + { + GuestToHostFunction(sub_82511B10, pTitleTask, deltaTime); + + pTitleTask->m_MovieWaitTime = pTitleTask->m_MovieWaitTime - deltaTime; + pTitleTask->m_Field60 = 0; + + if (pTitleTask->m_MovieWaitTime < 0.0f) + pTitleTask->m_MovieWaitTime = 0.0f; + } + else + { + if (auto& spInputManager = App::s_pApp->m_pDoc->m_vspInputManager[0]) + { + auto& rPadState = spInputManager->m_PadState; + + if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_B)) + { + if (pTitleTask->m_State == Sonicteam::TitleTask::TitleState_Wait) + { + // Play Title_Close_03. + Game_PlaySound("deside"); + GuestToHostFunction(sub_825119D8, pTitleTask, 9, 0); + + g_titleExitOutroTime = App::s_time; + g_quitMessageOpen = true; + } + else if (pTitleTask->m_State == Sonicteam::TitleTask::TitleState_OptionsWait) + { + // Return to title wait. + Game_PlaySound("window_close"); + GuestToHostFunction(sub_825119D8, pTitleTask, 1, 1); + GuestToHostFunction(sub_82511CA0, pTitleTask, (int)Sonicteam::TitleTask::TitleState_Wait); + } + } + } + + __imp__sub_825126A0(ctx, base); + } } PPC_FUNC_IMPL(__imp__sub_82511540); diff --git a/MarathonRecomp/patches/text_patches.cpp b/MarathonRecomp/patches/text_patches.cpp new file mode 100644 index 000000000..38ecaef55 --- /dev/null +++ b/MarathonRecomp/patches/text_patches.cpp @@ -0,0 +1,30 @@ +#include "text_patches.h" +#include +#include + +// Load text card. +PPC_FUNC_IMPL(__imp__sub_825ECB48); +PPC_FUNC(sub_825ECB48) +{ + auto* pMessage = (const char*)(base + ctx.r5.u32); + void* pNewMessage = nullptr; + + for (auto& redirect : TextPatches::s_redirectedMessages) + { + if (HashStr(pMessage) != redirect.first) + continue; + + pNewMessage = g_userHeap.Alloc(strlen(redirect.second) + 1); + + strcpy((char*)pNewMessage, redirect.second); + + ctx.r5.u32 = g_memory.MapVirtual(pNewMessage); + } + + __imp__sub_825ECB48(ctx, base); + + if (!pNewMessage) + return; + + g_userHeap.Free(pNewMessage); +} diff --git a/MarathonRecomp/patches/text_patches.h b/MarathonRecomp/patches/text_patches.h new file mode 100644 index 000000000..5bfecfc5c --- /dev/null +++ b/MarathonRecomp/patches/text_patches.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +class TextPatches +{ +public: + static inline xxHashMap s_redirectedMessages + { + { HashStr("msg_deviceselect"), "msg_retry" }, // Replace "Select storage device." text with "Retry" for alert windows. + { HashStr("msg_gamequitconfirm4"), "msg_backtotitle1" } // Replace "Exit the game." text with "Go back to the Title Screen." + }; +}; diff --git a/MarathonRecomp/ui/achievement_menu.cpp b/MarathonRecomp/ui/achievement_menu.cpp index 4b476410f..72622080b 100644 --- a/MarathonRecomp/ui/achievement_menu.cpp +++ b/MarathonRecomp/ui/achievement_menu.cpp @@ -66,7 +66,7 @@ static void DrawContainer(ImVec2 min, ImVec2 max, ImU32 gradientTop, ImU32 gradi { auto drawList = ImGui::GetBackgroundDrawList(); - DrawPauseContainer(min, max, alpha); + // DrawPauseContainer(min, max, alpha); drawList->PushClipRect({ min.x, min.y + Scale(20) }, { max.x, max.y - Scale(5) }); } @@ -101,7 +101,7 @@ static void DrawHeaderContainer(const char* text) ImVec2 min = { g_aspectRatioOffsetX + Scale(containerMarginX), g_aspectRatioOffsetY + Scale(136) }; ImVec2 max = { std::max(min.x + minTextSize, min.x + textMarginX * 2 + textSize.x + Scale(5)), g_aspectRatioOffsetY + Scale(196) }; - DrawPauseHeaderContainer(min, max, alpha); + // DrawPauseHeaderContainer(min, max, alpha); SetTextSkew((min.y + max.y) / 2.0f, Scale(3.0f)); @@ -139,8 +139,8 @@ static void DrawAchievement(int rowIndex, float yOffset, Achievement& achievemen auto icon = g_xdbfTextureCache[achievement.ID]; auto isSelected = rowIndex == g_selectedRowIndex; - if (isSelected) - DrawSelectionContainer(min, max); + // if (isSelected) + // DrawSelectionContainer(min, max); auto desc = isUnlocked ? achievement.UnlockedDesc.c_str() : achievement.LockedDesc.c_str(); auto fontSize = Scale(24); diff --git a/MarathonRecomp/ui/achievement_overlay.cpp b/MarathonRecomp/ui/achievement_overlay.cpp index d36afe80b..039b2ed30 100644 --- a/MarathonRecomp/ui/achievement_overlay.cpp +++ b/MarathonRecomp/ui/achievement_overlay.cpp @@ -62,7 +62,7 @@ static bool DrawContainer(ImVec2 min, ImVec2 max, float cornerRadius = 25) ? Hermite(1, 0, colourMotion) : Hermite(0, 1, colourMotion); - DrawPauseContainer(min, max, alpha); + // DrawPauseContainer(min, max, alpha); if (containerMotion >= 1.0f) { diff --git a/MarathonRecomp/ui/imgui_utils.cpp b/MarathonRecomp/ui/imgui_utils.cpp index 5931c5550..e012114a5 100644 --- a/MarathonRecomp/ui/imgui_utils.cpp +++ b/MarathonRecomp/ui/imgui_utils.cpp @@ -4,25 +4,28 @@ #include #include -#include +#include #include #include +#include #include #include -std::unique_ptr g_texGeneralWindow; +std::unique_ptr g_texWindow; std::unique_ptr g_texLight; std::unique_ptr g_texSelect; +std::unique_ptr g_texSelectArrow; std::unique_ptr g_texMainMenu1; std::unique_ptr g_texArrow; void InitImGuiUtils() { - g_texGeneralWindow = LOAD_ZSTD_TEXTURE(g_general_window); + g_texWindow = LOAD_ZSTD_TEXTURE(g_window); g_texLight = LOAD_ZSTD_TEXTURE(g_light); g_texSelect = LOAD_ZSTD_TEXTURE(g_select); + g_texSelectArrow = LOAD_ZSTD_TEXTURE(g_select_arrow); g_texMainMenu1 = LOAD_ZSTD_TEXTURE(g_main_menu1); - g_texArrow = LOAD_ZSTD_TEXTURE(arrow); + g_texArrow = LOAD_ZSTD_TEXTURE(g_arrow); } void SetGradient(const ImVec2& min, const ImVec2& max, ImU32 top, ImU32 bottom) @@ -171,43 +174,19 @@ float Scale(float size) return size * g_aspectRatioScale; } -static double ComputeLoopMotion(double time, double offset) +double ComputeLoopMotion(double time, double offset, double total) { - const double fadeInDuration = 0.1; - const double fadeOutDuration = 10.0; - - double elapsedTime = time - offset; - - if (elapsedTime < 0.0) { - return 0.0; - } - - double result; - - if (elapsedTime < fadeInDuration) { - double progress = elapsedTime / fadeInDuration; - result = 1.0 - pow(1.0 - progress, 2.0); - - } else if (elapsedTime < fadeInDuration + fadeOutDuration) { - double fadeOutTime = elapsedTime - fadeInDuration; - double progress = fadeOutTime / fadeOutDuration; - result = pow(1.0 - progress, 10.0); - - } else { - result = 0.0; - } - - return std::clamp(result, 0.0, 1.0); + return std::clamp(fmod((ImGui::GetTime() - time - (offset / 60.0)) / (total / 60.0), 1.0 + (total / 60.0)), 0.0, 1.0) / 1.0; } -double ComputeLinearMotion(double duration, double offset, double total) +double ComputeLinearMotion(double time, double offset, double total) { - return std::clamp((ImGui::GetTime() - duration - offset / 60.0) / total * 60.0, 0.0, 1.0); + return std::clamp((ImGui::GetTime() - time - offset / 60.0) / total * 60.0, 0.0, 1.0); } -double ComputeMotion(double duration, double offset, double total) +double ComputeMotion(double time, double offset, double total) { - return sqrt(ComputeLinearMotion(duration, offset, total)); + return sqrt(ComputeLinearMotion(time, offset, total)); } void DrawArrows(ImVec2 min, ImVec2 max) @@ -236,11 +215,42 @@ void DrawArrows(ImVec2 min, ImVec2 max) auto leftBaseY = min.y - (leftArrowSize / 2); auto leftEndY = min.y + (leftArrowSize / 2); + auto computeArrowLoopMotion = [&](double time, double offset) + { + const double fadeInDuration = 0.1; + const double fadeOutDuration = 10.0; + + double elapsedTime = time - offset; + + if (elapsedTime < 0.0) + return 0.0; + + double result; + + if (elapsedTime < fadeInDuration) + { + double progress = elapsedTime / fadeInDuration; + result = 1.0 - pow(1.0 - progress, 2.0); + } + else if (elapsedTime < fadeInDuration + fadeOutDuration) + { + double fadeOutTime = elapsedTime - fadeInDuration; + double progress = fadeOutTime / fadeOutDuration; + result = pow(1.0 - progress, 10.0); + } + else + { + result = 0.0; + } + + return std::clamp(result, 0.0, 1.0); + }; + for (uint32_t i = 0; i < leftArrows; i++) { auto baseX = min.x + (i * leftArrowOffset) - Scale(200) - totalAnimOffset; auto endX = baseX + leftArrowSize; - auto opacity = (uint32_t)((maxOpacity * ComputeLoopMotion(cycleTime, 1.0 + ((double)(leftArrows - i) / leftArrows))) * 255); + auto opacity = (uint32_t)((maxOpacity * computeArrowLoopMotion(cycleTime, 1.0 + ((double)(leftArrows - i) / leftArrows))) * 255); drawList->AddImage(g_texArrow.get(), { endX, leftBaseY }, { baseX, leftEndY }, GET_UV_COORDS(arrowUV), IM_COL32(255, 255, 255, opacity)); } @@ -252,7 +262,7 @@ void DrawArrows(ImVec2 min, ImVec2 max) { auto baseX = max.x - (i * rightArrowOffset) - rightArrowSize + Scale(20) + totalAnimOffset; auto endX = baseX + rightArrowSize; - auto opacity = (uint32_t)((maxOpacity * ComputeLoopMotion(cycleTime, ((double)(rightArrows - i) / rightArrows))) * 255); + auto opacity = (uint32_t)((maxOpacity * computeArrowLoopMotion(cycleTime, ((double)(rightArrows - i) / rightArrows))) * 255); drawList->AddImage(g_texArrow.get(), { baseX, rightBaseY }, { endX, rightEndY }, GET_UV_COORDS(arrowUV), IM_COL32(255, 255, 255, opacity)); } @@ -364,54 +374,6 @@ void DrawContainerBox(ImVec2 min, ImVec2 max, float alpha) ResetGradient(); } -void DrawPauseContainer(ImVec2 min, ImVec2 max, float alpha) -{ - auto drawList = ImGui::GetBackgroundDrawList(); - - auto commonWidth = Scale(35); - auto commonHeight = Scale(35); - auto bottomHeight = Scale(5); - - auto tl = PIXELS_TO_UV_COORDS(128, 512, 0, 0, 35, 35); - auto tc = PIXELS_TO_UV_COORDS(128, 512, 51, 0, 5, 35); - auto tr = PIXELS_TO_UV_COORDS(128, 512, 70, 0, 35, 35); - auto cl = PIXELS_TO_UV_COORDS(128, 512, 0, 35, 35, 235); - auto cc = PIXELS_TO_UV_COORDS(128, 512, 51, 35, 5, 235); - auto cr = PIXELS_TO_UV_COORDS(128, 512, 70, 35, 35, 235); - auto bl = PIXELS_TO_UV_COORDS(128, 512, 0, 270, 35, 40); - auto bc = PIXELS_TO_UV_COORDS(128, 512, 51, 270, 5, 40); - auto br = PIXELS_TO_UV_COORDS(128, 512, 70, 270, 35, 40); - - auto colour = IM_COL32(255, 255, 255, 255 * alpha); - - drawList->AddImage(g_texGeneralWindow.get(), min, { min.x + commonWidth, min.y + commonHeight }, GET_UV_COORDS(tl), colour); - drawList->AddImage(g_texGeneralWindow.get(), { min.x + commonWidth, min.y }, { max.x - commonWidth, min.y + commonHeight }, GET_UV_COORDS(tc), colour); - drawList->AddImage(g_texGeneralWindow.get(), { max.x - commonWidth, min.y }, { max.x, min.y + commonHeight }, GET_UV_COORDS(tr), colour); - drawList->AddImage(g_texGeneralWindow.get(), { min.x, min.y + commonHeight }, { min.x + commonWidth, max.y - commonHeight }, GET_UV_COORDS(cl), colour); - drawList->AddImage(g_texGeneralWindow.get(), { min.x + commonWidth, min.y + commonHeight }, { max.x - commonWidth, max.y - commonHeight }, GET_UV_COORDS(cc), colour); - drawList->AddImage(g_texGeneralWindow.get(), { max.x - commonWidth, min.y + commonHeight }, { max.x, max.y - commonHeight }, GET_UV_COORDS(cr), colour); - drawList->AddImage(g_texGeneralWindow.get(), { min.x, max.y - commonHeight }, { min.x + commonWidth, max.y + bottomHeight }, GET_UV_COORDS(bl), colour); - drawList->AddImage(g_texGeneralWindow.get(), { min.x + commonWidth, max.y - commonHeight }, { max.x - commonWidth, max.y + bottomHeight }, GET_UV_COORDS(bc), colour); - drawList->AddImage(g_texGeneralWindow.get(), { max.x - commonWidth, max.y - commonHeight }, { max.x, max.y + bottomHeight }, GET_UV_COORDS(br), colour); -} - -void DrawPauseHeaderContainer(ImVec2 min, ImVec2 max, float alpha) -{ - auto drawList = ImGui::GetBackgroundDrawList(); - - auto commonWidth = Scale(35); - - auto left = PIXELS_TO_UV_COORDS(128, 512, 0, 314, 35, 60); - auto centre = PIXELS_TO_UV_COORDS(128, 512, 51, 314, 5, 60); - auto right = PIXELS_TO_UV_COORDS(128, 512, 70, 314, 35, 60); - - auto colour = IM_COL32(255, 255, 255, 255 * alpha); - - drawList->AddImage(g_texGeneralWindow.get(), min, { min.x + commonWidth, max.y }, GET_UV_COORDS(left), colour); - drawList->AddImage(g_texGeneralWindow.get(), { min.x + commonWidth, min.y }, { max.x - commonWidth, max.y }, GET_UV_COORDS(centre), colour); - drawList->AddImage(g_texGeneralWindow.get(), { max.x - commonWidth, min.y }, max, GET_UV_COORDS(right), colour); -} - void DrawTextBasic(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 colour, const char* text) { auto drawList = ImGui::GetBackgroundDrawList(); @@ -429,46 +391,10 @@ void DrawTextWithMarquee(const ImFont* font, float fontSize, const ImVec2& posit drawList->PushClipRect(min, max, true); if (textX <= position.x) - { - DrawRubyAnnotatedText - ( - font, - fontSize, - FLT_MAX, - { textX, position.y }, - 0.0f, - text, - [=](const char* str, ImVec2 pos) - { - DrawTextBasic(font, fontSize, pos, color, str); - }, - [=](const char* str, float size, ImVec2 pos) - { - DrawTextBasic(font, size, pos, color, str); - } - ); - } + DrawTextBasic(font, fontSize, { textX, position.y }, color, text); if (textX + textSize.x < position.x) - { - DrawRubyAnnotatedText - ( - font, - fontSize, - FLT_MAX, - { textX + textSize.x + rectWidth, position.y }, - 0.0f, - text, - [=](const char* str, ImVec2 pos) - { - DrawTextBasic(font, fontSize, pos, color, str); - }, - [=](const char* str, float size, ImVec2 pos) - { - DrawTextBasic(font, size, pos, color, str); - } - ); - } + DrawTextBasic(font, fontSize, { textX + textSize.x + rectWidth, position.y }, color, text); drawList->PopClipRect(); } @@ -527,6 +453,34 @@ void DrawTextWithShadow(const ImFont* font, float fontSize, const ImVec2& pos, I drawList->AddText(font, fontSize, pos, colour, text, nullptr); } +void DrawTextParagraph(const ImFont* font, float fontSize, float maxWidth, const ImVec2& pos, float lineMargin, const char* text, std::function drawMethod, bool isCentred) +{ + auto lines = Split(text, font, fontSize, maxWidth); + auto paragraphSize = MeasureCentredParagraph(font, fontSize, lineMargin, lines); + auto offsetY = 0.0f; + + for (auto& line : lines) + { + auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, line.c_str()); + auto textX = pos.x; + auto textY = pos.y + offsetY; + + if (isCentred) + { + textX = line.starts_with("- ") + ? pos.x - paragraphSize.x / 2 + : pos.x - textSize.x / 2; + + textY = pos.y - paragraphSize.y / 2 + offsetY; + } + + drawMethod(line.c_str(), { textX, textY }); + + textX += textSize.x; + offsetY += textSize.y + Scale(lineMargin); + } +} + float CalcWidestTextSize(const ImFont* font, float fontSize, std::span strs) { auto result = 0.0f; @@ -563,86 +517,6 @@ std::string Truncate(const std::string& input, size_t maxLength, bool useEllipsi return input; } -std::pair> RemoveRubyAnnotations(const char* input) -{ - std::string output; - std::map rubyMap; - std::string currentMain, currentRuby; - size_t idx = 0; - - while (input[idx] != '\0') - { - if (input[idx] == '[') - { - idx++; - currentMain.clear(); - currentRuby.clear(); - - while (input[idx] != ':' && input[idx] != ']' && input[idx] != '\0') - { - currentMain += input[idx++]; - } - if (input[idx] == ':') - { - idx++; - while (input[idx] != ']' && input[idx] != '\0') - { - currentRuby += input[idx++]; - } - } - if (input[idx] == ']') - { - idx++; - } - - if (!currentMain.empty() && !currentRuby.empty()) - { - rubyMap[currentMain] = currentRuby; - } - - output += currentMain; - } - else - { - output += input[idx++]; - } - } - - return { output, rubyMap }; -} - -std::string ReAddRubyAnnotations(const std::string_view& wrappedText, const std::map& rubyMap) -{ - std::string annotatedText; - size_t idx = 0; - size_t length = wrappedText.length(); - - while (idx < length) { - bool matched = false; - for (const auto& [mainText, rubyText] : rubyMap) - { - if (wrappedText.substr(idx, mainText.length()) == mainText) - { - annotatedText += "["; - annotatedText += mainText; - annotatedText += ":"; - annotatedText += rubyText; - annotatedText += "]"; - - idx += mainText.length(); - matched = true; - break; - } - } - if (!matched) - { - annotatedText += wrappedText[idx++]; - } - } - - return annotatedText; -} - std::vector Split(const char* strStart, const ImFont* font, float fontSize, float maxWidth) { if (!strStart) @@ -757,102 +631,17 @@ std::vector Split(const char* strStart, const ImFont* font, float f return result; } -Paragraph CalculateAnnotatedParagraph(const std::vector& lines) -{ - Paragraph result; - - for (const auto& line : lines) - { - std::vector segments; - - size_t pos = 0; - size_t start = 0; - - while ((start = line.find('[', pos)) != std::string::npos) - { - size_t end = line.find(']', start); - if (end == std::string::npos) - break; - - size_t colon = line.find(':', start); - if (colon != std::string::npos && colon < end) - { - if (start != pos) - { - segments.push_back({ false, line.substr(pos, start - pos), "" }); - } - - segments.push_back({ true, line.substr(start + 1, colon - start - 1), line.substr(colon + 1, end - colon - 1) }); - - result.annotated = true; - pos = end + 1; - } - else - { - pos = start + 1; - } - } - - if (pos < line.size()) - { - segments.push_back({ false, line.substr(pos), "" }); - } - - result.lines.push_back(segments); - } - - return result; -} - -std::vector RemoveAnnotationFromParagraph(const std::vector& lines) -{ - std::vector result; - const auto paragraph = CalculateAnnotatedParagraph(lines); - - for (auto& annotatedLine : paragraph.lines) - { - std::string annotationRemovedLine = ""; - - for (const auto& segment : annotatedLine) - annotationRemovedLine += segment.text; - - result.push_back(annotationRemovedLine); - } - - return result; -} - -std::string RemoveAnnotationFromParagraphLine(const std::vector& annotatedLine) -{ - std::string result = ""; - - for (auto& segment : annotatedLine) - result += segment.text; - - return result; -} - ImVec2 MeasureCentredParagraph(const ImFont* font, float fontSize, float lineMargin, const std::vector& lines) { auto x = 0.0f; auto y = 0.0f; - const auto paragraph = CalculateAnnotatedParagraph(lines); - - std::vector annotationRemovedLines; - - for (const auto& line : paragraph.lines) - annotationRemovedLines.emplace_back(RemoveAnnotationFromParagraphLine(line)); - - for (size_t i = 0; i < annotationRemovedLines.size(); i++) + for (size_t i = 0; i < lines.size(); i++) { - auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, annotationRemovedLines[i].c_str()); + auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, lines[i].c_str()); x = std::max(x, textSize.x); y += textSize.y + Scale(lineMargin); - - if (paragraph.annotated && i != (annotationRemovedLines.size() - 1)) - y += fontSize * ANNOTATION_FONT_SIZE_MODIFIER; } return { x, y }; @@ -860,76 +649,7 @@ ImVec2 MeasureCentredParagraph(const ImFont* font, float fontSize, float lineMar ImVec2 MeasureCentredParagraph(const ImFont* font, float fontSize, float maxWidth, float lineMargin, const char* text) { - const auto input = RemoveRubyAnnotations(text); - auto lines = Split(input.first.c_str(), font, fontSize, maxWidth); - - for (auto& line : lines) - line = ReAddRubyAnnotations(line, input.second); - - return MeasureCentredParagraph(font, fontSize, lineMargin, lines); -} - -void DrawRubyAnnotatedText(const ImFont* font, float fontSize, float maxWidth, const ImVec2& pos, float lineMargin, const char* text, std::function drawMethod, std::function annotationDrawMethod, bool isCentred, bool leadingSpace) -{ - auto annotationFontSize = fontSize * ANNOTATION_FONT_SIZE_MODIFIER; - - const auto input = RemoveRubyAnnotations(text); - auto lines = Split(input.first.c_str(), font, fontSize, maxWidth); - - for (auto& line : lines) - { - line = ReAddRubyAnnotations(line, input.second); - if (!line.empty() && line.substr(0, 3) != "「" && leadingSpace) - { - line.insert(0, " "); - } - } - - auto paragraphSize = MeasureCentredParagraph(font, fontSize, lineMargin, lines); - auto offsetY = 0.0f; - - const auto paragraph = CalculateAnnotatedParagraph(lines); - - for (const auto& annotatedLine : paragraph.lines) - { - const auto annotationRemovedLine = RemoveAnnotationFromParagraphLine(annotatedLine); - - auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, annotationRemovedLine.c_str()); - auto annotationSize = font->CalcTextSizeA(annotationFontSize, FLT_MAX, 0, ""); - auto textX = pos.x; - auto textY = pos.y + offsetY; - - if (isCentred) - { - textX = annotationRemovedLine.starts_with("- ") - ? pos.x - paragraphSize.x / 2 - : pos.x - textSize.x / 2; - - textY = pos.y - paragraphSize.y / 2 + offsetY; - } - - for (const auto& segment : annotatedLine) - { - textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, segment.text.c_str()); - - if (segment.annotated) - { - annotationSize = font->CalcTextSizeA(annotationFontSize, FLT_MAX, 0, segment.annotation.c_str()); - float annotationX = textX + (textSize.x - annotationSize.x) / 2.0f; - - annotationDrawMethod(segment.annotation.c_str(), annotationFontSize, { annotationX, textY - annotationFontSize }); - } - - drawMethod(segment.text.c_str(), { textX, textY }); - - textX += textSize.x; - } - - offsetY += textSize.y + Scale(lineMargin); - - if (paragraph.annotated) - offsetY += annotationSize.y; - } + return MeasureCentredParagraph(font, fontSize, lineMargin, Split(text, font, fontSize, maxWidth)); } float Lerp(float a, float b, float t) @@ -977,50 +697,6 @@ void DrawVersionString(const ImFont* font, const ImU32 col) drawList->AddText(font, fontSize, { res.x - textSize.x - textMargin, res.y - textSize.y - textMargin }, col, g_versionString); } -void DrawSelectionContainer(ImVec2 min, ImVec2 max, bool fadeTop) -{ - auto drawList = ImGui::GetBackgroundDrawList(); - - static auto breatheStart = ImGui::GetTime(); - auto alpha = BREATHE_MOTION(1.0f, 0.55f, breatheStart, 0.92f); - auto colour = IM_COL32(255, 255, 255, 255 * alpha); - auto commonWidth = Scale(11); - auto commonHeight = Scale(24); - - if (fadeTop) - { - auto left = PIXELS_TO_UV_COORDS(64, 64, 0, 0, 11, 50); - auto centre = PIXELS_TO_UV_COORDS(64, 64, 11, 0, 8, 50); - auto right = PIXELS_TO_UV_COORDS(64, 64, 19, 0, 11, 50); - - drawList->AddImage(g_texSelect.get(), min, { min.x + commonWidth, max.y }, GET_UV_COORDS(left), colour); - drawList->AddImage(g_texSelect.get(), { min.x + commonWidth, min.y }, { max.x - commonWidth, max.y }, GET_UV_COORDS(centre), colour); - drawList->AddImage(g_texSelect.get(), { max.x - commonWidth, min.y }, max, GET_UV_COORDS(right), colour); - - return; - } - - auto tl = PIXELS_TO_UV_COORDS(64, 64, 34, 0, 11, 24); - auto tc = PIXELS_TO_UV_COORDS(64, 64, 45, 0, 8, 24); - auto tr = PIXELS_TO_UV_COORDS(64, 64, 53, 0, 11, 24); - auto cl = PIXELS_TO_UV_COORDS(64, 64, 34, 24, 11, 2); - auto cc = PIXELS_TO_UV_COORDS(64, 64, 45, 24, 8, 2); - auto cr = PIXELS_TO_UV_COORDS(64, 64, 53, 24, 11, 2); - auto bl = PIXELS_TO_UV_COORDS(64, 64, 34, 26, 11, 24); - auto bc = PIXELS_TO_UV_COORDS(64, 64, 45, 26, 8, 24); - auto br = PIXELS_TO_UV_COORDS(64, 64, 53, 26, 11, 24); - - drawList->AddImage(g_texSelect.get(), min, { min.x + commonWidth, min.y + commonHeight }, GET_UV_COORDS(tl), colour); - drawList->AddImage(g_texSelect.get(), { min.x + commonWidth, min.y }, { max.x - commonWidth, min.y + commonHeight }, GET_UV_COORDS(tc), colour); - drawList->AddImage(g_texSelect.get(), { max.x - commonWidth, min.y }, { max.x, min.y + commonHeight }, GET_UV_COORDS(tr), colour); - drawList->AddImage(g_texSelect.get(), { min.x, min.y + commonHeight }, { min.x + commonWidth, max.y - commonHeight }, GET_UV_COORDS(cl), colour); - drawList->AddImage(g_texSelect.get(), { min.x + commonWidth, min.y + commonHeight }, { max.x - commonWidth, max.y - commonHeight }, GET_UV_COORDS(cc), colour); - drawList->AddImage(g_texSelect.get(), { max.x - commonWidth, min.y + commonHeight }, { max.x, max.y - commonHeight }, GET_UV_COORDS(cr), colour); - drawList->AddImage(g_texSelect.get(), { min.x, max.y - commonHeight }, { min.x + commonWidth, max.y }, GET_UV_COORDS(bl), colour); - drawList->AddImage(g_texSelect.get(), { min.x + commonWidth, max.y - commonHeight }, { max.x - commonWidth, max.y }, GET_UV_COORDS(bc), colour); - drawList->AddImage(g_texSelect.get(), { max.x - commonWidth, max.y - commonHeight }, { max.x, max.y }, GET_UV_COORDS(br), colour); -} - void DrawToggleLight(ImVec2 pos, bool isEnabled, float alpha) { auto drawList = ImGui::GetBackgroundDrawList(); diff --git a/MarathonRecomp/ui/imgui_utils.h b/MarathonRecomp/ui/imgui_utils.h index b6a34f6d7..4cd0a4812 100644 --- a/MarathonRecomp/ui/imgui_utils.h +++ b/MarathonRecomp/ui/imgui_utils.h @@ -14,22 +14,10 @@ #define BREATHE_MOTION(start, end, time, rate) Lerp(start, end, (sin((ImGui::GetTime() - time) * (2.0f * M_PI / rate)) + 1.0f) / 2.0f) -constexpr float ANNOTATION_FONT_SIZE_MODIFIER = 0.6f; - -extern std::unique_ptr g_texGeneralWindow; +extern std::unique_ptr g_texWindow; extern std::unique_ptr g_texLight; extern std::unique_ptr g_texSelect; - -struct TextSegment { - bool annotated; - std::string text; - std::string annotation; -}; - -struct Paragraph { - bool annotated = false; - std::vector> lines; -}; +extern std::unique_ptr g_texSelectArrow; void InitImGuiUtils(); @@ -54,35 +42,28 @@ void ResetProceduralOrigin(); void SetAdditive(bool enabled); void ResetAdditive(); float Scale(float size); -double ComputeLinearMotion(double duration, double offset, double total); -double ComputeMotion(double duration, double offset, double total); +double ComputeLoopMotion(double time, double offset, double total); +double ComputeLinearMotion(double time, double offset, double total); +double ComputeMotion(double time, double offset, double total); void DrawArrows(ImVec2 min, ImVec2 max); void DrawHUD(ImVec2 min, ImVec2 max, const ImFont* font, const char* text); void DrawContainerBox(ImVec2 min, ImVec2 max, float alpha = 1); -void DrawPauseContainer(ImVec2 min, ImVec2 max, float alpha = 1); void DrawTextBasic(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 colour, const char* text); -void DrawPauseHeaderContainer(ImVec2 min, ImVec2 max, float alpha = 1); void DrawTextWithMarquee(const ImFont* font, float fontSize, const ImVec2& position, const ImVec2& min, const ImVec2& max, ImU32 color, const char* text, double time, double delay, double speed); void DrawTextWithMarqueeShadow(const ImFont* font, float fontSize, const ImVec2& pos, const ImVec2& min, const ImVec2& max, ImU32 colour, const char* text, double time, double delay, double speed, float offset = 2.0f, float radius = 1.0f, ImU32 shadowColour = IM_COL32(0, 0, 0, 255)); void DrawTextWithOutline(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 color, const char* text, float outlineSize, ImU32 outlineColor, uint32_t shaderModifier = IMGUI_SHADER_MODIFIER_NONE); void DrawTextWithShadow(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 colour, const char* text, float offset = 2.0f, float radius = 1.0f, ImU32 shadowColour = IM_COL32(0, 0, 0, 255)); +void DrawTextParagraph(const ImFont* font, float fontSize, float maxWidth, const ImVec2& pos, float lineMargin, const char* text, std::function drawMethod, bool isCentred = false); float CalcWidestTextSize(const ImFont* font, float fontSize, std::span strs); std::string Truncate(const std::string& input, size_t maxLength, bool useEllipsis = true, bool usePrefixEllipsis = false); -std::pair> RemoveRubyAnnotations(const char* input); -std::string ReAddRubyAnnotations(const std::string_view& wrappedText, const std::map& rubyMap); std::vector Split(const char* strStart, const ImFont* font, float fontSize, float maxWidth); -Paragraph CalculateAnnotatedParagraph(const std::vector& lines); -std::vector RemoveAnnotationFromParagraph(const std::vector& lines); -std::string RemoveAnnotationFromParagraphLine(const std::vector& annotatedLine); ImVec2 MeasureCentredParagraph(const ImFont* font, float fontSize, float lineMargin, const std::vector& lines); ImVec2 MeasureCentredParagraph(const ImFont* font, float fontSize, float maxWidth, float lineMargin, const char* text); -void DrawRubyAnnotatedText(const ImFont* font, float fontSize, float maxWidth, const ImVec2& pos, float lineMargin, const char* text, std::function drawMethod, std::function annotationDrawMethod, bool isCentred = false, bool leadingSpace = false); float Lerp(float a, float b, float t); float Cubic(float a, float b, float t); float Hermite(float a, float b, float t); ImVec2 Lerp(const ImVec2& a, const ImVec2& b, float t); ImU32 ColourLerp(ImU32 c0, ImU32 c1, float t); void DrawVersionString(const ImFont* font, const ImU32 col = IM_COL32(255, 255, 255, 70)); -void DrawSelectionContainer(ImVec2 min, ImVec2 max, bool fadeTop = false); void DrawToggleLight(ImVec2 pos, bool isEnabled, float alpha = 1.0f); const char* CalcWordWrapPositionA(const ImFont* font, float scale, const char* text, const char* text_end, float wrap_width); diff --git a/MarathonRecomp/ui/installer_wizard.cpp b/MarathonRecomp/ui/installer_wizard.cpp index 18c5e9e79..5434f2c5e 100644 --- a/MarathonRecomp/ui/installer_wizard.cpp +++ b/MarathonRecomp/ui/installer_wizard.cpp @@ -511,7 +511,6 @@ static void DrawDescriptionContainer() auto &res = ImGui::GetIO().DisplaySize; auto drawList = ImGui::GetBackgroundDrawList(); auto fontSize = Scale(25.0f); - auto annotationFontSize = fontSize * ANNOTATION_FONT_SIZE_MODIFIER; ImVec2 descriptionMin = { round(g_aspectRatioOffsetX + Scale(CONTAINER_X + 0.5f)), round(g_aspectRatioOffsetY + Scale(CONTAINER_Y + 0.5f)) }; ImVec2 descriptionMax = { round(g_aspectRatioOffsetX + Scale(CONTAINER_X + 0.5f + CONTAINER_WIDTH)), round(g_aspectRatioOffsetY + Scale(CONTAINER_Y + 0.5f + CONTAINER_HEIGHT)) }; @@ -559,29 +558,9 @@ static void DrawDescriptionContainer() float lineMargin = 5.0f; - if (Config::Language == ELanguage::Japanese) - { - lineMargin = 5.5f; - - // Removing some padding of the applied due to the inclusion of annotation for Japanese - textX -= (fontSize + Scale(1.5f)); - textY -= Scale(7.0f); - - // The annotation (and thus the Japanese) can be drawn above the edges of the info panel thus the clip needs to be extended a bit - clipRectMin.x -= annotationFontSize; - clipRectMin.y -= annotationFontSize; - clipRectMax.x += annotationFontSize; - clipRectMax.y += annotationFontSize; - - textX += annotationFontSize; - textY += annotationFontSize; - - lineWidth += annotationFontSize; - } - drawList->PushClipRect(clipRectMin, clipRectMax, false); - DrawRubyAnnotatedText + DrawTextParagraph ( g_rodinFont, fontSize, @@ -589,16 +568,11 @@ static void DrawDescriptionContainer() { textX, textY }, lineMargin, descriptionText, + [=](const char* str, ImVec2 pos) { DrawTextBasic(g_rodinFont, fontSize, pos, IM_COL32(255, 255, 255, 255 * textAlpha), str); - }, - [=](const char* str, float size, ImVec2 pos) - { - DrawTextBasic(g_rodinFont, size, pos, IM_COL32(255, 255, 255, 255 * textAlpha), str); - }, - false, - Config::Language == ELanguage::Japanese + } ); drawList->PopClipRect(); diff --git a/MarathonRecomp/ui/message_window.cpp b/MarathonRecomp/ui/message_window.cpp index 3f602eeb5..6fa133913 100644 --- a/MarathonRecomp/ui/message_window.cpp +++ b/MarathonRecomp/ui/message_window.cpp @@ -1,49 +1,38 @@ #include "message_window.h" #include +#include #include #include #include #include -#include #include #include #include #include #include -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 = 4; +static bool g_isAwaitingResult{}; +static bool g_isClosing{}; -static bool g_isAwaitingResult = false; -static bool g_isClosing = false; -static bool g_isControlsVisible = false; +static int g_selectedRowIndex{}; +static int g_prevSelectedRowIndex{}; -static double g_rowSelectionTime; -static int g_selectedRowIndex; -static int g_prevSelectedRowIndex; -static int g_foregroundCount; - -static bool g_upWasHeld; -static bool g_downWasHeld; +static bool g_upWasHeld{}; +static bool g_downWasHeld{}; static ImVec2 g_joypadAxis = {}; -static bool g_isAccepted; -static bool g_isDeclined; +static bool g_isAccepted{}; +static bool g_isDeclined{}; -static double g_appearTime; -static double g_controlsAppearTime; +static double g_time{}; -static ImFont* g_rodinFont; +static ImFont* g_rodinFont{}; -std::string g_text; -int g_result; -static std::vector g_buttons; -int g_defaultButtonIndex; -int g_cancelButtonIndex; +static std::string g_text{}; +static int g_result{}; +static std::vector g_buttons{}; +static int g_defaultButtonIndex{}; +static int g_cancelButtonIndex{}; class SDLEventListenerForMessageWindow : public SDLEventListener { @@ -65,11 +54,11 @@ class SDLEventListenerForMessageWindow : public SDLEventListener switch (event->key.keysym.scancode) { case SDL_SCANCODE_UP: - g_joypadAxis.y = -1.0f; + g_joypadAxis.y = 1.0f; break; case SDL_SCANCODE_DOWN: - g_joypadAxis.y = 1.0f; + g_joypadAxis.y = -1.0f; break; case SDL_SCANCODE_RETURN: @@ -92,7 +81,7 @@ class SDLEventListenerForMessageWindow : public SDLEventListener break; // Only accept mouse buttons when an item is selected. - if (g_isControlsVisible && g_selectedRowIndex == -1) + if (g_selectedRowIndex == -1) break; g_isAccepted = true; @@ -105,11 +94,11 @@ class SDLEventListenerForMessageWindow : public SDLEventListener switch (event->cbutton.button) { case SDL_CONTROLLER_BUTTON_DPAD_UP: - g_joypadAxis = { 0.0f, -1.0f }; + g_joypadAxis = { 0.0f, 1.0f }; break; case SDL_CONTROLLER_BUTTON_DPAD_DOWN: - g_joypadAxis = { 0.0f, 1.0f }; + g_joypadAxis = { 0.0f, -1.0f }; break; case SDL_CONTROLLER_BUTTON_A: @@ -128,7 +117,7 @@ class SDLEventListenerForMessageWindow : public SDLEventListener { if (event->caxis.axis < 2) { - float newAxisValue = event->caxis.value / axisValueRange; + float newAxisValue = -(event->caxis.value / axisValueRange); bool sameDirection = (newAxisValue * g_joypadAxis[event->caxis.axis]) > 0.0f; bool wasInRange = abs(g_joypadAxis[event->caxis.axis]) > axisTapRange; bool isInRange = abs(newAxisValue) > axisTapRange; @@ -148,79 +137,174 @@ class SDLEventListenerForMessageWindow : public SDLEventListener } g_sdlEventListenerForMessageWindow; -bool DrawContainer(float appearTime, ImVec2 centre, ImVec2 max, bool isForeground = true) +void DrawContainerArrow(const ImVec2 pos, float scale, float rotation, uint32_t colour) { - auto drawList = ImGui::GetBackgroundDrawList(); + auto arrowRadius = Scale(63.0f * scale); - ImVec2 _min = { centre.x - max.x, centre.y - max.y }; - ImVec2 _max = { centre.x + max.x, centre.y + max.y }; + std::array vertices = + { + pos, // Top Left + { pos.x + arrowRadius, pos.y }, // Top Right + { pos.x + arrowRadius, pos.y + arrowRadius }, // Bottom Right + { pos.x, pos.y + arrowRadius } // Bottom Left + }; - // Expand/retract animation. - auto containerMotion = ComputeMotion(appearTime, OVERLAY_CONTAINER_COMMON_MOTION_START, OVERLAY_CONTAINER_COMMON_MOTION_END); + // Adjust base rotation, since the texture + // points the arrow to the bottom left. + auto adjRotation = rotation + 90.0f; - if (g_isClosing) - { - _min.x = Hermite(_min.x, centre.x, containerMotion); - _max.x = Hermite(_max.x, centre.x, containerMotion); - _min.y = Hermite(_min.y, centre.y, containerMotion); - _max.y = Hermite(_max.y, centre.y, containerMotion); - } - else + auto radians = adjRotation * (IM_PI / 180.0f); + auto c = cosf(radians); + auto s = sinf(radians); + + auto& pivot = vertices[3]; + + // Rotate around bottom left. + for (auto& v : vertices) { - _min.x = Hermite(centre.x, _min.x, containerMotion); - _max.x = Hermite(centre.x, _max.x, containerMotion); - _min.y = Hermite(centre.y, _min.y, containerMotion); - _max.y = Hermite(centre.y, _max.y, containerMotion); + float dx = v.x - pivot.x; + float dy = v.y - pivot.y; + + v.x = pivot.x + dx * c - dy * s; + v.y = pivot.y + dx * s + dy * c; } - // Transparency fade animation. - auto colourMotion = g_isClosing - ? ComputeMotion(appearTime, OVERLAY_CONTAINER_OUTRO_FADE_START, OVERLAY_CONTAINER_OUTRO_FADE_END) - : ComputeMotion(appearTime, OVERLAY_CONTAINER_INTRO_FADE_START, OVERLAY_CONTAINER_INTRO_FADE_END); + // Adjust height to pivot. + for (auto& v : vertices) + v.y -= arrowRadius; + + auto drawList = ImGui::GetBackgroundDrawList(); + auto arrowUVs = PIXELS_TO_UV_COORDS(128, 128, 65, 0, 63, 63); + + auto& uvMin = std::get<0>(arrowUVs); + auto& uvMax = std::get<1>(arrowUVs); + + drawList->AddImageQuad(g_texWindow.get(), vertices[0], vertices[1], vertices[2], vertices[3], uvMin, { uvMax.x, uvMin.y }, { uvMax.x, uvMax.y }, { uvMin.x, uvMax.y }, colour); +} + +void DrawContainer(const ImVec2 min, const ImVec2 max) +{ + auto drawList = ImGui::GetBackgroundDrawList(); + + constexpr auto containerTopColour = IM_COL32(20, 56, 130, 200); + constexpr auto containerBottomColour = IM_COL32(8, 22, 51, 200); + + drawList->AddRectFilledMultiColor(min, max, containerTopColour, containerTopColour, containerBottomColour, containerBottomColour); + + auto lineHorzUVs = PIXELS_TO_UV_COORDS(128, 128, 2, 0, 60, 5); + auto lineVertUVs = PIXELS_TO_UV_COORDS(128, 128, 0, 66, 5, 60); + + auto lineScale = Scale(1); + auto lineOffsetRight = Scale(3); + + // Top + drawList->AddImage(g_texWindow.get(), min, { max.x, min.y + lineScale }, GET_UV_COORDS(lineHorzUVs)); + + // Bottom + drawList->AddImage(g_texWindow.get(), { min.x, max.y - lineOffsetRight }, { max.x, (max.y - lineOffsetRight) + lineScale }, GET_UV_COORDS(lineHorzUVs)); + + // Left + drawList->AddImage(g_texWindow.get(), min, { min.x + lineScale, max.y }, GET_UV_COORDS(lineVertUVs)); + + // Right + drawList->AddImage(g_texWindow.get(), { max.x - lineOffsetRight, min.y }, { (max.x - lineOffsetRight) + lineScale, max.y }, GET_UV_COORDS(lineVertUVs)); + + SetAdditive(true); + + constexpr auto arrowPixelRadius = 63.0f; + constexpr auto arrowInnerScale = 0.16f; + constexpr auto arrowOuterScale = 0.225f; + constexpr auto arrowOuterColour = IM_COL32(255, 255, 255, 45); + + auto arrowOuterOffset = Scale(arrowPixelRadius * arrowOuterScale) / 2; - auto alpha = g_isClosing - ? Lerp(1, 0, colourMotion) - : Lerp(0, 1, colourMotion); + // Top Left (Inner) + DrawContainerArrow(min, arrowInnerScale, 0.0f, containerTopColour); - if (!isForeground) - g_foregroundCount++; + // Top Right (Inner) + DrawContainerArrow({ max.x, min.y }, arrowInnerScale, 90.0f, containerTopColour); - if (isForeground) - drawList->AddRectFilled({ 0.0f, 0.0f }, ImGui::GetIO().DisplaySize, IM_COL32(0, 0, 0, 190 * (g_foregroundCount ? 1 : alpha))); + // Bottom Right (Inner) + DrawContainerArrow(max, arrowInnerScale, 180.0f, containerTopColour); - DrawPauseContainer(_min, _max, alpha); + // Bottom Left (Inner) + DrawContainerArrow({ min.x, max.y }, arrowInnerScale, 270.0f, containerTopColour); - if (containerMotion >= 1.0f && !g_isClosing) + // Top Left (Outer) + DrawContainerArrow({ min.x - arrowOuterOffset, min.y - arrowOuterOffset }, arrowOuterScale, 0.0f, arrowOuterColour); + + // Top Right (Outer) + DrawContainerArrow({ max.x + arrowOuterOffset, min.y - arrowOuterOffset }, arrowOuterScale, 90.0f, arrowOuterColour); + + // Bottom Right (Outer) + DrawContainerArrow({ max.x + arrowOuterOffset, max.y + arrowOuterOffset }, arrowOuterScale, 180.0f, arrowOuterColour); + + // Bottom Left (Outer) + DrawContainerArrow({ min.x - arrowOuterOffset, max.y + arrowOuterOffset }, arrowOuterScale, 270.0f, arrowOuterColour); + + ResetAdditive(); + + drawList->PushClipRect(min, max); +} + +void DrawButtonArrow(const ImVec2 pos) +{ + auto drawList = ImGui::GetBackgroundDrawList(); + + auto arrowUVs = PIXELS_TO_UV_COORDS(50, 50, 0, 0, 27, 50); + auto arrowScaleX = Scale(14); + auto arrowScaleY = Scale(25); + auto arrowOffset = Scale(8); + + for (int i = 0; i < 3; i++) { - drawList->PushClipRect(_min, _max); - return true; - } + auto arrowRight = (arrowOffset * 3) - (arrowOffset * i); + auto arrowAlphaMotionIn = ComputeLoopMotion(g_time, 3.0 * i, 12.0); + auto arrowAlphaMotionOut = ComputeLoopMotion(g_time, 3.0 * (i + 1), 12.0); + + // horrible + auto arrowAlphaMotion = arrowAlphaMotionIn >= 1.0 + ? arrowAlphaMotionOut >= 1.0 + ? arrowAlphaMotionIn + : arrowAlphaMotionOut + : arrowAlphaMotionIn; + + auto arrowAlpha = (int)Lerp(50 * (i + 1), 255, arrowAlphaMotion); - return false; + drawList->AddImage + ( + g_texSelectArrow.get(), + { pos.x + arrowRight, pos.y }, + { pos.x + arrowRight + arrowScaleX, pos.y + arrowScaleY }, + GET_UV_COORDS(arrowUVs), + IM_COL32(255, 255, 255, arrowAlpha) + ); + } } -void DrawButton(int rowIndex, float yOffset, float width, float height, std::string& text) +void DrawButton(int rowIndex, float yOffset, float yPadding, float width, float height, std::string& text) { auto drawList = ImGui::GetBackgroundDrawList(); auto clipRectMin = drawList->GetClipRectMin(); auto clipRectMax = drawList->GetClipRectMax(); - ImVec2 min = { clipRectMin.x + ((clipRectMax.x - clipRectMin.x) - width) / 2, clipRectMin.y + height * rowIndex + yOffset }; + ImVec2 min = { clipRectMin.x + ((clipRectMax.x - clipRectMin.x) - width) / 2, clipRectMin.y + (height * rowIndex) + yOffset + (yPadding * rowIndex) }; ImVec2 max = { min.x + width, min.y + height }; - bool isSelected = rowIndex == g_selectedRowIndex; + auto textColour = IM_COL32_WHITE; - if (isSelected) + if (rowIndex == g_selectedRowIndex) { - auto prevItemOffset = (g_prevSelectedRowIndex - g_selectedRowIndex) * height; - auto animRatio = std::clamp((ImGui::GetTime() - g_rowSelectionTime) * 60.0 / 8.0, 0.0, 1.0); - prevItemOffset *= pow(1.0 - animRatio, 3.0); + auto gb = 255 * BREATHE_MOTION(1.0f, 0.0f, g_time, (g_isClosing ? 0.1f : 0.9f)); + + textColour = IM_COL32(255, gb, gb, 255); - DrawSelectionContainer({ min.x, min.y + prevItemOffset }, { max.x, max.y + prevItemOffset }, true); + if (!g_isClosing) + DrawButtonArrow(min); } - auto fontSize = Scale(28); + auto fontSize = Scale(27); auto textSize = g_rodinFont->CalcTextSizeA(fontSize, FLT_MAX, 0, text.c_str()); // Show low quality text in-game. @@ -232,7 +316,7 @@ void DrawButton(int rowIndex, float yOffset, float width, float height, std::str g_rodinFont, fontSize, { /* X */ min.x + ((max.x - min.x) - textSize.x) / 2, /* Y */ min.y + ((max.y - min.y) - textSize.y) / 2 }, - isSelected ? IM_COL32(255, 128, 0, 255) : IM_COL32(255, 255, 255, 255), + textColour, text.c_str() ); @@ -241,26 +325,6 @@ void DrawButton(int rowIndex, float yOffset, float width, float height, std::str SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); } -void DrawNextButtonGuide(bool isController, bool isKeyboard) -{ - auto icon = isController - ? EButtonIcon::A - : isKeyboard - ? EButtonIcon::Enter - : EButtonIcon::LMB; - - auto fontQuality = EFontQuality::High; - - // Always show controller prompt and low quality text in-game. - if (App::s_isInit) - { - icon = EButtonIcon::A; - fontQuality = EFontQuality::Low; - } - - ButtonGuide::Open(Button("Common_Next", FLT_MAX, icon, fontQuality)); -} - static void ResetSelection() { /* Always use -1 for mouse input to prevent the selection @@ -278,8 +342,6 @@ static void ResetSelection() void MessageWindow::Init() { - auto& io = ImGui::GetIO(); - g_rodinFont = ImFontAtlasSnapshot::GetFont("FOT-RodinPro-DB.otf"); } @@ -288,40 +350,16 @@ void MessageWindow::Draw() if (!s_isVisible) return; - auto drawList = ImGui::GetBackgroundDrawList(); - auto& res = ImGui::GetIO().DisplaySize; - - ImVec2 centre = { res.x / 2, res.y / 2 }; - - auto maxWidth = Scale(820); - auto fontSize = Scale(28); - - const auto input = RemoveRubyAnnotations(g_text.c_str()); - auto lines = Split(input.first.c_str(), g_rodinFont, fontSize, maxWidth); - - for (auto& line : lines) + if (g_isClosing && (ImGui::GetTime() - g_time) > (1.0 / 60.0) * 30.0) { - line = ReAddRubyAnnotations(line, input.second); + g_isAwaitingResult = false; + s_isVisible = false; + return; } - auto lineMargin = Config::Language != ELanguage::Japanese ? 5.0f : 5.5f; - auto textSize = MeasureCentredParagraph(g_rodinFont, fontSize, lineMargin, lines); - auto textMarginX = Scale(37); - auto textMarginY = Scale(45); - - auto textX = centre.x; - auto textY = centre.y + Scale(3); - - if (Config::Language == ELanguage::Japanese) - { - textMarginX -= Scale(2.5f); - textMarginY -= Scale(2.0f); - - textY += Scale(lines.size() % 2 == 0 ? 1.5f : 8.0f); - } - - bool isController = hid::IsInputDeviceController(); - bool isKeyboard = hid::g_inputDevice == hid::EInputDevice::Keyboard; + auto drawList = ImGui::GetBackgroundDrawList(); + auto isController = hid::IsInputDeviceController(); + auto isKeyboard = hid::g_inputDevice == hid::EInputDevice::Keyboard; // Handle controller input when the game is booted. if (App::s_isInit) @@ -329,222 +367,149 @@ void MessageWindow::Draw() // Always assume keyboard to prevent mouse from blocking control in-game. isKeyboard = true; -// if (auto pInputState = SWA::CInputState::GetInstance()) -// { -// auto& rPadState = pInputState->GetPadState(); -// -// g_joypadAxis.y = rPadState.LeftStickVertical; -// -// if (rPadState.IsTapped(SWA::eKeyState_DpadUp)) -// g_joypadAxis.y = -1.0f; -// -// if (rPadState.IsTapped(SWA::eKeyState_DpadDown)) -// g_joypadAxis.y = 1.0f; -// -// g_isAccepted = rPadState.IsTapped(SWA::eKeyState_A); -// g_isDeclined = rPadState.IsTapped(SWA::eKeyState_B); -// -// if (isKeyboard) -// g_isAccepted = g_isAccepted || rPadState.IsTapped(SWA::eKeyState_Start); -// } - } - - if (DrawContainer(g_appearTime, centre, { textSize.x / 2 + textMarginX, textSize.y / 2 + textMarginY }, !g_isControlsVisible)) - { - // Use low quality text when the game is booted to not clash with existing UI. - if (App::s_isInit) - SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); - - DrawRubyAnnotatedText - ( - g_rodinFont, - fontSize, - maxWidth, - { textX, textY }, - lineMargin, - g_text.c_str(), - - [=](const char* str, ImVec2 pos) - { - DrawTextBasic(g_rodinFont, fontSize, pos, IM_COL32(255, 255, 255, 255), str); - }, - [=](const char* str, float size, ImVec2 pos) - { - DrawTextBasic(g_rodinFont, size, pos, IM_COL32(255, 255, 255, 255), str); - }, - - true - ); - - // Reset the shader modifier. - if (App::s_isInit) - SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); + if (auto& spInputManager = App::s_pApp->m_pDoc->m_vspInputManager[0]) + { + auto& rPadState = spInputManager->m_PadState; - drawList->PopClipRect(); + g_joypadAxis.y = -rPadState.LeftStickVertical; - if (g_buttons.size()) - { - auto itemWidth = std::max(Scale(162), Scale(CalcWidestTextSize(g_rodinFont, fontSize, g_buttons))); - auto itemHeight = Scale(57); - auto windowMarginX = Scale(23); - auto windowMarginY = Scale(30); + if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_DpadUp)) + g_joypadAxis.y = 1.0f; - ImVec2 controlsMax = { /* X */ itemWidth / 2 + windowMarginX, /* Y */ itemHeight / 2 * g_buttons.size() + windowMarginY }; + if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_DpadDown)) + g_joypadAxis.y = -1.0f; - if (g_isControlsVisible && DrawContainer(g_controlsAppearTime, centre, controlsMax)) - { - auto rowCount = 0; + g_isAccepted = rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_A); + g_isDeclined = rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_B); - for (auto& button : g_buttons) - DrawButton(rowCount++, windowMarginY, itemWidth, itemHeight, button); + if (isKeyboard) + g_isAccepted = g_isAccepted || rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_Start); + } + } - if (isController || isKeyboard) - { - bool upIsHeld = g_joypadAxis.y > 0.5f; - bool downIsHeld = g_joypadAxis.y < -0.5f; - - bool scrollUp = !g_upWasHeld && upIsHeld; - bool scrollDown = !g_downWasHeld && downIsHeld; - - auto prevSelectedRowIndex = g_selectedRowIndex; - - if (scrollUp) - { - --g_selectedRowIndex; - if (g_selectedRowIndex < 0) - g_selectedRowIndex = rowCount - 1; - } - else if (scrollDown) - { - ++g_selectedRowIndex; - if (g_selectedRowIndex >= rowCount) - g_selectedRowIndex = 0; - } - - if (scrollUp || scrollDown) - { - Game_PlaySound("move"); - g_rowSelectionTime = ImGui::GetTime(); - g_prevSelectedRowIndex = prevSelectedRowIndex; - g_joypadAxis.y = 0; - } - - g_upWasHeld = upIsHeld; - g_downWasHeld = downIsHeld; - - auto selectIcon = EButtonIcon::A; - auto backIcon = EButtonIcon::B; - - if (isController || isKeyboard) - { - if (isKeyboard && !App::s_isInit) - { - // Only display keyboard prompt during installer. - selectIcon = EButtonIcon::Enter; - backIcon = EButtonIcon::Escape; - } - - auto fontQuality = EFontQuality::High; - if (App::s_isInit) - { - // Show low quality text in-game. - fontQuality = EFontQuality::Low; - } - - std::array buttons = - { - Button("Common_Select", 115.0f, selectIcon, fontQuality), - Button("Common_Back", FLT_MAX, backIcon, fontQuality), - }; - - ButtonGuide::Open(buttons); - } - - if (g_isDeclined) - { - g_result = g_cancelButtonIndex; - - Game_PlaySound("cursor2"); - MessageWindow::Close(); - } - } - else if (!App::s_isInit) // Only accept mouse input during installer. - { - auto clipRectMin = drawList->GetClipRectMin(); - auto clipRectMax = drawList->GetClipRectMax(); + ImVec2 msgMin = { g_aspectRatioOffsetX + Scale(96), Scale(96) }; + ImVec2 msgMax = { msgMin.x + Scale(1088), msgMin.y + Scale(384) }; + ImVec2 msgCentre = { (msgMin.x / 2) + (msgMax.x / 2), (msgMin.y / 2) + (msgMax.y / 2) }; - ImVec2 listMin = { clipRectMin.x + windowMarginX, clipRectMin.y + windowMarginY }; - ImVec2 listMax = { clipRectMax.x - windowMarginX, clipRectMin.y + windowMarginY + itemHeight * rowCount }; + DrawContainer(msgMin, msgMax); - // Invalidate index if the mouse cursor is outside of the list box. - if (!ImGui::IsMouseHoveringRect(listMin, listMax, false)) - g_selectedRowIndex = -1; + // Use low quality text when the game is booted to not clash with existing UI. + if (App::s_isInit) + SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); - for (int i = 0; i < rowCount; i++) - { - ImVec2 itemMin = { listMin.x, listMin.y + itemHeight * i }; - ImVec2 itemMax = { listMax.x, clipRectMin.y + windowMarginY + itemHeight * i + itemHeight }; + auto fontSize = Scale(27); + auto textSize = g_rodinFont->CalcTextSizeA(fontSize, FLT_MAX, 0, g_text.c_str()); - if (ImGui::IsMouseHoveringRect(itemMin, itemMax, false)) - { - if (g_selectedRowIndex != i) - Game_PlaySound("move"); + DrawTextBasic(g_rodinFont, fontSize, { msgCentre.x - textSize.x / 2, msgCentre.y - textSize.y / 2 }, IM_COL32_WHITE, g_text.c_str()); - g_selectedRowIndex = i; + // Reset the shader modifier. + if (App::s_isInit) + SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); - break; - } - } + drawList->PopClipRect(); - std::array buttons = - { - Button("Common_Select", 115.0f, EButtonIcon::LMB), - Button("Common_Back", FLT_MAX, EButtonIcon::Escape), - }; + ImVec2 selMin = { msgMin.x, msgMax.y + (msgMin.y / 2) }; + ImVec2 selMax = { msgMax.x, selMin.y + Scale(128) }; - ButtonGuide::Open(buttons); - } + DrawContainer(selMin, selMax); - if (g_selectedRowIndex != -1 && g_isAccepted) - { - g_result = g_selectedRowIndex; + auto rowCount = 0; + auto windowMarginX = Scale(36); + auto itemWidth = msgMax.x - msgMin.x - windowMarginX; + auto itemHeight = Scale(25); + auto itemPadding = Scale(18); + auto windowMarginY = ((selMax.y - selMin.y) / 2) - (((itemHeight + itemPadding) / 2) * g_buttons.size()); - Game_PlaySound("main_deside"); - MessageWindow::Close(); - } + for (auto& button : g_buttons) + DrawButton(rowCount++, windowMarginY, itemPadding, itemWidth, itemHeight, button); - drawList->PopClipRect(); + if (isController || isKeyboard) + { + auto upIsHeld = g_joypadAxis.y > 0.5f; + auto downIsHeld = g_joypadAxis.y < -0.5f; + auto scrollUp = !g_upWasHeld && upIsHeld; + auto scrollDown = !g_downWasHeld && downIsHeld; + + auto prevSelectedRowIndex = g_selectedRowIndex; + + if (scrollUp) + { + if (g_selectedRowIndex > 0) + --g_selectedRowIndex; + } + else if (scrollDown) + { + if (g_selectedRowIndex < rowCount - 1) + ++g_selectedRowIndex; + } + + if (scrollUp || scrollDown) + { + Game_PlaySound("move"); + g_prevSelectedRowIndex = prevSelectedRowIndex; + g_joypadAxis.y = 0; + } + + g_upWasHeld = upIsHeld; + g_downWasHeld = downIsHeld; + + if (g_isDeclined) + { + if (g_selectedRowIndex == g_cancelButtonIndex) + { + Game_PlaySound("window_close"); } else { - DrawNextButtonGuide(isController, isKeyboard); - - if (!g_isControlsVisible && g_isAccepted) - { - g_controlsAppearTime = ImGui::GetTime(); - g_isControlsVisible = true; - - ResetSelection(); - Game_PlaySound("window_open"); - } + Game_PlaySound("move"); } + + g_selectedRowIndex = g_cancelButtonIndex; + g_isDeclined = false; } - else + } + else if (!App::s_isInit) // Only accept mouse input during installer. + { + auto clipRectMin = drawList->GetClipRectMin(); + auto clipRectMax = drawList->GetClipRectMax(); + + ImVec2 listMin = { clipRectMin.x, clipRectMin.y + windowMarginY }; + ImVec2 listMax = { clipRectMax.x, clipRectMin.y + windowMarginY + (itemHeight * rowCount) + (itemPadding * rowCount) }; + + // Invalidate index if the mouse cursor is outside of the list box. + if (!ImGui::IsMouseHoveringRect(listMin, listMax, false)) + g_selectedRowIndex = -1; + + for (int i = 0; i < rowCount; i++) { - DrawNextButtonGuide(isController, isKeyboard); + auto currentHeight = itemHeight * i; + auto currentPadding = itemPadding * i; + + ImVec2 itemMin = { listMin.x, listMin.y + currentHeight + currentPadding }; + ImVec2 itemMax = { listMax.x, clipRectMin.y + windowMarginY + currentHeight + itemHeight + currentPadding }; - if (g_isAccepted) + if (ImGui::IsMouseHoveringRect(itemMin, itemMax, false)) { - g_result = 0; + if (g_selectedRowIndex != i) + Game_PlaySound("move"); - MessageWindow::Close(); + g_selectedRowIndex = i; + + break; } } } - else if (g_isClosing) + + if (g_selectedRowIndex != -1 && g_isAccepted && !g_isClosing) { - s_isVisible = false; + g_result = g_selectedRowIndex; + + Game_PlaySound("main_deside"); + + MessageWindow::Close(); } + + drawList->PopClipRect(); } bool MessageWindow::Open(std::string text, int* result, std::span buttons, int defaultButtonIndex, int cancelButtonIndex) @@ -553,23 +518,21 @@ bool MessageWindow::Open(std::string text, int* result, std::span b { s_isVisible = true; g_isClosing = false; - g_isControlsVisible = false; - g_foregroundCount = 0; - g_appearTime = ImGui::GetTime(); - g_controlsAppearTime = ImGui::GetTime(); + g_time = ImGui::GetTime(); g_text = text; g_buttons = std::vector(buttons.begin(), buttons.end()); g_defaultButtonIndex = defaultButtonIndex; g_cancelButtonIndex = cancelButtonIndex; - ResetSelection(); + if (g_buttons.empty()) + g_buttons.push_back(Localise("Common_OK")); - Game_PlaySound("window_open"); + ResetSelection(); g_isAwaitingResult = true; } - + *result = g_result; // Returns true when the message window is closed. @@ -578,17 +541,9 @@ bool MessageWindow::Open(std::string text, int* result, std::span b void MessageWindow::Close() { - if (!g_isClosing) - { - g_appearTime = ImGui::GetTime(); - g_controlsAppearTime = ImGui::GetTime(); - g_isClosing = true; - g_isControlsVisible = false; - g_foregroundCount = 0; - g_isAwaitingResult = false; - - ButtonGuide::Close(); - } + if (g_isClosing) + return; - Game_PlaySound("window_close"); + g_time = ImGui::GetTime(); + g_isClosing = true; } diff --git a/MarathonRecomp/ui/options_menu.cpp b/MarathonRecomp/ui/options_menu.cpp index 59ff68a3a..a1b6d3437 100644 --- a/MarathonRecomp/ui/options_menu.cpp +++ b/MarathonRecomp/ui/options_menu.cpp @@ -1482,7 +1482,6 @@ static void DrawInfoPanel(ImVec2 infoMin, ImVec2 infoMax) clipRectMin = { clipRectMin.x, thumbnailMax.y }; auto fontSize = Scale(28.0f); - auto annotationFontSize = fontSize * ANNOTATION_FONT_SIZE_MODIFIER; /* Extra padding between the start of the description text and the @@ -1493,25 +1492,6 @@ static void DrawInfoPanel(ImVec2 infoMin, ImVec2 infoMax) auto textY = thumbnailMax.y + offsetY; float lineWidth = clipRectMax.x - clipRectMin.x; - if (Config::Language == ELanguage::Japanese) - { - /* Removing some padding of the applied due - to the inclusion of annotation for Japanese. */ - textY -= Scale(8.0f); - - /* The annotation (and thus the Japanese) can be - drawn above the edges of the info panel thus the - clip needs to be extended a bit. */ - clipRectMin.x -= annotationFontSize; - clipRectMax.x += annotationFontSize; - - textY += annotationFontSize; - - // Dirty hack to disallow clipping on Japanese text - // whilst allowing annotations to go over the border - lineWidth -= annotationFontSize; - } - auto textSize = MeasureCentredParagraph(g_rodinFont, fontSize, lineWidth, 5.0f, desc.c_str()); drawList->PushClipRect(clipRectMin, clipRectMax, false); @@ -1584,7 +1564,7 @@ static void DrawInfoPanel(ImVec2 infoMin, ImVec2 infoMax) SetVerticalMarqueeFade({ clipRectMin.x, clipRectMin.y + Scale(5.5f) }, clipRectMax, Scale(10), Scale(10)); - DrawRubyAnnotatedText + DrawTextParagraph ( g_rodinFont, fontSize, @@ -1592,13 +1572,10 @@ static void DrawInfoPanel(ImVec2 infoMin, ImVec2 infoMax) { textX, textY - scrollOffset }, 5.0f, desc.c_str(), + [=](const char* str, ImVec2 pos) { DrawTextBasic(g_rodinFont, fontSize, pos, IM_COL32_WHITE, str); - }, - [=](const char* str, float size, ImVec2 pos) - { - DrawTextBasic(g_rodinFont, size, pos, IM_COL32_WHITE, str); } ); diff --git a/MarathonRecompLib/config/Marathon.toml b/MarathonRecompLib/config/Marathon.toml index 74bb2c5ff..36269892d 100644 --- a/MarathonRecompLib/config/Marathon.toml +++ b/MarathonRecompLib/config/Marathon.toml @@ -98,24 +98,7 @@ address = 0x82607CD4 registers = ["r4"] [[midasm_hook]] -name = "RestoreTitleButtons" -address = 0x82512810 -registers = ["r4"] -after_instruction = true - -[[midasm_hook]] -name = "RestoreTitleButtons2" -address = 0x82512874 -registers = ["r11"] -after_instruction = true - -[[midasm_hook]] -name = "DisableTitleTaskStartWait" -address = 0x82512748 -jump_address_on_true = 0x8251274C - -[[midasm_hook]] -name = "SetDefaultTitleTaskSelection" +name = "TitleTask_SetDefaultOption" address = 0x82512B10 registers = ["r3", "r4"] @@ -263,3 +246,9 @@ registers = ["r8"] name = "MovieVoiceLang" address = 0x8264B4E4 registers = ["r19"] + +[[midasm_hook]] +name = "TitleTask_RedirectStateTransitionToOutroAnim" +address = 0x82512C90 +registers = ["r31"] +jump_address_on_true = 0x82512C9C diff --git a/MarathonRecompResources b/MarathonRecompResources index 5ef68780a..e0c15da29 160000 --- a/MarathonRecompResources +++ b/MarathonRecompResources @@ -1 +1 @@ -Subproject commit 5ef68780ab0b7dec5d12eed4112c25147ae9ab6a +Subproject commit e0c15da298954b8046af761af731ed1b9ff14412