From c8fa4f0791690aebc9cc653e2231d9c4b3369ddb Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Sat, 4 Oct 2025 20:44:57 +0100 Subject: [PATCH 01/86] Initial options menu hooks and design Co-authored-by: squidbus <175574877+squidbus@users.noreply.github.com> --- MarathonRecomp/CMakeLists.txt | 56 +- MarathonRecomp/api/Marathon.h | 4 +- MarathonRecomp/api/Sonicteam/GameImp.h | 7 +- MarathonRecomp/api/Sonicteam/MainMenuTask.h | 6 +- .../Sonicteam/Message/MsgPauseAdapterText.h | 15 + MarathonRecomp/api/Sonicteam/PauseAdapter.h | 11 + MarathonRecomp/api/Sonicteam/SoX/IResource2.h | 1 + MarathonRecomp/api/Sonicteam/TextBook.h | 2 + .../gpu/imgui/imgui_font_builder.cpp | 19 +- MarathonRecomp/gpu/imgui/imgui_snapshot.h | 2 +- MarathonRecomp/gpu/video.cpp | 3 +- .../patches/MainMenuTask_patches.cpp | 39 +- .../patches/aspect_ratio_patches.cpp | 9 + MarathonRecomp/patches/aspect_ratio_patches.h | 2 + MarathonRecomp/patches/pause_patches.cpp | 59 + MarathonRecomp/sdl_listener.h | 11 + MarathonRecomp/ui/black_bar.cpp | 12 +- MarathonRecomp/ui/common_menu.cpp | 342 ++++ MarathonRecomp/ui/common_menu.h | 61 + MarathonRecomp/ui/imgui_utils.cpp | 127 +- MarathonRecomp/ui/imgui_utils.h | 8 +- MarathonRecomp/ui/installer_wizard.cpp | 49 +- MarathonRecomp/ui/options_menu.cpp | 1795 +---------------- MarathonRecomp/ui/options_menu.h | 16 +- MarathonRecomp/ui/options_menu_thumbnails.cpp | 208 -- MarathonRecomp/ui/options_menu_thumbnails.h | 8 - MarathonRecomp/ui/tv_static.cpp | 392 ---- MarathonRecomp/ui/tv_static.h | 8 - MarathonRecompLib/config/Marathon.toml | 10 + 29 files changed, 711 insertions(+), 2571 deletions(-) create mode 100644 MarathonRecomp/api/Sonicteam/Message/MsgPauseAdapterText.h create mode 100644 MarathonRecomp/api/Sonicteam/PauseAdapter.h create mode 100644 MarathonRecomp/patches/pause_patches.cpp create mode 100644 MarathonRecomp/ui/common_menu.cpp create mode 100644 MarathonRecomp/ui/common_menu.h delete mode 100644 MarathonRecomp/ui/options_menu_thumbnails.cpp delete mode 100644 MarathonRecomp/ui/options_menu_thumbnails.h delete mode 100644 MarathonRecomp/ui/tv_static.cpp delete mode 100644 MarathonRecomp/ui/tv_static.h diff --git a/MarathonRecomp/CMakeLists.txt b/MarathonRecomp/CMakeLists.txt index 262528d6e..d9a29b278 100644 --- a/MarathonRecomp/CMakeLists.txt +++ b/MarathonRecomp/CMakeLists.txt @@ -143,6 +143,7 @@ set(MARATHON_RECOMP_PATCHES_CXX_SOURCES "patches/loading_patches.cpp" "patches/MainMenuTask_patches.cpp" "patches/misc_patches.cpp" + "patches/pause_patches.cpp" "patches/player_patches.cpp" "patches/SaveDataTask_patches.cpp" "patches/text_patches.cpp" @@ -154,15 +155,14 @@ set(MARATHON_RECOMP_UI_CXX_SOURCES "ui/achievement_menu.cpp" "ui/achievement_overlay.cpp" "ui/black_bar.cpp" - "ui/button_guide.cpp" + "ui/button_guide.cpp" + "ui/common_menu.cpp" "ui/fader.cpp" "ui/game_window.cpp" "ui/imgui_utils.cpp" "ui/installer_wizard.cpp" "ui/message_window.cpp" "ui/options_menu.cpp" - "ui/options_menu_thumbnails.cpp" - "ui/tv_static.cpp" ) set(MARATHON_RECOMP_INSTALL_CXX_SOURCES @@ -561,56 +561,6 @@ BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/ins BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_007.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_007.dds" ARRAY_NAME "g_install_007" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_008.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_008.dds" ARRAY_NAME "g_install_008" COMPRESSION_TYPE "zstd") -## Options Menu ## -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/achievement_notifications.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/achievement_notifications.dds" ARRAY_NAME "g_achievement_notifications" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/allow_background_input_ps.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/allow_background_input_ps.dds" ARRAY_NAME "g_allow_background_input_ps" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/allow_background_input_xb.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/allow_background_input_xb.dds" ARRAY_NAME "g_allow_background_input_xb" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/antialiasing_none.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/antialiasing_none.dds" ARRAY_NAME "g_antialiasing_none" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/antialiasing_2x.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/antialiasing_2x.dds" ARRAY_NAME "g_antialiasing_2x" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/antialiasing_4x.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/antialiasing_4x.dds" ARRAY_NAME "g_antialiasing_4x" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/antialiasing_8x.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/antialiasing_8x.dds" ARRAY_NAME "g_antialiasing_8x" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/aspect_ratio.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/aspect_ratio.dds" ARRAY_NAME "g_aspect_ratio" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/brightness.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/brightness.dds" ARRAY_NAME "g_brightness" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/channel_stereo.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/channel_stereo.dds" ARRAY_NAME "g_channel_stereo" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/channel_surround.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/channel_surround.dds" ARRAY_NAME "g_channel_surround" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/control_tutorial_ps.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/control_tutorial_ps.dds" ARRAY_NAME "g_control_tutorial_ps" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/control_tutorial_xb.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/control_tutorial_xb.dds" ARRAY_NAME "g_control_tutorial_xb" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/controller_icons.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/controller_icons.dds" ARRAY_NAME "g_controller_icons" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/default.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/default.dds" ARRAY_NAME "g_default" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/effects_volume.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/effects_volume.dds" ARRAY_NAME "g_effects_volume" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/fps.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/fps.dds" ARRAY_NAME "g_fps" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/fullscreen.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/fullscreen.dds" ARRAY_NAME "g_fullscreen" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/csm_texture_filtering_bilinear.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/csm_texture_filtering_bilinear.dds" ARRAY_NAME "g_csm_texture_filtering_bilinear" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/csm_texture_filtering_bicubic.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/csm_texture_filtering_bicubic.dds" ARRAY_NAME "g_csm_texture_filtering_bicubic" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/hints.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/hints.dds" ARRAY_NAME "g_hints" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/horizontal_camera.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/horizontal_camera.dds" ARRAY_NAME "g_horizontal_camera" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/language.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/language.dds" ARRAY_NAME "g_language" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/master_volume.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/master_volume.dds" ARRAY_NAME "g_master_volume" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/monitor.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/monitor.dds" ARRAY_NAME "g_monitor" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/movie_scale_fit.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/movie_scale_fit.dds" ARRAY_NAME "g_movie_scale_fit" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/movie_scale_fill.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/movie_scale_fill.dds" ARRAY_NAME "g_movie_scale_fill" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/music_attenuation.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/music_attenuation.dds" ARRAY_NAME "g_music_attenuation" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/music_volume.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/music_volume.dds" ARRAY_NAME "g_music_volume" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/shadow_resolution_x512.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/shadow_resolution_x512.dds" ARRAY_NAME "g_shadow_resolution_x512" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/shadow_resolution_x1024.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/shadow_resolution_x1024.dds" ARRAY_NAME "g_shadow_resolution_x1024" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/shadow_resolution_x2048.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/shadow_resolution_x2048.dds" ARRAY_NAME "g_shadow_resolution_x2048" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/shadow_resolution_x4096.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/shadow_resolution_x4096.dds" ARRAY_NAME "g_shadow_resolution_x4096" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/shadow_resolution_x8192.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/shadow_resolution_x8192.dds" ARRAY_NAME "g_shadow_resolution_x8192" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/transparency_antialiasing_false.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/transparency_antialiasing_false.dds" ARRAY_NAME "g_transparency_antialiasing_false" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/transparency_antialiasing_true.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/transparency_antialiasing_true.dds" ARRAY_NAME "g_transparency_antialiasing_true" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/ui_alignment_centre.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/ui_alignment_centre.dds" ARRAY_NAME "g_ui_alignment_centre" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/ui_alignment_edge.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/ui_alignment_edge.dds" ARRAY_NAME "g_ui_alignment_edge" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/vertical_camera.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/vertical_camera.dds" ARRAY_NAME "g_vertical_camera" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/voice_language.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/voice_language.dds" ARRAY_NAME "g_voice_language" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/vibration_ps.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/vibration_ps.dds" ARRAY_NAME "g_vibration_ps" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/vibration_xb.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/vibration_xb.dds" ARRAY_NAME "g_vibration_xb" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/vsync_on.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/vsync_on.dds" ARRAY_NAME "g_vsync_on" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/vsync_off.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/vsync_off.dds" ARRAY_NAME "g_vsync_off" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp 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 MarathonRecomp 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 MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/options_static.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/options_static.dds" ARRAY_NAME "g_options_static" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/options_static_flash.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/options_static_flash.dds" ARRAY_NAME "g_options_static_flash" COMPRESSION_TYPE "zstd") - ## Game Icon ## BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/game_icon.bmp" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/game_icon.bmp" ARRAY_NAME "g_game_icon") diff --git a/MarathonRecomp/api/Marathon.h b/MarathonRecomp/api/Marathon.h index a12e4dd1e..de924114a 100644 --- a/MarathonRecomp/api/Marathon.h +++ b/MarathonRecomp/api/Marathon.h @@ -21,8 +21,8 @@ #include "Sonicteam/AppMarathon.h" #include "Sonicteam/AudioEngineXenon.h" #include "Sonicteam/CObjBalloonIconDrawable.h" -#include "Sonicteam/Camera/CameraModeManager.h" #include "Sonicteam/Camera/CameraMode.h" +#include "Sonicteam/Camera/CameraModeManager.h" #include "Sonicteam/Camera/Cameraman.h" #include "Sonicteam/Camera/SonicCamera.h" #include "Sonicteam/CsdLink.h" @@ -56,6 +56,7 @@ #include "Sonicteam/MainMode.h" #include "Sonicteam/Message/MsgCameramanCameraMode.h" #include "Sonicteam/Message/MsgObjJump123GetNextPoint.h" +#include "Sonicteam/Message/MsgPauseAdapterText.h" #include "Sonicteam/Message/MsgSuckPlayer.h" #include "Sonicteam/MessageWindowTask.h" #include "Sonicteam/MovieObject.h" @@ -70,6 +71,7 @@ #include "Sonicteam/NoSyncThread.h" #include "Sonicteam/ObjectVehicle.h" #include "Sonicteam/ObjectVehicleBike.h" +#include "Sonicteam/PauseAdapter.h" #include "Sonicteam/Player/GroundRayListener.h" #include "Sonicteam/Player/ICollisionListener.h" #include "Sonicteam/Player/ICollisionListenerTemplate.h" diff --git a/MarathonRecomp/api/Sonicteam/GameImp.h b/MarathonRecomp/api/Sonicteam/GameImp.h index 8f9b5325d..028a9db43 100644 --- a/MarathonRecomp/api/Sonicteam/GameImp.h +++ b/MarathonRecomp/api/Sonicteam/GameImp.h @@ -6,12 +6,14 @@ #include #include #include +#include #include namespace Sonicteam { - class ActorManager; // Gauge patch + class ActorManager; class GameScript; + class GameImp : public SoX::MessageReceiver { public: @@ -61,7 +63,8 @@ namespace Sonicteam xpointer m_pGameScript; be m_aObjPlayerActorID[0xF]; boost::shared_ptr m_spActorManager; - MARATHON_INSERT_PADDING(0xC); + xpointer m_pSystemTextBook; + MARATHON_INSERT_PADDING(8); stdx::vector>> m_vvspCameras; MARATHON_INSERT_PADDING(0x7D4); SoX::RefSharedPointer m_spPhysicsWorld; diff --git a/MarathonRecomp/api/Sonicteam/MainMenuTask.h b/MarathonRecomp/api/Sonicteam/MainMenuTask.h index 26e805ece..5d284a3f3 100644 --- a/MarathonRecomp/api/Sonicteam/MainMenuTask.h +++ b/MarathonRecomp/api/Sonicteam/MainMenuTask.h @@ -33,7 +33,11 @@ namespace Sonicteam }; be m_State; - MARATHON_INSERT_PADDING(0x248); + MARATHON_INSERT_PADDING(0x50); + be m_SelectedIndex; + MARATHON_INSERT_PADDING(0x1D8); + be m_PressedButtons; + MARATHON_INSERT_PADDING(0x18); xpointer m_Field298; xpointer m_SelectCharacters[9]; }; diff --git a/MarathonRecomp/api/Sonicteam/Message/MsgPauseAdapterText.h b/MarathonRecomp/api/Sonicteam/Message/MsgPauseAdapterText.h new file mode 100644 index 000000000..36a42911e --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/Message/MsgPauseAdapterText.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include +#include +#include + +namespace Sonicteam::Message +{ + struct MsgPauseAdapterText : SoX::Message<0x1C003> + { + stdx::string PauseName; + stdx::string SelectedName; + }; +} diff --git a/MarathonRecomp/api/Sonicteam/PauseAdapter.h b/MarathonRecomp/api/Sonicteam/PauseAdapter.h new file mode 100644 index 000000000..c4db48be7 --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/PauseAdapter.h @@ -0,0 +1,11 @@ +#pragma once + +namespace Sonicteam +{ + class PauseAdapter : public SoX::Engine::Task + { + public: + MARATHON_INSERT_PADDING(4); + be m_SelectedIndex; + }; +} diff --git a/MarathonRecomp/api/Sonicteam/SoX/IResource2.h b/MarathonRecomp/api/Sonicteam/SoX/IResource2.h index 2251aa7d0..fc01e8bbd 100644 --- a/MarathonRecomp/api/Sonicteam/SoX/IResource2.h +++ b/MarathonRecomp/api/Sonicteam/SoX/IResource2.h @@ -2,6 +2,7 @@ #include #include +#include namespace Sonicteam::SoX { diff --git a/MarathonRecomp/api/Sonicteam/TextBook.h b/MarathonRecomp/api/Sonicteam/TextBook.h index f0c9185ec..18bc4757e 100644 --- a/MarathonRecomp/api/Sonicteam/TextBook.h +++ b/MarathonRecomp/api/Sonicteam/TextBook.h @@ -1,7 +1,9 @@ #pragma once #include +#include #include +#include #include namespace Sonicteam diff --git a/MarathonRecomp/gpu/imgui/imgui_font_builder.cpp b/MarathonRecomp/gpu/imgui/imgui_font_builder.cpp index 0b2e28d76..5d28610c9 100644 --- a/MarathonRecomp/gpu/imgui/imgui_font_builder.cpp +++ b/MarathonRecomp/gpu/imgui/imgui_font_builder.cpp @@ -220,10 +220,19 @@ static bool FontBuilder_Build(ImFontAtlas* atlas) for (size_t i = 0; i < atlas->ConfigData.size(); i++) { + auto& config = atlas->ConfigData[i]; + double spaceAdvance = 0.0; + double spaceMultiplier = 1.0; - auto& config = atlas->ConfigData[i]; - bool increaseSpacing = strstr(config.Name, "Rodin") != nullptr; + if (strstr(config.Name, "FOT-Rodin") != nullptr) + { + spaceMultiplier = 1.75; + } + else if (strstr(config.Name, "FOT-NewRodin") != nullptr) + { + spaceMultiplier = 2.5; + } auto& [index, count] = ranges[i]; for (size_t j = 0; j < count; j++) @@ -236,11 +245,7 @@ static bool FontBuilder_Build(ImFontAtlas* atlas) double advance = glyph.getAdvance(); if (glyph.getCodepoint() == ' ') { - if (increaseSpacing) - { - advance *= 1.75; - } - + advance *= spaceMultiplier; spaceAdvance = advance; } diff --git a/MarathonRecomp/gpu/imgui/imgui_snapshot.h b/MarathonRecomp/gpu/imgui/imgui_snapshot.h index a77745165..f72bb3719 100644 --- a/MarathonRecomp/gpu/imgui/imgui_snapshot.h +++ b/MarathonRecomp/gpu/imgui/imgui_snapshot.h @@ -3,7 +3,7 @@ // Undefine this to generate a font atlas file in working directory. // You also need to do this if you are testing localization, as only // characters in the locale get added to the atlas. -#define ENABLE_IM_FONT_ATLAS_SNAPSHOT +// #define ENABLE_IM_FONT_ATLAS_SNAPSHOT struct ImFontAtlasSnapshot { diff --git a/MarathonRecomp/gpu/video.cpp b/MarathonRecomp/gpu/video.cpp index e70ba4d41..ad2c21972 100644 --- a/MarathonRecomp/gpu/video.cpp +++ b/MarathonRecomp/gpu/video.cpp @@ -1588,7 +1588,6 @@ static void CreateImGuiBackend() AchievementOverlay::Init(); ButtonGuide::Init(); MessageWindow::Init(); - OptionsMenu::Init(); InstallerWizard::Init(); ImGui_ImplSDL2_InitForOther(GameWindow::s_pWindow); @@ -2854,7 +2853,7 @@ static void DrawImGui() #endif AchievementMenu::Draw(); -// OptionsMenu::Draw(); + OptionsMenu::Draw(); AchievementOverlay::Draw(); InstallerWizard::Draw(); ButtonGuide::Draw(); diff --git a/MarathonRecomp/patches/MainMenuTask_patches.cpp b/MarathonRecomp/patches/MainMenuTask_patches.cpp index 3c83651d9..2f50f9d8b 100644 --- a/MarathonRecomp/patches/MainMenuTask_patches.cpp +++ b/MarathonRecomp/patches/MainMenuTask_patches.cpp @@ -1,4 +1,6 @@ #include "MainMenuTask_patches.h" +#include +#include // Sonicteam::MainMenuTask::Update PPC_FUNC_IMPL(__imp__sub_824FFCF8); @@ -6,10 +8,39 @@ PPC_FUNC(sub_824FFCF8) { auto pMainMenuTask = (Sonicteam::MainMenuTask*)(base + ctx.r3.u32); - MainMenuTaskPatches::State = (Sonicteam::MainMenuTask::MainMenuState)pMainMenuTask->m_State.get(); + if (pMainMenuTask->m_State == Sonicteam::MainMenuTask::MainMenuState_MainMenu && pMainMenuTask->m_SelectedIndex == 3) + { + if (!OptionsMenu::s_isVisible && (pMainMenuTask->m_PressedButtons.get() & 0x10) != 0) + { + OptionsMenu::Open(); - for (auto& event : MainMenuTaskPatches::Events) - event->Update(pMainMenuTask, ctx.f1.f64); + Game_PlaySound("main_deside"); - __imp__sub_824FFCF8(ctx, base); + pMainMenuTask->m_State = Sonicteam::MainMenuTask::MainMenuState_MainMenu; + pMainMenuTask->m_PressedButtons = 0; + } + else if (OptionsMenu::s_isVisible && (pMainMenuTask->m_PressedButtons.get() & 0x20) != 0) + { + OptionsMenu::Close(); + + Game_PlaySound("window_close"); + + pMainMenuTask->m_State = Sonicteam::MainMenuTask::MainMenuState_MainMenu; + pMainMenuTask->m_PressedButtons = 0; + } + } + + if (!OptionsMenu::s_isVisible) + { + MainMenuTaskPatches::State = (Sonicteam::MainMenuTask::MainMenuState)pMainMenuTask->m_State.get(); + + for (auto& event : MainMenuTaskPatches::Events) + event->Update(pMainMenuTask, ctx.f1.f64); + + __imp__sub_824FFCF8(ctx, base); + } + else + { + pMainMenuTask->m_PressedButtons = 0; + } } diff --git a/MarathonRecomp/patches/aspect_ratio_patches.cpp b/MarathonRecomp/patches/aspect_ratio_patches.cpp index 7aad517d6..682e090f1 100644 --- a/MarathonRecomp/patches/aspect_ratio_patches.cpp +++ b/MarathonRecomp/patches/aspect_ratio_patches.cpp @@ -181,6 +181,15 @@ void AspectRatioPatches::ComputeOffsets() g_aspectRatioNarrowScale = std::clamp((g_aspectRatio - NARROW_ASPECT_RATIO) / (WIDE_ASPECT_RATIO - NARROW_ASPECT_RATIO), 0.0f, 1.0f); g_radarMapScale = 256 * g_aspectRatioScale * g_aspectRatioGameplayScale; + + if (g_aspectRatio > WIDE_ASPECT_RATIO) + { + g_pillarboxWidth = (width - (height * 16.0f / 9.0f)) / 2.0f; + } + else if (WIDE_ASPECT_RATIO > g_aspectRatio) + { + g_letterboxHeight = (height - (width * 9.0f / 16.0f)) / 2.0f; + } } void EmplacePath(const void* key, const std::string_view& value) diff --git a/MarathonRecomp/patches/aspect_ratio_patches.h b/MarathonRecomp/patches/aspect_ratio_patches.h index 5dd140369..02ed17ce9 100644 --- a/MarathonRecomp/patches/aspect_ratio_patches.h +++ b/MarathonRecomp/patches/aspect_ratio_patches.h @@ -18,6 +18,8 @@ inline float g_aspectRatioScale; inline float g_aspectRatioGameplayScale; inline float g_aspectRatioNarrowScale; inline float g_radarMapScale; +inline float g_pillarboxWidth; +inline float g_letterboxHeight; class AspectRatioPatches { diff --git a/MarathonRecomp/patches/pause_patches.cpp b/MarathonRecomp/patches/pause_patches.cpp new file mode 100644 index 000000000..294c1ed59 --- /dev/null +++ b/MarathonRecomp/patches/pause_patches.cpp @@ -0,0 +1,59 @@ +#include +#include +#include + +void AddPauseMenuItem +( + Sonicteam::TextBook* in_pTextBook, + stdx::vector* in_pvActionNames, + stdx::vector* in_pvTextCards, + stdx::vector* in_pvEnabledItems, + const char* in_pActionName, + const char* in_pTextName, + bool in_isEnabled +) +{ + guest_stack_var actionName(in_pActionName); + guest_stack_var textName(in_pTextName); + guest_stack_var> spTextCard; + guest_stack_var isEnabled((int)in_isEnabled); + + GuestToHostFunction(sub_8217D608, in_pvActionNames, actionName.get()); + GuestToHostFunction(sub_825ECB48, spTextCard.get(), in_pTextBook, textName->c_str()); + GuestToHostFunction(sub_8239E8F0, in_pvTextCards, spTextCard.get()); + GuestToHostFunction(sub_823879C8, in_pvEnabledItems, isEnabled.get()); +} + +void GameImp_PauseMenu_AddQuitPrefix(PPCRegister& r1, PPCRegister& r30) +{ + auto pGameImp = (Sonicteam::GameImp*)g_memory.Translate(r30.u32); + auto pvActionNames = (stdx::vector*)g_memory.Translate(r1.u32 + 0x70); + auto pvTextCards = (stdx::vector*)g_memory.Translate(r1.u32 + 0x60); + auto pvEnabledItems = (stdx::vector*)g_memory.Translate(r1.u32 + 0x80); + + AddPauseMenuItem(pGameImp->m_pSystemTextBook, pvActionNames, pvTextCards, pvEnabledItems, "options", "msg_options", true); +} + +// Sonicteam::PauseAdapter::MapActionNameToIndex (speculatory) +PPC_FUNC_IMPL(__imp__sub_8216DA08); +PPC_FUNC(sub_8216DA08) +{ + auto pPauseAdapter = (Sonicteam::PauseAdapter*)(base + ctx.r3.u32); + auto pMsgPauseAdapterText = (Sonicteam::Message::MsgPauseAdapterText*)(base + ctx.r4.u32); + + __imp__sub_8216DA08(ctx, base); + + // Set selected index to unused slot. + if (pMsgPauseAdapterText->SelectedName == "options") + pPauseAdapter->m_SelectedIndex = 6; +} + +void PauseAdapter_DoAction(PPCRegister& r11) +{ + switch (r11.u32) + { + case 6: + OptionsMenu::Open(true); + break; + } +} diff --git a/MarathonRecomp/sdl_listener.h b/MarathonRecomp/sdl_listener.h index f4f6bed6d..2f449ab03 100644 --- a/MarathonRecomp/sdl_listener.h +++ b/MarathonRecomp/sdl_listener.h @@ -17,5 +17,16 @@ class SDLEventListener : public ISDLEventListener GetEventListeners().emplace_back(this); } + ~SDLEventListener() override + { + auto& eventListeners = GetEventListeners(); + + auto it = std::find(eventListeners.begin(), eventListeners.end(), this); + + assert(it != eventListeners.end()); + + eventListeners.erase(it); + } + bool OnSDLEvent(SDL_Event* event) override { return false; } }; diff --git a/MarathonRecomp/ui/black_bar.cpp b/MarathonRecomp/ui/black_bar.cpp index 549ad6d18..0aafe31fd 100644 --- a/MarathonRecomp/ui/black_bar.cpp +++ b/MarathonRecomp/ui/black_bar.cpp @@ -11,17 +11,13 @@ void BlackBar::Draw() if (g_aspectRatio > WIDE_ASPECT_RATIO) { - auto width = (res.x - (res.y * 16.0f / 9.0f)) / 2.0f; - - drawList->AddRectFilled({ 0.0f, 0.0f }, { width, res.y }, IM_COL32_BLACK); - drawList->AddRectFilled({ res.x - width, 0.0f }, res, IM_COL32_BLACK); + drawList->AddRectFilled({ 0.0f, 0.0f }, { g_pillarboxWidth, res.y }, IM_COL32_BLACK); + drawList->AddRectFilled({ res.x - g_pillarboxWidth, 0.0f }, res, IM_COL32_BLACK); } else if (WIDE_ASPECT_RATIO > g_aspectRatio) { - auto height = (res.y - (res.x * 9.0f / 16.0f)) / 2.0f; - - drawList->AddRectFilled({ 0.0f, 0.0f }, { res.x, height }, IM_COL32_BLACK); - drawList->AddRectFilled({ 0.0f, res.y - height }, res, IM_COL32_BLACK); + drawList->AddRectFilled({ 0.0f, 0.0f }, { res.x, g_letterboxHeight }, IM_COL32_BLACK); + drawList->AddRectFilled({ 0.0f, res.y - g_letterboxHeight }, res, IM_COL32_BLACK); } } diff --git a/MarathonRecomp/ui/common_menu.cpp b/MarathonRecomp/ui/common_menu.cpp new file mode 100644 index 000000000..e6feed529 --- /dev/null +++ b/MarathonRecomp/ui/common_menu.cpp @@ -0,0 +1,342 @@ +#include "common_menu.h" +#include +#include +#include +#include + +void CommonMenu::Open() +{ + m_appearTime = ImGui::GetTime(); + m_titleAppearTime = m_appearTime; + m_descAppearTime = m_appearTime; +} + +void CommonMenu::Draw() +{ + auto* drawList = ImGui::GetBackgroundDrawList(); + auto& res = ImGui::GetIO().DisplaySize; + + auto width = 0.0f; + auto height = 0.0f; + + if (g_aspectRatio > WIDE_ASPECT_RATIO) + { + width = (res.x - (res.y * 16.0f / 9.0f)) / 2.0f; + } + else if (WIDE_ASPECT_RATIO > g_aspectRatio) + { + height = (res.y - (res.x * 9.0f / 16.0f)) / 2.0f; + } + + if (App::s_isInit) + SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); + + ImVec2 min = { width, height }; + ImVec2 max = { res.x - width, res.y - height }; + + auto borderMotionTime = PlayIntroAnim ? ComputeMotion(m_appearTime, 0, 10) : 1.0; + + auto gradientTop = IM_COL32(0, 103, 255, 255); + auto gradientBottom = IM_COL32(0, 92, 229, 255); + auto gradientHeight = Scale(126, true); + auto gradientMotion = Lerp(min.y - gradientHeight, 0.0, borderMotionTime); + + // Draw gradient to fill gap between red strip and top metal plates. + drawList->AddRectFilledMultiColor({ 0.0f, gradientMotion }, { res.x, gradientMotion + gradientHeight }, gradientTop, gradientTop, gradientBottom, gradientBottom); + + auto redStripCornerUVs = PIXELS_TO_UV_COORDS(1024, 1024, 0, 301, 300, 50); + auto redStripLeftStretchUVs = PIXELS_TO_UV_COORDS(1024, 1024, 0, 301, 150, 50); + auto redStripRightStretchUVs = PIXELS_TO_UV_COORDS(1024, 1024, 300, 301, 300, 50); + auto redStripColour = IM_COL32(168, 15, 15, 255); + auto redStripOffsetY = Scale(81.75, true); + auto redStripHeight = Scale(50, true); + auto redStripMotion = Lerp(min.y - redStripHeight, min.y + redStripOffsetY, borderMotionTime); + + // TODO: account for metal plate height in motion. + ImVec2 redStripCornerMin = { min.x - Scale(43, true), redStripMotion }; + ImVec2 redStripCornerMax = { redStripCornerMin.x + Scale(300, true), redStripMotion + redStripHeight }; + + // Draw corner left red strip. + drawList->AddImage(g_texMainMenu1.get(), redStripCornerMin, redStripCornerMax, GET_UV_COORDS(redStripCornerUVs), redStripColour); + + // Draw left stretched red strip. + drawList->AddImage(g_texMainMenu1.get(), { 0.0f, redStripCornerMin.y }, { redStripCornerMin.x, redStripCornerMax.y }, GET_UV_COORDS(redStripLeftStretchUVs), redStripColour); + + // Draw right stretched red strip. + drawList->AddImage(g_texMainMenu1.get(), { redStripCornerMax.x, redStripCornerMin.y }, { res.x, redStripCornerMax.y }, GET_UV_COORDS(redStripRightStretchUVs), redStripColour); + + auto redStripHighlightWidth = Scale(270, true); + auto redStripHighlightHeight = Scale(48, true); + auto redStripHighlightY = redStripCornerMin.y - Scale(3, true); + + auto drawRedStripHighlight = [&](ImVec2 redStripHighlightMin, uint32_t tlColour, uint32_t brColour) + { + auto redStripHighlightUVs = PIXELS_TO_UV_COORDS(1024, 1024, 201, 501, 264, 50); + + ImVec2 redStripHighlightMax = { redStripHighlightMin.x + redStripHighlightWidth, redStripHighlightMin.y + redStripHighlightHeight }; + + SetAdditive(true); + SetHorizontalGradient(redStripHighlightMin, redStripHighlightMax, tlColour, brColour); + + drawList->AddImage(g_texMainMenu1.get(), redStripHighlightMin, redStripHighlightMax, GET_UV_COORDS(redStripHighlightUVs)); + + ResetGradient(); + ResetAdditive(); + }; + + // Draw fixed highlights for red strip. + drawRedStripHighlight({ redStripCornerMax.x + Scale(67, true), redStripHighlightY }, IM_COL32(255, 172, 0, 0), IM_COL32(255, 172, 0, 67)); + drawRedStripHighlight({ redStripCornerMax.x + Scale(-43, true), redStripHighlightY }, IM_COL32(255, 89, 0, 0), IM_COL32(255, 89, 0, 20)); + + auto redStripHighlightMotionOffsetX = Scale(63, true); + auto redStripHighlightMotionX1Time = ComputeMotion(m_titleAppearTime, PlayIntroAnim ? 10 : 0, 10); + auto redStripHighlightMotionX2Time = ComputeMotion(m_titleAppearTime, PlayIntroAnim ? 12 : 2, 10); + auto redStripHighlightMotionAlphaIn1Time = ComputeMotion(m_titleAppearTime, PlayIntroAnim ? 10 : 0, 8); + auto redStripHighlightMotionAlphaIn2Time = ComputeMotion(m_titleAppearTime, PlayIntroAnim ? 12 : 2, 8); + auto redStripHighlightMotionAlphaOut1Time = ComputeMotion(m_titleAppearTime, PlayIntroAnim ? 18 : 8, 2); + auto redStripHighlightMotionAlphaOut2Time = ComputeMotion(m_titleAppearTime, PlayIntroAnim ? 20 : 2, 2); + auto redStripHighlightMotionX1 = Lerp(res.x + redStripHighlightWidth, min.x + redStripHighlightMotionOffsetX, redStripHighlightMotionX1Time); + auto redStripHighlightMotionX2 = Lerp(res.x + redStripHighlightWidth, min.x + redStripHighlightMotionOffsetX, redStripHighlightMotionX2Time); + auto redStripHighlightMotionAlpha1 = Lerp(0.0, 100.0, redStripHighlightMotionAlphaIn1Time); + auto redStripHighlightMotionAlpha2 = Lerp(0.0, 100.0, redStripHighlightMotionAlphaIn2Time); + + if (redStripHighlightMotionAlphaIn1Time >= 1.0) + redStripHighlightMotionAlpha1 = Lerp(100.0, 0.0, redStripHighlightMotionAlphaOut1Time); + + if (redStripHighlightMotionAlphaIn2Time >= 1.0) + redStripHighlightMotionAlpha2 = Lerp(100.0, 0.0, redStripHighlightMotionAlphaOut2Time); + + // Draw animated highlights for title animation. + drawRedStripHighlight({ redStripHighlightMotionX1, redStripHighlightY }, IM_COL32(255, 172, 0, 0), IM_COL32(255, 172, 0, redStripHighlightMotionAlpha1)); + drawRedStripHighlight({ redStripHighlightMotionX2, redStripHighlightY }, IM_COL32(255, 172, 0, 0), IM_COL32(255, 172, 0, redStripHighlightMotionAlpha2)); + + auto titleFontSize = Scale(33, true); + auto titleSize = g_fntNewRodin->CalcTextSizeA(titleFontSize, FLT_MAX, 0.0f, Title); + auto titleOffsetX = redStripCornerMax.x - Scale(105, true); + auto titleOffsetY = min.y + Scale(85, true); + auto titleOffsetXMotionTime = ComputeMotion(m_titleAppearTime, PlayIntroAnim ? 10 : 0, 10); + auto titleOffsetXMotion = Lerp(max.x + titleSize.x, titleOffsetX, titleOffsetXMotionTime); + + if (m_previousTitle) + { + auto prevTitleAlphaMotionTime = ComputeMotion(m_titleAppearTime, PlayIntroAnim ? 10 : 0, 3); + + // Draw previous title fading out. + drawList->AddText(g_fntNewRodin, titleFontSize, { titleOffsetX, titleOffsetY }, IM_COL32(255, 255, 255, Lerp(255, 0, prevTitleAlphaMotionTime)), m_previousTitle); + } + + // Draw title. + drawList->AddText(g_fntNewRodin, titleFontSize, { titleOffsetXMotion, min.y + Scale(85, true) }, IM_COL32(255, 255, 255, 255 * titleOffsetXMotionTime), Title ? Title : "DUMMY"); + + auto topPlateCornerUVs = PIXELS_TO_UV_COORDS(1024, 1024, 0, 150, 250, 145); + auto topPlateLeftStretchUVs = PIXELS_TO_UV_COORDS(1024, 1024, 2, 150, 150, 145); + auto topPlateRightStretchUVs = PIXELS_TO_UV_COORDS(1024, 1024, 250, 150, 750, 145); + auto topPlateOffsetY = Scale(3.5, true); + auto topPlateHeight = Scale(145, true); + auto topPlateMotion = Lerp(min.y - topPlateHeight, min.y - topPlateOffsetY, borderMotionTime); + + ImVec2 topPlateCornerMin = { min.x - Scale(45, true), topPlateMotion }; + ImVec2 topPlateCornerMax = { topPlateCornerMin.x + Scale(250, true), topPlateMotion + topPlateHeight }; + ImVec2 topPlateStretchMin = { topPlateCornerMax.x, topPlateCornerMin.y }; + ImVec2 topPlateStretchMax = { res.x, topPlateCornerMax.y }; + + // Draw top left corner metal plate. + drawList->AddImage(g_texMainMenu1.get(), topPlateCornerMin, topPlateCornerMax, GET_UV_COORDS(topPlateCornerUVs)); + + // Draw top left stretched metal plate. + AddImageFlipped(g_texMainMenu1.get(), { 0.0f, topPlateCornerMin.y }, { topPlateCornerMin.x + Scale(2, true), topPlateCornerMax.y }, GET_UV_COORDS(topPlateLeftStretchUVs), IM_COL32_WHITE, true); + + // Draw top right stretched metal plate. + SetHorizontalGradient(topPlateStretchMin, topPlateStretchMax, IM_COL32_WHITE, IM_COL32(200, 200, 200, 255)); + drawList->AddImage(g_texMainMenu1.get(), topPlateStretchMin, topPlateStretchMax, GET_UV_COORDS(topPlateRightStretchUVs)); + ResetGradient(); + + auto textCoverCornerUVs = PIXELS_TO_UV_COORDS(1024, 1024, 801, 400, 150, 150); + auto textCoverCornerExtendUVs = PIXELS_TO_UV_COORDS(1024, 1024, 801, 400, 125, 150); + auto textCoverCentreUVs = PIXELS_TO_UV_COORDS(1024, 1024, 950, 400, 50, 150); + auto textCoverCornerUVCompensation = Scale(2, true); + auto textCoverOffsetY = Scale(16.4, true); + auto textCoverWidth = Scale(150, true); + auto textCoverHeight = Scale(150, true); + auto textCoverMotion = Lerp(max.y + textCoverHeight, max.y - textCoverOffsetY - textCoverHeight, borderMotionTime); + auto textCoverColour = IM_COL32(0, 23, 57, 255); + + ImVec2 textCoverCornerLeftMin = { min.x, textCoverMotion }; + ImVec2 textCoverCornerLeftMax = { textCoverCornerLeftMin.x + textCoverWidth, textCoverCornerLeftMin.y + textCoverHeight }; + ImVec2 textCoverCentreMin = { textCoverCornerLeftMax.x, textCoverCornerLeftMin.y }; + ImVec2 textCoverCentreMax = { max.x - textCoverWidth, textCoverCornerLeftMax.y }; + ImVec2 textCoverCornerRightMin = { max.x - textCoverWidth, textCoverCornerLeftMin.y }; + ImVec2 textCoverCornerRightMax = { max.x, textCoverCornerLeftMax.y }; + + // Draw text cover backdrop. + drawList->AddRectFilled({ 0.0f, textCoverMotion }, { res.x, textCoverMotion + textCoverHeight }, IM_COL32(0, 0, 0, 65)); + + if (Description) + { + auto descFadeScale = Scale(20, true); + auto descFontSize = Scale(27, true); + auto descSize = g_fntRodin->CalcTextSizeA(descFontSize, FLT_MAX, 0.0f, Description); + + ImVec2 descBoundsMin = { textCoverCentreMin.x - Scale(18, true), textCoverCentreMin.y + Scale(20, true) }; + ImVec2 descBoundsMax = { textCoverCentreMax.x + Scale(18, true), textCoverCentreMax.y - Scale(90, true) }; + auto descBoundsWidth = descBoundsMax.x - descBoundsMin.x; + + m_descPos = + { + descBoundsMin.x + ((descBoundsMax.x - descBoundsMin.x) / 2) - (descSize.x / 2), + descBoundsMin.y + ((descBoundsMax.y - descBoundsMin.y) / 2) - (descSize.y / 2) + }; + + if (descSize.x > descBoundsWidth) + { + auto descScrollMax = descSize.x - (descBoundsWidth - descFadeScale * 2); + auto descScrollSpeed = Scale(150, true); + auto descScrollDelay = 1.2f; + + if (descScrollMax > 0.0f) + { + auto horz = -m_inputListener.RightStickX; + + if (fabs(horz) > 0.25f) + { + m_isDescManualScrolling = true; + m_descScrollOffset += horz * descScrollSpeed * App::s_deltaTime; + } + else if (m_isDescManualScrolling && fabs(horz) <= 0.25f) + { + m_isDescScrolling = false; + m_isDescManualScrolling = false; + m_descScrollTimer = 0.0f; + m_descScrollDirection = horz > 0.0f ? 1.0f : -1.0f; + } + + if (!m_isDescManualScrolling) + { + if (!m_isDescScrolling) + { + m_descScrollTimer += App::s_deltaTime; + + if (m_descScrollTimer >= descScrollDelay) + m_isDescScrolling = true; + } + + if (m_isDescScrolling) + { + m_descScrollOffset += descScrollSpeed * m_descScrollDirection * App::s_deltaTime; + + if (m_descScrollOffset >= descScrollMax) + { + m_isDescScrolling = false; + m_descScrollOffset = descScrollMax; + m_descScrollTimer = 0.0f; + m_descScrollDirection = -1.0f; + } + else if (m_descScrollOffset <= 0.0f) + { + m_isDescScrolling = false; + m_descScrollOffset = 0; + m_descScrollTimer = 0.0f; + m_descScrollDirection = 1.0f; + } + } + } + + m_descScrollOffset = std::clamp(m_descScrollOffset, 0.0f, descScrollMax); + } + else + { + m_isDescScrolling = false; + m_descScrollOffset = 0.0f; + m_descScrollTimer = 0.0f; + m_descScrollDirection = 1.0f; + } + + m_descPos.x = (descBoundsMin.x + descFadeScale) - m_descScrollOffset; + } + + auto descAlphaMotionTime = ComputeMotion(m_descAppearTime, PlayIntroAnim ? 10 : 0, 15); + + // Draw previous description fading out. + if (m_previousDesc) + drawList->AddText(g_fntRodin, descFontSize, m_previousDescPos, IM_COL32(255, 255, 255, Lerp(255, 0, descAlphaMotionTime)), m_previousDesc); + + // Draw description. + drawList->AddText(g_fntRodin, descFontSize, m_descPos, IM_COL32(255, 255, 255, Lerp(0, 255, descAlphaMotionTime)), Description); + + // Draw left text cover. + drawList->AddImage(g_texMainMenu1.get(), { 0.0f, textCoverCornerLeftMin.y }, { textCoverCornerLeftMin.x + textCoverCornerUVCompensation, textCoverCornerLeftMax.y }, GET_UV_COORDS(textCoverCornerExtendUVs), textCoverColour); + drawList->AddImage(g_texMainMenu1.get(), textCoverCornerLeftMin, textCoverCornerLeftMax, GET_UV_COORDS(textCoverCornerUVs), textCoverColour); + + // Draw centre text cover. + drawList->AddImage(g_texMainMenu1.get(), textCoverCentreMin, textCoverCentreMax, GET_UV_COORDS(textCoverCentreUVs), textCoverColour); + + // Draw right text cover. + AddImageFlipped(g_texMainMenu1.get(), { textCoverCornerRightMax.x - textCoverCornerUVCompensation, textCoverCornerRightMin.y }, { res.x, textCoverCornerRightMax.y }, GET_UV_COORDS(textCoverCornerExtendUVs), textCoverColour); + AddImageFlipped(g_texMainMenu1.get(), textCoverCornerRightMin, textCoverCornerRightMax, GET_UV_COORDS(textCoverCornerUVs), textCoverColour, true); + } + else + { + // Draw blank text cover. + drawList->AddRectFilled({ 0.0f, textCoverCornerLeftMin.y }, { res.x, textCoverCornerLeftMax.y }, textCoverColour); + } + + auto bottomPlateUVs = PIXELS_TO_UV_COORDS(1024, 1024, 0, 0, 700, 145); + auto bottomPlateStretchUVs = PIXELS_TO_UV_COORDS(1024, 1024, 1, 0, 128, 145); + auto bottomPlateOffsetX = Scale(-59.5, true); + auto bottomPlateOffsetY = Scale(28.5, true); + auto bottomPlateWidth = Scale(700, true); + auto bottomPlateHeight = Scale(145, true); + auto bottomPlateMotion = Lerp(max.y + bottomPlateHeight, (max.y + bottomPlateOffsetY) - bottomPlateHeight, borderMotionTime); + + ImVec2 bottomPlateLeftMin = { min.x + bottomPlateOffsetX, bottomPlateMotion }; + ImVec2 bottomPlateLeftMax = { bottomPlateLeftMin.x + bottomPlateWidth, bottomPlateMotion + bottomPlateHeight }; + ImVec2 bottomPlateRightMin = { max.x - bottomPlateWidth - bottomPlateOffsetX, bottomPlateLeftMin.y }; + ImVec2 bottomPlateRightMax = { bottomPlateRightMin.x + bottomPlateWidth, bottomPlateLeftMax.y }; + + // Draw bottom left metal plate. + drawList->AddImage(g_texMainMenu1.get(), bottomPlateLeftMin, bottomPlateLeftMax, GET_UV_COORDS(bottomPlateUVs)); + + // Draw bottom left stretched metal plate. + AddImageFlipped(g_texMainMenu1.get(), { 0.0f, bottomPlateLeftMin.y }, { bottomPlateLeftMin.x, bottomPlateLeftMax.y }, GET_UV_COORDS(bottomPlateStretchUVs), IM_COL32_WHITE, true); + + // Draw bottom right metal plate. + AddImageFlipped(g_texMainMenu1.get(), bottomPlateRightMin, bottomPlateRightMax, GET_UV_COORDS(bottomPlateUVs), IM_COL32_WHITE, true); + + // Draw bottom right stretched metal plate. + AddImageFlipped(g_texMainMenu1.get(), { bottomPlateRightMax.x, bottomPlateRightMin.y }, { res.x, bottomPlateRightMax.y }, GET_UV_COORDS(bottomPlateStretchUVs), IM_COL32_WHITE, true); + + if (App::s_isInit) + SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); + + if (ShowVersionString) + { + auto verAlphaMotionTime = PlayIntroAnim ? ComputeMotion(m_appearTime, 10, 10) : 1.0; + + DrawVersionString(g_fntNewRodin, IM_COL32(0, 0, 0, 70 * verAlphaMotionTime)); + } +} + +void CommonMenu::SetTitle(const char* title, bool isAnimated) +{ + m_previousTitle = Title; + Title = title; + + if (!isAnimated) + return; + + m_titleAppearTime = ImGui::GetTime(); +} + +void CommonMenu::SetDescription(const char* desc, bool isAnimated) +{ + m_previousDesc = Description; + m_previousDescPos = m_descPos; + Description = desc; + + resetDescScroll(); + + if (!isAnimated) + return; + + m_descAppearTime = ImGui::GetTime(); +} diff --git a/MarathonRecomp/ui/common_menu.h b/MarathonRecomp/ui/common_menu.h new file mode 100644 index 000000000..74c7a3a59 --- /dev/null +++ b/MarathonRecomp/ui/common_menu.h @@ -0,0 +1,61 @@ +#pragma once + +#include + +class CommonMenu +{ + const char* m_previousTitle{}; + const char* m_previousDesc{}; + + ImVec2 m_descPos{}; + ImVec2 m_previousDescPos{}; + + double m_appearTime{}; + double m_titleAppearTime{}; + double m_descAppearTime{}; + + bool m_isDescScrolling{}; + bool m_isDescManualScrolling{}; + float m_descScrollOffset{}; + float m_descScrollTimer{}; + float m_descScrollDirection{ 1.0f }; + + class CommonMenuInputListener : public SDLEventListener + { + public: + float RightStickX{}; + + bool OnSDLEvent(SDL_Event* event) override + { + if (event->type == SDL_CONTROLLERAXISMOTION && event->caxis.axis == SDL_CONTROLLER_AXIS_RIGHTX) + RightStickX = event->caxis.value / 32767.0f; + + return false; + } + } + m_inputListener{}; + + void resetDescScroll() + { + m_isDescScrolling = false; + m_descScrollOffset = 0.0f; + m_descScrollTimer = 0.0f; + m_descScrollDirection = 1.0f; + } + +public: + const char* Title{}; + const char* Description{}; + bool PlayIntroAnim{}; + bool ShowVersionString{ true }; + + CommonMenu() {} + + CommonMenu(const char* title, const char* desc = nullptr, bool playIntroAnim = false, bool showVersionString = true) + : Title(title), Description(desc), PlayIntroAnim(playIntroAnim), ShowVersionString(showVersionString) {} + + void Open(); + void Draw(); + void SetTitle(const char* title, bool isAnimated = true); + void SetDescription(const char* desc, bool isAnimated = true); +}; diff --git a/MarathonRecomp/ui/imgui_utils.cpp b/MarathonRecomp/ui/imgui_utils.cpp index e012114a5..2ac67e0f9 100644 --- a/MarathonRecomp/ui/imgui_utils.cpp +++ b/MarathonRecomp/ui/imgui_utils.cpp @@ -1,4 +1,5 @@ #include "imgui_utils.h" +#include #include #include #include @@ -11,6 +12,9 @@ #include #include +ImFont* g_fntRodin; +ImFont* g_fntNewRodin; + std::unique_ptr g_texWindow; std::unique_ptr g_texLight; std::unique_ptr g_texSelect; @@ -20,6 +24,9 @@ std::unique_ptr g_texArrow; void InitImGuiUtils() { + g_fntRodin = ImFontAtlasSnapshot::GetFont("FOT-RodinPro-DB.otf"); + g_fntNewRodin = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-UB.otf"); + g_texWindow = LOAD_ZSTD_TEXTURE(g_window); g_texLight = LOAD_ZSTD_TEXTURE(g_light); g_texSelect = LOAD_ZSTD_TEXTURE(g_select); @@ -169,9 +176,43 @@ void ResetAdditive() SetAdditive(false); } -float Scale(float size) +void AddImageFlipped(ImTextureID texture, const ImVec2& min, const ImVec2& max, const ImVec2& uvMin, const ImVec2& uvMax, ImU32 col, bool flipHorz, bool flipVert) +{ + auto drawList = ImGui::GetBackgroundDrawList(); + + ImVec2 uv0 = uvMin; + ImVec2 uv1 = { uvMax.x, uvMin.y }; + ImVec2 uv2 = uvMax; + ImVec2 uv3 = { uvMin.x, uvMax.y }; + + if (flipHorz) + { + std::swap(uv0.x, uv1.x); + std::swap(uv2.x, uv3.x); + } + + if (flipVert) + { + std::swap(uv0.y, uv3.y); + std::swap(uv1.y, uv2.y); + } + + ImVec2 p0 = min; + ImVec2 p1 = { max.x, min.y }; + ImVec2 p2 = max; + ImVec2 p3 = { min.x, max.y }; + + drawList->AddImageQuad(texture, p0, p1, p2, p3, uv0, uv1, uv2, uv3, col); +} + +float Scale(float size, bool useGameplayScale) { - return size * g_aspectRatioScale; + auto result = size * g_aspectRatioScale; + + if (useGameplayScale) + result *= g_aspectRatioGameplayScale; + + return result; } double ComputeLoopMotion(double time, double offset, double total) @@ -268,81 +309,18 @@ void DrawArrows(ImVec2 min, ImVec2 max) } } -void DrawHUD(ImVec2 min, ImVec2 max, const ImFont* font, const char* text) +static double ImValueDebug(double& value, double increment = 1.0) { - auto drawList = ImGui::GetBackgroundDrawList(); - - auto leftWidth = Scale(200); - auto stripeHeight = Scale(50); - auto offset = Scale(81); - - auto leftStripe = PIXELS_TO_UV_COORDS(1024, 1024, 0, 300, 200, 50); - auto centerStripe = PIXELS_TO_UV_COORDS(1024, 1024, 200, 300, 824, 50); - - auto stripeColor = IM_COL32(168, 15, 15, 255); - - drawList->AddImage(g_texMainMenu1.get(), { min.x, min.y + offset }, { min.x + leftWidth, min.y + offset + stripeHeight }, GET_UV_COORDS(leftStripe), stripeColor); - drawList->AddImage(g_texMainMenu1.get(), { min.x + leftWidth , min.y + offset }, { max.x, min.y + offset + stripeHeight }, GET_UV_COORDS(centerStripe), stripeColor); - - auto stripOffset = Scale(78); - auto stripUV = PIXELS_TO_UV_COORDS(1024, 1024, 201, 501, 264, 50); - - auto strip1LeftOffset = leftWidth + Scale(180); - - auto tlStrip1Color = IM_COL32(255, 172, 0, 0); - auto brStrip1Color = IM_COL32(255, 172, 0, 67); - - ImVec2 strip1Min = { min.x + strip1LeftOffset, min.y + stripOffset }; - ImVec2 strip1Max = { min.x + strip1LeftOffset + Scale(270), min.y + stripOffset + stripeHeight }; - - SetHorizontalGradient(strip1Min, strip1Max, tlStrip1Color, brStrip1Color); - - drawList->AddImage(g_texMainMenu1.get(), strip1Min, strip1Max, GET_UV_COORDS(stripUV), IM_COL32_WHITE); - - ResetGradient(); - - auto strip2LeftOffset = leftWidth + Scale(40); - - auto tlStrip2Color = IM_COL32(255, 116, 0, 0); - auto brStrip2Color = IM_COL32(255, 116, 0, 17); - - ImVec2 strip2Min = { min.x + strip2LeftOffset, min.y + stripOffset }; - ImVec2 strip2Max = { min.x + strip2LeftOffset + Scale(270), min.y + stripOffset + stripeHeight }; - - SetHorizontalGradient(strip2Min, strip2Max, tlStrip2Color, brStrip2Color); - - drawList->AddImage(g_texMainMenu1.get(), strip2Min, strip2Max, GET_UV_COORDS(stripUV), IM_COL32_WHITE); - - ResetGradient(); - - auto backBarHeight = Scale(150); - auto backBarColor = IM_COL32(0, 23, 57, 255); - - drawList->AddRectFilled({ min.x, max.y - backBarHeight }, { max.x, max.y }, backBarColor); - - auto leftSilver = PIXELS_TO_UV_COORDS(1024, 1024, 0, 154, 200, 116); - auto centerSilver = PIXELS_TO_UV_COORDS(1024, 1024, 200, 154, 824, 116); - - auto silverHeight = Scale(116); - - drawList->AddImage(g_texMainMenu1.get(), { min.x - Scale(1), min.y }, { min.x - Scale(1) + leftWidth, min.y + silverHeight }, GET_UV_COORDS(leftSilver), IM_COL32_WHITE); - drawList->AddImage(g_texMainMenu1.get(), { min.x - Scale(1) + leftWidth, min.y }, { max.x, min.y + silverHeight }, GET_UV_COORDS(centerSilver), IM_COL32_WHITE); - - auto centerBottomBar = PIXELS_TO_UV_COORDS(1024, 1024, 180, 60, 844, 55); - - auto bottomBarHeight = Scale(55); - auto bottomTabWidth = Scale(180); - - drawList->AddImage(g_texMainMenu1.get(), { min.x + bottomTabWidth, max.y - bottomBarHeight }, { max.x - bottomTabWidth, max.y }, GET_UV_COORDS(centerBottomBar), IM_COL32_WHITE); - - auto bottomTab = PIXELS_TO_UV_COORDS(1024, 1024, 0, 0, 180, 115); - - auto bottomTabHeight = Scale(115); +#ifdef _WIN32 + if (GetAsyncKeyState(VK_OEM_PLUS) & 1) + value += increment; + if (GetAsyncKeyState(VK_OEM_MINUS) & 1) + value -= increment; +#endif - drawList->AddImage(g_texMainMenu1.get(), { min.x, max.y - bottomTabHeight }, { min.x + bottomTabWidth, max.y }, GET_UV_COORDS(bottomTab), IM_COL32_WHITE); - drawList->AddImage(g_texMainMenu1.get(), { max.x, max.y - bottomTabHeight }, { max.x - bottomTabWidth, max.y }, GET_UV_COORDS(bottomTab), IM_COL32_WHITE); + LOGF_UTILITY("{}", value); - drawList->AddText(font, Scale(32), { min.x + leftWidth + Scale(2), min.y + offset + Scale(5) }, IM_COL32_WHITE, text); + return value; } void DrawContainerBox(ImVec2 min, ImVec2 max, float alpha) @@ -694,6 +672,7 @@ void DrawVersionString(const ImFont* font, const ImU32 col) auto textMargin = Scale(2); auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, g_versionString); + drawList->AddText(font, fontSize, { textMargin, res.y - textSize.y - textMargin }, col, "WORK IN PROGRESS"); drawList->AddText(font, fontSize, { res.x - textSize.x - textMargin, res.y - textSize.y - textMargin }, col, g_versionString); } diff --git a/MarathonRecomp/ui/imgui_utils.h b/MarathonRecomp/ui/imgui_utils.h index 4cd0a4812..12fc9efbf 100644 --- a/MarathonRecomp/ui/imgui_utils.h +++ b/MarathonRecomp/ui/imgui_utils.h @@ -14,10 +14,14 @@ #define BREATHE_MOTION(start, end, time, rate) Lerp(start, end, (sin((ImGui::GetTime() - time) * (2.0f * M_PI / rate)) + 1.0f) / 2.0f) +extern ImFont* g_fntRodin; +extern ImFont* g_fntNewRodin; + extern std::unique_ptr g_texWindow; extern std::unique_ptr g_texLight; extern std::unique_ptr g_texSelect; extern std::unique_ptr g_texSelectArrow; +extern std::unique_ptr g_texMainMenu1; void InitImGuiUtils(); @@ -41,12 +45,12 @@ void SetProceduralOrigin(ImVec2 proceduralOrigin); void ResetProceduralOrigin(); void SetAdditive(bool enabled); void ResetAdditive(); -float Scale(float size); +void AddImageFlipped(ImTextureID texture, const ImVec2& min, const ImVec2& max, const ImVec2& uvMin = { 0, 0 }, const ImVec2& uvMax = { 0, 0 }, ImU32 col = IM_COL32_WHITE, bool flipHorz = false, bool flipVert = false); +float Scale(float size, bool useGameplayScale = false); 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 DrawTextBasic(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 colour, const char* text); 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); diff --git a/MarathonRecomp/ui/installer_wizard.cpp b/MarathonRecomp/ui/installer_wizard.cpp index 5434f2c5e..17b04880e 100644 --- a/MarathonRecomp/ui/installer_wizard.cpp +++ b/MarathonRecomp/ui/installer_wizard.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -69,8 +70,7 @@ constexpr float BORDER_OVERSHOOT = 36.0f; static constexpr size_t GRID_SIZE = 9; -static ImFont *g_rodinFont; -static ImFont *g_newRodinFont; +static CommonMenu g_commonMenu; static double g_appearTime = 0.0; static double g_disappearTime = DBL_MAX; @@ -473,19 +473,6 @@ static void DrawLeftImage() drawList->AddImage(guestTexture, min, max, ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, a)); } -static void DrawHUD() -{ - auto &res = ImGui::GetIO().DisplaySize; - - // Installer text - auto& headerText = Localise(g_currentPage == WizardPage::Installing ? "Installer_Header_Installing" : "Installer_Header_Installer"); - auto alphaMotion = ComputeMotionInstaller(g_appearTime, g_disappearTime, TITLE_ANIMATION_TIME, TITLE_ANIMATION_DURATION); - - DrawHUD({0, 0}, res, g_newRodinFont, headerText.c_str()); - - DrawVersionString(g_newRodinFont, IM_COL32(0, 0, 0, 70 * alphaMotion)); -} - static void DrawContainer(ImVec2 min, ImVec2 max, bool isTextArea) { auto &res = ImGui::GetIO().DisplaySize; @@ -562,7 +549,7 @@ static void DrawDescriptionContainer() DrawTextParagraph ( - g_rodinFont, + g_fntRodin, fontSize, lineWidth, { textX, textY }, @@ -571,7 +558,7 @@ static void DrawDescriptionContainer() [=](const char* str, ImVec2 pos) { - DrawTextBasic(g_rodinFont, fontSize, pos, IM_COL32(255, 255, 255, 255 * textAlpha), str); + DrawTextBasic(g_fntRodin, fontSize, pos, IM_COL32(255, 255, 255, 255 * textAlpha), str); } ); @@ -580,10 +567,10 @@ static void DrawDescriptionContainer() if (g_currentPage == WizardPage::InstallSucceeded) { - auto descTextSize = MeasureCentredParagraph(g_rodinFont, fontSize, lineWidth, lineMargin, descriptionText); + auto descTextSize = MeasureCentredParagraph(g_fntRodin, fontSize, lineWidth, lineMargin, descriptionText); auto hedgeDevStr = "hedge-dev"; - auto hedgeDevTextSize = g_rodinFont->CalcTextSizeA(fontSize, FLT_MAX, 0, hedgeDevStr); + auto hedgeDevTextSize = g_fntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, hedgeDevStr); auto hedgeDevTextMarginX = Scale(15); auto colWhite = IM_COL32(255, 255, 255, 255 * textAlpha); @@ -593,7 +580,7 @@ static void DrawDescriptionContainer() auto containerRight = containerLeft + Scale(CONTAINER_WIDTH); auto containerBottom = containerTop + Scale(CONTAINER_HEIGHT); - auto marqueeTextSize = g_rodinFont->CalcTextSizeA(fontSize, FLT_MAX, 0, g_creditsStr.c_str()); + auto marqueeTextSize = g_fntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, g_creditsStr.c_str()); auto marqueeTextMarginX = Scale(5); auto marqueeTextMarginY = Scale(15); @@ -619,7 +606,7 @@ static void DrawDescriptionContainer() drawList->AddText ( - g_rodinFont, + g_fntRodin, fontSize, { /* X */ imageMax.x + hedgeDevTextMarginX, /* Y */ imageMin.y + (imageScale / 2) - (hedgeDevTextSize.y / 2) }, colWhite, @@ -627,7 +614,7 @@ static void DrawDescriptionContainer() ); SetHorizontalMarqueeFade(marqueeTextMin, marqueeTextMax, Scale(32)); - DrawTextWithMarquee(g_rodinFont, fontSize, marqueeTextPos, marqueeTextMin, marqueeTextMax, colWhite, g_creditsStr.c_str(), g_installerEndTime, 0.9, Scale(200)); + DrawTextWithMarquee(g_fntRodin, fontSize, marqueeTextPos, marqueeTextMin, marqueeTextMax, colWhite, g_creditsStr.c_str(), g_installerEndTime, 0.9, Scale(200)); ResetMarqueeFade(); } @@ -737,7 +724,7 @@ static void DrawButton(ImVec2 min, ImVec2 max, const char *buttonText, bool sour DrawButtonContainer(min, max, baser, baseg, alpha); - ImFont *font = g_rodinFont; + ImFont *font = g_fntRodin; float size = Scale(18.0f); float squashRatio; ImVec2 textSize = ComputeTextSize(font, buttonText, size, squashRatio, Scale(maxTextWidth)); @@ -1011,7 +998,10 @@ static void DrawLanguagePicker() DrawToggleLight({ min.x + lightSize, min.y + ((max.y - min.y) - lightSize) / 2 + Scale(1) }, Config::Language == LANGUAGE_ENUM[i], alphaMotion); if (buttonPressed) + { Config::Language = LANGUAGE_ENUM[i]; + g_commonMenu.SetTitle(Localise("Installer_Header_Installer").c_str()); + } } } } @@ -1025,7 +1015,7 @@ static void DrawSourcePickers() constexpr float ADD_BUTTON_MAX_TEXT_WIDTH = 168.0f; const std::string &addFilesText = Localise("Installer_Button_AddFiles"); float squashRatio; - ImVec2 textSize = ComputeTextSize(g_rodinFont, addFilesText.c_str(), 20.0f, squashRatio, ADD_BUTTON_MAX_TEXT_WIDTH); + ImVec2 textSize = ComputeTextSize(g_fntRodin, addFilesText.c_str(), 20.0f, squashRatio, ADD_BUTTON_MAX_TEXT_WIDTH); ImVec2 min = { g_aspectRatioOffsetX + Scale(CONTAINER_X + BOTTOM_X_GAP), g_aspectRatioOffsetY + Scale(CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP) }; ImVec2 max = { g_aspectRatioOffsetX + Scale(CONTAINER_X + BOTTOM_X_GAP + textSize.x * squashRatio + BUTTON_TEXT_GAP), g_aspectRatioOffsetY + Scale(CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP + BUTTON_HEIGHT) }; DrawButton(min, max, addFilesText.c_str(), false, true, buttonPressed, ADD_BUTTON_MAX_TEXT_WIDTH); @@ -1037,7 +1027,7 @@ static void DrawSourcePickers() min.x += Scale(BOTTOM_X_GAP + textSize.x * squashRatio + BUTTON_TEXT_GAP); const std::string &addFolderText = Localise("Installer_Button_AddFolder"); - textSize = ComputeTextSize(g_rodinFont, addFolderText.c_str(), 20.0f, squashRatio, ADD_BUTTON_MAX_TEXT_WIDTH); + textSize = ComputeTextSize(g_fntRodin, addFolderText.c_str(), 20.0f, squashRatio, ADD_BUTTON_MAX_TEXT_WIDTH); max.x = min.x + Scale(textSize.x * squashRatio + BUTTON_TEXT_GAP); DrawButton(min, max, addFolderText.c_str(), false, true, buttonPressed, ADD_BUTTON_MAX_TEXT_WIDTH); if (buttonPressed) @@ -1078,6 +1068,7 @@ static void DrawInstallingProgress() g_installerThread.reset(); g_installerEndTime = ImGui::GetTime(); g_currentPage = g_installerFailed ? WizardPage::InstallFailed : WizardPage::InstallSucceeded; + g_commonMenu.SetTitle(Localise("Installer_Header_Installer").c_str()); } } } @@ -1115,6 +1106,7 @@ static void InstallerStart() g_installerFailed = false; g_installerFinished = false; g_installerThread = std::make_unique(InstallerThread); + g_commonMenu.SetTitle(Localise("Installer_Header_Installing").c_str()); } static bool InstallerParseSources(std::string &errorMessage) @@ -1175,7 +1167,7 @@ static void DrawNavigationButton() } const std::string &nextButtonText = Localise(nextButtonKey); - ImVec2 nextTextSize = ComputeTextSize(g_newRodinFont, nextButtonText.c_str(), 20.0f, squashRatio, NAV_BUTTON_MAX_TEXT_WIDTH); + ImVec2 nextTextSize = ComputeTextSize(g_fntRodin, nextButtonText.c_str(), 20.0f, squashRatio, NAV_BUTTON_MAX_TEXT_WIDTH); ImVec2 min = { g_aspectRatioOffsetX + Scale(CONTAINER_X + CONTAINER_WIDTH - nextTextSize.x * squashRatio - BOTTOM_X_GAP - BUTTON_TEXT_GAP), g_aspectRatioOffsetY + Scale(CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP) }; ImVec2 max = { g_aspectRatioOffsetX + Scale(CONTAINER_X + CONTAINER_WIDTH - BOTTOM_X_GAP), g_aspectRatioOffsetY + Scale(CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP + BUTTON_HEIGHT) }; @@ -1400,8 +1392,7 @@ void InstallerWizard::Init() { auto &io = ImGui::GetIO(); - g_rodinFont = ImFontAtlasSnapshot::GetFont("FOT-RodinPro-DB.otf"); - g_newRodinFont = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-UB.otf"); + g_commonMenu = CommonMenu(Localise("Installer_Header_Installer").c_str(), nullptr, true); g_installTextures[0] = LOAD_ZSTD_TEXTURE(g_install_001); g_installTextures[1] = LOAD_ZSTD_TEXTURE(g_install_002); g_installTextures[2] = LOAD_ZSTD_TEXTURE(g_install_003); @@ -1430,7 +1421,7 @@ void InstallerWizard::Draw() DrawBackground(); DrawArrows(); DrawLeftImage(); - DrawHUD(); + g_commonMenu.Draw(); DrawDescriptionContainer(); DrawLanguagePicker(); DrawSourcePickers(); diff --git a/MarathonRecomp/ui/options_menu.cpp b/MarathonRecomp/ui/options_menu.cpp index a1b6d3437..715b4e3e6 100644 --- a/MarathonRecomp/ui/options_menu.cpp +++ b/MarathonRecomp/ui/options_menu.cpp @@ -1,1802 +1,69 @@ #include "options_menu.h" -#include "options_menu_thumbnails.h" -#include "tv_static.h" - -#include -#include -#include -#include -#include -#include -#include -#include #include #include -#include -#include -#include +#include #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 VALUE_SLIDER_INTRO_DURATION = 20.0; - -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; - -static constexpr float CONTAINER_POS_Y = 117.0f; - -static constexpr float SETTINGS_WIDE_GRID_COUNT = 90.0f; -static constexpr float INFO_WIDE_GRID_COUNT = 42.0f; -static constexpr float PADDING_WIDE_GRID_COUNT = 3.0f; - -static constexpr float SETTINGS_NARROW_GRID_COUNT = 70.0f; -static constexpr float INFO_NARROW_GRID_COUNT = 34.0f; -static constexpr float PADDING_NARROW_GRID_COUNT = 1.0f; - -static constexpr float INFO_TEXT_MARQUEE_DELAY = 1.2f; - -static constexpr int32_t g_categoryCount = 4; -static int32_t g_categoryIndex; -static ImVec2 g_categoryAnimMin; -static ImVec2 g_categoryAnimMax; - -static int32_t g_firstVisibleRowIndex; -static int32_t g_prevSelectedRowIndex; -static int32_t g_selectedRowIndex; -static double g_rowSelectionTime; -static float g_prevOffsetRatio; - -static bool g_leftWasHeld; -static bool g_upWasHeld; -static bool g_rightWasHeld; -static bool g_downWasHeld; - -static bool g_lockedOnOption; -static double g_lockedOnTime; -static double g_lastTappedTime; -static double g_lastIncrementTime; -static double g_lastIncrementSoundTime; - -static constexpr size_t GRID_SIZE = 9; - -static ImFont* g_rodinFont; -static ImFont* g_newRodinFont; - -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 bool g_titleAnimBegin = true; +#include +static CommonMenu g_commonMenu; static double g_appearTime = 0.0; -static std::unique_ptr g_upMilesElectric; - -static float g_rightStickY; - -class SDLEventListenerForOptionsMenu : public SDLEventListener +void OptionsMenu::Draw() { -public: - bool OnSDLEvent(SDL_Event* event) override - { - if (!OptionsMenu::s_isVisible || !hid::IsInputAllowed()) - return false; - - if (event->type == SDL_CONTROLLERAXISMOTION && event->caxis.axis == SDL_CONTROLLER_AXIS_RIGHTY) - g_rightStickY = event->caxis.value / 32767.0f; - - return false; - } -} -g_sdlEventListenerForOptionsMenu; - -static void DrawTitle() -{ - static constexpr double fadeOffset = 3.0; - static constexpr double fadeDuration = 28.0; - static constexpr double fadeAdditiveDuration = 20.0; - static constexpr double squareMoveDuration = 5.0; - static constexpr double squareMoveEndDuration = 40.0; - static constexpr double squareBlinkDuration = 10.0; - static constexpr double squareFadeDuration = 45.0; - - static bool isRectVisible; - static bool isRectFinalAdjustment; - static float rectX; - static double rectMoveMotionOffset; - static double rectBlinkMotionOffset; - - if (g_titleAnimBegin) - { - isRectVisible = true; - isRectFinalAdjustment = false; - rectX = 0; - rectMoveMotionOffset = 0; - rectBlinkMotionOffset = 0; - g_titleAnimBegin = false; - } - - auto drawList = ImGui::GetBackgroundDrawList(); - auto x = Scale(122); - auto y = Scale(56); - - if (g_aspectRatio >= WIDE_ASPECT_RATIO) - x += g_aspectRatioOffsetX; - else - x += (1.0f - g_aspectRatioNarrowScale) * g_aspectRatioScale * -20.0f; - - ImVec2 optionsMin = { x, y }; - - auto drawText = [&](float alpha) - { - DrawTextWithOutline - ( - g_newRodinFont, - Scale(48), - optionsMin, - IM_COL32(255, 190, 33, 255 * alpha), - Localise("Options_Header_Name").c_str(), - 4, - IM_COL32(0, 0, 0, 255 * alpha), - IMGUI_SHADER_MODIFIER_TITLE_BEVEL - ); - }; - - if (g_isStage) - { - drawText(1.0f); + if (!s_isVisible) return; - } - - drawText(Hermite(0.0f, 1.0f, ComputeMotion(g_appearTime, fadeOffset, fadeDuration))); - auto rectMoveMotion = ComputeMotion(g_appearTime, rectMoveMotionOffset, squareMoveDuration); - auto rectEndMotion = ComputeMotion(g_appearTime, 0.0, squareMoveEndDuration); - auto rectBlinkMotion = sin(ComputeMotion(g_appearTime, squareMoveEndDuration + rectBlinkMotionOffset, squareBlinkDuration) * M_PI); - auto rectAlphaMotion = 1.0f; - auto rectY = Scale(10); - auto rectSize = Scale(32); - - if (rectBlinkMotion > 0.0) - { - if (!isRectFinalAdjustment) - { - /* Ensure the blinking animation starts - past the German localisation. */ - rectX += rectSize + (rectSize * 0.25f); - isRectFinalAdjustment = true; - } - - isRectVisible = !isRectVisible; - rectBlinkMotionOffset += squareBlinkDuration; - } - - if (rectEndMotion >= 1.0) - { - // Fade out the square. - rectAlphaMotion = 1.0f - ComputeMotion(g_appearTime, squareMoveEndDuration + squareBlinkDuration, squareFadeDuration); - } - else if (rectMoveMotion >= 1.0) + switch (s_state) { - // Move the square along in steps by its own width and offset the animation to repeat. - rectX += rectSize; - rectMoveMotionOffset += squareMoveDuration; - } + case OptionsMenuState::Opening: + g_commonMenu.Open(); + s_state = OptionsMenuState::Idle; + break; - if (isRectVisible) - { - float rectScale = 1.0f; - if (rectBlinkMotion == 0.0) + default: { - constexpr float RECT_SCALES[] = { 1.2f, 1.1f, 1.1f, 1.0f, 1.15f, 0.4f, 1.2f, 1.1f, 1.05f, 1.0f, 1.5f, 1.2f, 1.0f }; - rectScale = RECT_SCALES[uint32_t(round(rectX / rectSize)) % std::size(RECT_SCALES)]; - } - - ImVec2 rectMin = { optionsMin.x + rectX, optionsMin.y + rectY }; - ImVec2 rectMax = { optionsMin.x + rectX + rectSize * rectScale, optionsMin.y + rectY + rectSize }; - - auto rectOutlineMargin = Scale(2.5f); - - ImVec2 rectOutlineMin = { rectMin.x - rectOutlineMargin, rectMin.y - rectOutlineMargin }; - ImVec2 rectOutlineMax = { rectMax.x + rectOutlineMargin, rectMax.y + rectOutlineMargin }; + auto* drawList = ImGui::GetBackgroundDrawList(); + auto& res = ImGui::GetIO().DisplaySize; - drawList->AddRectFilled(rectOutlineMin, rectOutlineMax, IM_COL32(0, 0, 0, 255 * rectAlphaMotion), Scale(5)); + auto alphaMotionTime = s_isPause ? ComputeMotion(g_appearTime, 0, 10) : 0.0; + auto alpha = s_isPause ? Lerp(0, 175, alphaMotionTime) : 255; + auto gradientTop = IM_COL32(0, 103, 255, alpha); + auto gradientBottom = IM_COL32(0, 41, 100, alpha); - SetShaderModifier(IMGUI_SHADER_MODIFIER_RECTANGLE_BEVEL); - SetGradient(rectMin, rectMax, IM_COL32_WHITE, IM_COL32_WHITE); + drawList->AddRectFilledMultiColor({ 0.0f, g_letterboxHeight }, { res.x, res.y - g_letterboxHeight }, gradientTop, gradientTop, gradientBottom, gradientBottom); - drawList->AddRectFilled(rectMin, rectMax, IM_COL32(255, 188, 0, 255 * rectAlphaMotion)); + DrawArrows({ 0, res.y / 2 - Scale(10) }, res); + g_commonMenu.Draw(); - ResetGradient(); - SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); - } - - // The flash gets rendered after the rectangle in the original game. - SetAdditive(true); - drawText(1.0 - 2.0 * abs(ComputeLinearMotion(g_appearTime, fadeDuration, fadeAdditiveDuration) - 0.5)); - SetAdditive(false); -} - -static void DrawScanlineBars() -{ - constexpr uint32_t COLOR0 = IM_COL32(203, 255, 0, 0); - constexpr uint32_t COLOR1 = IM_COL32(203, 255, 0, 55); - constexpr uint32_t FADE_COLOR0 = IM_COL32(0, 0, 0, 255); - constexpr uint32_t FADE_COLOR1 = IM_COL32(0, 0, 0, 0); - - float height = Scale(105.0f); - - auto& res = ImGui::GetIO().DisplaySize; - auto drawList = ImGui::GetBackgroundDrawList(); - -// if (OptionsMenu::s_pauseMenuType != SWA::eMenuType_WorldMap) -// { -// // Top bar fade -// drawList->AddRectFilledMultiColor -// ( -// { 0.0f, 0.0f }, -// { res.x, height }, -// FADE_COLOR0, -// FADE_COLOR0, -// FADE_COLOR1, -// FADE_COLOR1 -// ); -// -// // Bottom bar fade -// drawList->AddRectFilledMultiColor -// ( -// { res.x, res.y }, -// { 0.0f, res.y - height }, -// FADE_COLOR0, -// FADE_COLOR0, -// FADE_COLOR1, -// FADE_COLOR1 -// ); -// } - - // Top bar - drawList->AddRectFilledMultiColor - ( - { 0.0f, 0.0f }, - { res.x, height }, - COLOR0, - COLOR0, - COLOR1, - COLOR1 - ); - - // Bottom bar - ImVec2 max{ 0.0f, res.y - height }; - SetProceduralOrigin(max); - - drawList->AddRectFilledMultiColor - ( - { res.x, res.y }, - max, - COLOR0, - COLOR0, - COLOR1, - COLOR1 - ); - - ResetProceduralOrigin(); - - DrawTitle(); - - auto drawLine = [&](bool top) - { - float y = top ? height : (res.y - height); - - constexpr uint32_t TOP_COLOR0 = IM_COL32(222, 255, 189, 7); - constexpr uint32_t TOP_COLOR1 = IM_COL32(222, 255, 189, 65); - constexpr uint32_t BOTTOM_COLOR0 = IM_COL32(173, 255, 156, 65); - constexpr uint32_t BOTTOM_COLOR1 = IM_COL32(173, 255, 156, 7); - - drawList->AddRectFilledMultiColor( - { 0.0f, y - Scale(2.0f) }, - { res.x, y }, - top ? TOP_COLOR0 : BOTTOM_COLOR1, - top ? TOP_COLOR0 : BOTTOM_COLOR1, - top ? TOP_COLOR1 : BOTTOM_COLOR0, - top ? TOP_COLOR1 : BOTTOM_COLOR0); - - drawList->AddRectFilledMultiColor( - { 0.0f, y + Scale(1.0f) }, - { res.x, y + Scale(3.0f) }, - top ? BOTTOM_COLOR0 : TOP_COLOR1, - top ? BOTTOM_COLOR0 : TOP_COLOR1, - top ? BOTTOM_COLOR1 : TOP_COLOR0, - top ? BOTTOM_COLOR1 : TOP_COLOR0); - - constexpr uint32_t CENTER_COLOR = IM_COL32(115, 178, 104, 255); - drawList->AddRectFilled({ 0.0f, y }, { res.x, y + Scale(1.0f) }, CENTER_COLOR); - }; - - // Top bar line - drawLine(true); - - // Bottom bar line - drawLine(false); - - DrawVersionString(g_newRodinFont); -} - -static float AlignToNextGrid(float value) -{ - return round(value / GRID_SIZE) * GRID_SIZE; -} - -static void DrawContainer(ImVec2 min, ImVec2 max, bool drawRightOutline) -{ - 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); - max.y = Lerp(center, max.y, containerHeight); - - auto& res = ImGui::GetIO().DisplaySize; - auto drawList = ImGui::GetBackgroundDrawList(); - - 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); - const uint32_t innerColor = IM_COL32(0, 33, 0, 255 * innerAlpha); - const uint32_t backgroundColor = IM_COL32(0, 0, 0, 223 * backgroundAlpha); - - float gridSize = Scale(GRID_SIZE); - - drawList->AddRectFilled(min, max, backgroundColor); // Background - - float lineSize = Scale(2); - - // Top line - drawList->AddLine({ min.x + gridSize, min.y + gridSize }, { min.x + gridSize, min.y + gridSize * 2.0f }, lineColor, lineSize); // Vertical left - drawList->AddLine({ min.x + gridSize, min.y + gridSize }, { max.x - gridSize, min.y + gridSize }, lineColor, lineSize); // Horizontal - drawList->AddLine({ max.x - gridSize, min.y + gridSize }, { max.x - gridSize, min.y + gridSize * 2.0f }, lineColor, lineSize); // Vertical right - - // Bottom line - drawList->AddLine({ min.x + gridSize, max.y - gridSize }, { min.x + gridSize, max.y - gridSize * 2.0f }, lineColor, lineSize); // Vertical left - drawList->AddLine({ min.x + gridSize, max.y - gridSize }, { max.x - gridSize, max.y - gridSize }, lineColor, lineSize); // Horizontal - drawList->AddLine({ max.x - gridSize, max.y - gridSize }, { max.x - gridSize, max.y - gridSize * 2.0f }, lineColor, lineSize); // Vertical right - - // The draw area - 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 std::string& GetCategory(int index) -{ - // TODO: Don't use raw numbers here! - switch (index) - { - case 0: return Localise("Options_Category_System"); - case 1: return Localise("Options_Category_Input"); - case 2: return Localise("Options_Category_Audio"); - case 3: return Localise("Options_Category_Video"); - } - - return g_localeMissing; -} - -static void ResetSelection() -{ - g_firstVisibleRowIndex = 0; - g_selectedRowIndex = 0; - g_prevSelectedRowIndex = 0; - g_rowSelectionTime = ImGui::GetTime(); - g_prevOffsetRatio = 0.0f; - g_leftWasHeld = false; - g_upWasHeld = false; - g_rightWasHeld = false; - g_downWasHeld = false; - g_canReset = false; -} - -//static bool DrawCategories() -//{ -// double motion = g_isStage ? 1.0 : ComputeMotion(g_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 moveRight = !g_lockedOnOption && inputState->GetPadState().IsTapped(SWA::eKeyState_RightBumper); -// -// if (moveLeft) -// { -// --g_categoryIndex; -// if (g_categoryIndex < 0) -// g_categoryIndex = g_categoryCount - 1; -// } -// else if (moveRight) -// { -// ++g_categoryIndex; -// if (g_categoryIndex >= g_categoryCount) -// g_categoryIndex = 0; -// } -// -// if (moveLeft || moveRight) -// { -// ResetSelection(); -// Game_PlaySound("sys_actstg_score"); -// } -// -// auto drawList = ImGui::GetBackgroundDrawList(); -// auto clipRectMin = drawList->GetClipRectMin(); -// auto clipRectMax = drawList->GetClipRectMax(); -// -// float gridSize = Scale(GRID_SIZE); -// -// float size = Scale(32.0f); -// ImVec2 textSizes[g_categoryCount]; -// float clipRectWidth = clipRectMax.x - clipRectMin.x; -// -// float textWidthSum = 0.0f; -// for (size_t i = 0; i < g_categoryCount; i++) -// { -// textSizes[i] = g_newRodinFont->CalcTextSizeA(size, FLT_MAX, 0.0f, GetCategory(i).c_str()); -// textWidthSum += textSizes[i].x; -// } -// -// float textSquashRatio = 1.0f; -// float maxTextWidthSum = clipRectWidth - (gridSize * 4.0f * (g_categoryCount - 1)); -// if (textWidthSum > maxTextWidthSum) -// { -// textSquashRatio = maxTextWidthSum / textWidthSum; -// for (auto& textSize : textSizes) -// textSize.x *= textSquashRatio; -// -// textWidthSum = maxTextWidthSum; -// } -// -// float tabHeight = gridSize * 4.0f; -// float textPadding = (clipRectWidth - textWidthSum) / (g_categoryCount + 1.0f); -// float xOffset = textPadding; -// xOffset -= (1.0 - motion) * gridSize * 4.0; -// -// ImVec2 textPositions[g_categoryCount]; -// -// for (size_t i = 0; i < g_categoryCount; i++) -// { -// float tabPadding = std::min(textPadding / 2.0f, gridSize * 3.0f); -// -// ImVec2 min = { clipRectMin.x + xOffset - tabPadding, clipRectMin.y }; -// ImVec2 max = { min.x + textSizes[i].x + tabPadding * 2.0f, min.y + tabHeight}; -// -// if (g_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) -// { -// g_categoryAnimMin = min; -// g_categoryAnimMax = max; -// } -// else -// { -// float animWidth = g_categoryAnimMax.x - g_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 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 }; -// } -// -// drawList->AddRectFilledMultiColor -// ( -// g_categoryAnimMin, -// g_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) -// ); -// -// drawList->AddRectFilledMultiColor -// ( -// g_categoryAnimMin, -// g_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) -// ); -// -// drawList->AddRectFilledMultiColor -// ( -// g_categoryAnimMin, -// g_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) -// ); -// } -// -// // Store to draw again later, otherwise the tab background gets drawn on top of text during the animation. -// textPositions[i] = { clipRectMin.x + xOffset, clipRectMin.y }; -// xOffset += textSizes[i].x + textPadding; -// } -// -// SetScale({ textSquashRatio, 1.0f }); -// -// for (size_t i = 0; i < g_categoryCount; i++) -// { -// auto& pos = textPositions[i]; -// uint8_t alpha = (i == g_categoryIndex ? 235 : 128) * motion; -// -// SetOrigin({ pos.x, pos.y }); -// SetGradient -// ( -// pos, -// { pos.x + textSizes[i].x, pos.y + textSizes[i].y }, -// IM_COL32(128, 255, 0, alpha), -// IM_COL32(255, 192, 0, alpha) -// ); -// -// DrawTextWithOutline -// ( -// g_newRodinFont, -// size, -// pos, -// IM_COL32_WHITE, -// GetCategory(i).c_str(), -// 4, -// IM_COL32_BLACK, -// IMGUI_SHADER_MODIFIER_CATEGORY_BEVEL -// ); -// } -// -// SetScale({ 1.0f, 1.0f }); -// SetOrigin({ 0.0f, 0.0f }); -// ResetGradient(); -// -// 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; -// } -// -// return false; -//} - -static void DrawSelectionArrows(ImVec2 min, ImVec2 max, bool isLeftTapped, bool isRightTapped, bool isSlider) -{ - static constexpr double sizeMotionDuration = 16.0; - - static bool isLeftArrowMotion = false; - static bool isRightArrowMotion = false; - - auto drawList = ImGui::GetBackgroundDrawList(); - auto gridSize = Scale(GRID_SIZE); - auto width = gridSize * 2.5f; - auto padding = gridSize; - - auto bgMotion = (isLeftArrowMotion || isRightArrowMotion) - ? ComputeMotion(g_lastTappedTime, 8.0, sizeMotionDuration) - : 0; - - if (isLeftTapped) - { - isLeftArrowMotion = true; - isRightArrowMotion = false; - } - - if (isRightTapped) - { - isLeftArrowMotion = false; - isRightArrowMotion = true; - } - - if (bgMotion >= 1.0 || isSlider) - { - isLeftArrowMotion = false; - isRightArrowMotion = false; - } - - auto getBgColour = [&](bool isAnim) -> ImU32 - { - return IM_COL32(0, 97, 0, Lerp(96, 255, isAnim ? bgMotion : 1)); - }; - - auto bgLeftColour = getBgColour(isLeftArrowMotion); - auto bgRightColour = getBgColour(isRightArrowMotion); - - auto invertMotion = isSlider ? ComputeMotion(g_lockedOnTime, 0, VALUE_SLIDER_INTRO_DURATION) : 0; - auto invertMotionX = invertMotion > 0.5 ? 1.0 : 0.0; // Arrow side point invert animation - - auto xAdd = Hermite(0, Scale(10), sin(invertMotion * M_PI)); // Arrow jump animation - auto y = (min.y + max.y) / 2.0f; - - // Left triangle vertices - auto leftX = Lerp(min.x - padding, min.x - padding - width, invertMotionX) - xAdd; - auto leftV1Y = Hermite(min.y, max.y, invertMotion); - auto leftV2Y = Hermite(max.y, min.y, invertMotion); - auto leftV3X = Hermite(min.x - padding - width, min.x - padding, invertMotionX) - xAdd; - - // Right triangle vertices - auto rightX = Lerp(max.x + padding, max.x + padding + width, invertMotionX) + xAdd; - auto rightV1Y = Hermite(max.y, min.y, invertMotion); - auto rightV2Y = Hermite(min.y, max.y, invertMotion); - auto rightV3X = Hermite(max.x + padding + width, max.x + padding, invertMotionX) + xAdd; - - auto drawLeftArrow = [&](ImU32 col) - { - drawList->AddTriangleFilled({ leftX, leftV1Y }, { leftX, leftV2Y }, { leftV3X, y }, col); - }; - - auto drawRightArrow = [&](ImU32 col) - { - drawList->AddTriangleFilled({ rightX, rightV1Y }, { rightX, rightV2Y }, { rightV3X, y }, col); - }; - - drawLeftArrow(bgLeftColour); - drawRightArrow(bgRightColour); - - // Additive gradient colours - auto c0 = IM_COL32(255, 0, 255, 255); - auto c1 = IM_COL32(255, 128, 255, 255); - - SetAdditive(true); - - // Apply additive gradients - SetGradient({ leftX, leftV1Y }, { leftV3X, y }, c0, c1, c1, c0); - drawLeftArrow(bgLeftColour); - SetGradient({ rightX, rightV1Y }, { rightV3X, y }, c0, c1, c1, c0); - drawRightArrow(bgRightColour); - ResetGradient(); - - if (isSlider) - { - auto col = IM_COL32(0, 97, 0, 255 * invertMotion); - - drawLeftArrow(col); - drawRightArrow(col); - } - else - { - auto fgMotion = ComputeMotion(g_lastTappedTime, 0, sizeMotionDuration); - auto fgMotionSine = sin(fgMotion * M_PI); - auto fgScale = Lerp(0, Scale(4), fgMotionSine); - auto fgColour = IM_COL32(0, 97, 0, 255 * fgMotionSine); - - if (isLeftArrowMotion) - { - drawList->AddTriangleFilled - ( - { min.x - padding, min.y - fgScale }, - { min.x - padding, max.y + fgScale }, - { min.x - padding - width - (fgScale + Scale(2)), (min.y + max.y) / 2.0f }, - fgColour - ); - } - - if (isRightArrowMotion) - { - drawList->AddTriangleFilled - ( - { max.x + padding, max.y + fgScale }, - { max.x + padding, min.y - fgScale }, - { max.x + padding + width + (fgScale + Scale(2)), (min.y + max.y) / 2.0f }, - fgColour - ); + break; } } - - SetAdditive(false); } -//template -//static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* config, -// bool isAccessible, std::string* inaccessibleReason = nullptr, -// T valueMin = T(0), T valueCenter = T(0.5), T valueMax = T(1), bool isSlider = true) -//{ -// auto drawList = ImGui::GetBackgroundDrawList(); -// auto clipRectMin = drawList->GetClipRectMin(); -// auto clipRectMax = drawList->GetClipRectMax(); -// auto& padState = SWA::CInputState::GetInstance()->GetPadState(); -// -// constexpr float OPTION_NARROW_GRID_COUNT = 36.0f; -// constexpr float OPTION_WIDE_GRID_COUNT = 54.0f; -// constexpr bool IS_SLIDER_TYPE = std::is_same_v || std::is_same_v; -// -// auto isValueSlider = IS_SLIDER_TYPE && isSlider; -// auto gridSize = Scale(GRID_SIZE); -// auto optionWidth = gridSize * floor(Lerp(OPTION_NARROW_GRID_COUNT, OPTION_WIDE_GRID_COUNT, g_aspectRatioNarrowScale)); -// auto optionHeight = gridSize * 5.5f; -// auto optionPadding = gridSize * 0.5f; -// auto valueWidth = Scale(192.0f); -// auto valueHeight = gridSize * 3.0f; -// -// // Left side -// ImVec2 min = { clipRectMin.x, clipRectMin.y + (optionHeight + optionPadding) * rowIndex + yOffset }; -// ImVec2 max = { min.x + optionWidth, min.y + optionHeight }; -// -// auto configName = config->GetNameLocalised(Config::Language); -// auto size = Scale(26.0f); -// auto textSize = g_rodinFont->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) -// { -// g_selectedItem = config; -// g_inaccessibleReason = isAccessible ? nullptr : inaccessibleReason; -// -// if (!g_isEnterKeyBuffered) -// { -// if (isAccessible) -// { -// if constexpr (std::is_same_v) -// { -// if (padState.IsTapped(SWA::eKeyState_A)) -// { -// config->Value = !config->Value; -// -// if (config->Callback) -// config->Callback(config); -// -// VideoConfigValueChangedCallback(config); -// XAudioConfigValueChangedCallback(config); -// -// Game_PlaySound("sys_worldmap_finaldecide"); -// } -// } -// else -// { -// static T s_oldValue; -// -// if (padState.IsTapped(SWA::eKeyState_A)) -// { -// g_lockedOnOption ^= true; -// -// if (g_lockedOnOption) -// { -// g_lockedOnTime = ImGui::GetTime(); -// g_leftWasHeld = false; -// g_rightWasHeld = false; -// -// // remember value -// s_oldValue = config->Value; -// -// if (config->LockCallback) -// config->LockCallback(config); -// -// Game_PlaySound("sys_worldmap_decide"); -// } -// else -// { -// // released lock, do callbacks if value is different -// if (config->Value != s_oldValue) -// { -// VideoConfigValueChangedCallback(config); -// XAudioConfigValueChangedCallback(config); -// -// if (config->ApplyCallback) -// config->ApplyCallback(config); -// } -// -// Game_PlaySound("sys_worldmap_finaldecide"); -// } -// } -// else if (padState.IsTapped(SWA::eKeyState_B)) -// { -// // released lock, restore old value -// config->Value = s_oldValue; -// -// g_lockedOnOption = false; -// -// Game_PlaySound("sys_worldmap_cansel"); -// } -// -// lockedOnOption = g_lockedOnOption; -// } -// -// if (g_canReset && padState.IsTapped(SWA::eKeyState_X)) -// { -// if (!config->IsDefaultValue()) -// { -// config->MakeDefault(); -// -// VideoConfigValueChangedCallback(config); -// XAudioConfigValueChangedCallback(config); -// -// if (config->Callback) -// config->Callback(config); -// -// if (config->ApplyCallback) -// config->ApplyCallback(config); -// } -// -// Game_PlaySound("sys_worldmap_decide"); -// } -// } -// else -// { -// if (padState.IsTapped(SWA::eKeyState_A)) -// Game_PlaySound("sys_actstg_stateserror"); -// } -// } -// } -// -// auto fadedOut = (g_lockedOnOption && g_selectedItem != config) || !isAccessible; -// auto alpha = fadedOut ? 0.5f : 1.0f; -// auto textColour = IM_COL32(255, 255, 255, 255 * alpha); -// -// if (Config::Language == ELanguage::Japanese) -// textPos.y += Scale(10.0f); -// -// if (g_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); -// prevItemOffset *= pow(1.0 - animRatio, 3.0); -// -// auto c0 = IM_COL32(0xE2, 0x71, 0x22, isAccessible ? 0x80 : 0x30); -// auto c1 = IM_COL32(0x92, 0xFF, 0x31, isAccessible ? 0x80 : 0x30); -// -// drawList->AddRectFilledMultiColor({ min.x, min.y + prevItemOffset }, { max.x, max.y + prevItemOffset }, c0, c0, c1, c1); -// -// DrawTextWithMarquee(g_rodinFont, size, textPos, min, max, textColour, configName.c_str(), g_rowSelectionTime, 0.9, Scale(250.0)); -// -// // large -// g_canReset = g_isControlsVisible && -// !g_lockedOnOption && -// g_selectedItem->GetName().find("Language") == std::string::npos && -// g_selectedItem != &Config::WindowSize && -// isAccessible; -// } -// else -// { -// drawList->PushClipRect(min, max, true); -// -// DrawRubyAnnotatedText -// ( -// g_rodinFont, -// size, -// FLT_MAX, -// textPos, -// 0.0f, -// configName.c_str(), -// [=](const char* str, ImVec2 pos) -// { -// DrawTextBasic(g_rodinFont, size, pos, textColour, str); -// }, -// [=](const char* str, float annotationSize, ImVec2 pos) -// { -// DrawTextBasic(g_rodinFont, annotationSize, pos, textColour, str); -// } -// ); -// -// drawList->PopClipRect(); -// } -// -// // Right side -// min = { max.x + (clipRectMax.x - max.x - valueWidth) / 2.0f, min.y + (optionHeight - valueHeight) / 2.0f }; -// max = { min.x + valueWidth, min.y + valueHeight }; -// -// drawList->AddRectFilledMultiColor(min, max, IM_COL32(0, 130, 0, 223 * alpha), IM_COL32(0, 130, 0, 178 * alpha), IM_COL32(0, 130, 0, 223 * alpha), IM_COL32(0, 130, 0, 178 * alpha)); -// drawList->AddRectFilledMultiColor(min, max, IM_COL32(0, 0, 0, 13 * alpha), IM_COL32(0, 0, 0, 0), IM_COL32(0, 0, 0, 55 * alpha), IM_COL32(0, 0, 0, 6 * alpha)); -// drawList->AddRectFilledMultiColor(min, max, IM_COL32(0, 130, 0, 13 * alpha), IM_COL32(0, 130, 0, 111 * alpha), IM_COL32(0, 130, 0, 0), IM_COL32(0, 130, 0, 55 * alpha)); -// -// if (isSlider) -// { -// if constexpr (std::is_same_v || std::is_same_v) -// { -// if (lockedOnOption) -// { -// SetAdditive(true); -// drawList->AddRectFilled(min, max, IM_COL32(192, 192, 0, 96 * ComputeMotion(g_lockedOnTime, 0, VALUE_SLIDER_INTRO_DURATION))); -// SetAdditive(false); -// } -// -// // Inner container of slider -// const uint32_t innerColor0 = IM_COL32(0, 65, 0, 255 * alpha); -// const uint32_t innerColor1 = IM_COL32(0, 32, 0, 255 * alpha); -// -// float xPadding = Scale(6); -// float yPadding = Scale(3); -// -// drawList->AddRectFilledMultiColor -// ( -// { min.x + xPadding, min.y + yPadding }, -// { max.x - xPadding, max.y - yPadding }, -// innerColor0, -// innerColor0, -// innerColor1, -// innerColor1 -// ); -// -// // The actual slider -// const uint32_t sliderColor0 = IM_COL32(57, 241, 0, 255 * alpha); -// const uint32_t sliderColor1 = IM_COL32(2, 106, 0, 255 * alpha); -// -// xPadding += Scale(2); -// yPadding += Scale(2); -// -// ImVec2 sliderMin = { min.x + xPadding, min.y + yPadding }; -// ImVec2 sliderMax = { max.x - xPadding, max.y - yPadding }; -// float factor; -// -// if (config->Value <= valueCenter) -// factor = float(config->Value - valueMin) / (valueCenter - valueMin) * 0.5f; -// else -// factor = 0.5f + float(config->Value - valueCenter) / (valueMax - valueCenter) * 0.5f; -// -// sliderMax.x = sliderMin.x + (sliderMax.x - sliderMin.x) * factor; -// -// drawList->AddRectFilledMultiColor(sliderMin, sliderMax, sliderColor0, sliderColor0, sliderColor1, sliderColor1); -// } -// } -// -// if constexpr (std::is_same_v) -// DrawToggleLight({ min.x + Scale(14), min.y + ((max.y - min.y) - Scale(14)) / 2 + Scale(1) }, config->Value, alpha); -// -// // Selection triangles -// if (lockedOnOption) -// { -// 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; -// -// double time = ImGui::GetTime(); -// -// if (leftTapped || rightTapped) -// g_lastTappedTime = time; -// -// bool decrement = leftTapped; -// bool increment = rightTapped; -// -// g_leftWasHeld = leftIsHeld; -// g_rightWasHeld = rightIsHeld; -// -// DrawSelectionArrows(min, max, leftTapped, rightTapped, isValueSlider); -// -// if constexpr (std::is_enum_v) -// { -// auto it = config->EnumTemplateReverse.find(config->Value); -// -// if (leftTapped) -// { -// if (it == config->EnumTemplateReverse.begin()) -// it = config->EnumTemplateReverse.end(); -// -// --it; -// } -// else if (rightTapped) -// { -// ++it; -// -// if (it == config->EnumTemplateReverse.end()) -// it = config->EnumTemplateReverse.begin(); -// } -// -// config->Value = it->first; -// -// if (increment || decrement) -// Game_PlaySound("sys_actstg_pausecursor"); -// } -// else if constexpr (std::is_same_v || std::is_same_v) -// { -// float deltaTime = ImGui::GetIO().DeltaTime; -// -// bool fastIncrement = (time - g_lastTappedTime) > 0.5; -// bool isPlayIncrementSound = true; -// -// constexpr double INCREMENT_TIME = 1.0 / 120.0; -// constexpr double INCREMENT_SOUND_TIME = 1.0 / 7.5; -// -// if (isSlider) -// { -// if (fastIncrement) -// { -// isPlayIncrementSound = (time - g_lastIncrementSoundTime) > INCREMENT_SOUND_TIME; -// -// if ((time - g_lastIncrementTime) < INCREMENT_TIME) -// fastIncrement = false; -// else -// g_lastIncrementTime = time; -// } -// -// if (fastIncrement) -// { -// decrement = leftIsHeld; -// increment = rightIsHeld; -// } -// } -// -// do -// { -// if constexpr (std::is_integral_v) -// { -// if (decrement) -// config->Value -= 1; -// else if (increment) -// config->Value += 1; -// } -// else -// { -// if (decrement) -// config->Value -= 0.01f; -// else if (increment) -// config->Value += 0.01f; -// } -// -// deltaTime -= INCREMENT_TIME; -// } -// while (fastIncrement && deltaTime > 0.0f); -// -// bool isConfigValueInBounds = config->Value >= valueMin && config->Value <= valueMax; -// -// if ((increment || decrement) && isConfigValueInBounds && isPlayIncrementSound) -// { -// g_lastIncrementSoundTime = time; -// Game_PlaySound("sys_actstg_twn_speechbutton"); -// } -// -// config->Value = std::clamp(config->Value, valueMin, valueMax); -// } -// -// if (!config->ApplyCallback) -// { -// if ((increment || decrement) && config->Callback) -// config->Callback(config); -// } -// } -// -// std::string valueText; -// if constexpr (std::is_same_v) -// { -// valueText = fmt::format("{}%", int32_t(round(config->Value * 100.0f))); -// } -// else if constexpr (std::is_same_v) -// { -// if (config == &Config::WindowSize) -// { -// if (Config::Fullscreen) -// { -// int displayW, displayH; -// GameWindow::GetSizeInPixels(&displayW, &displayH); -// valueText = fmt::format("{}x{}", displayW, displayH); -// } -// else -// { -// auto displayModes = GameWindow::GetDisplayModes(); -// if (config->Value >= 0 && config->Value < displayModes.size()) -// { -// auto& displayMode = displayModes[config->Value]; -// -// valueText = fmt::format("{}x{}", displayMode.w, displayMode.h); -// } -// else -// { -// valueText = fmt::format("{}x{}", GameWindow::s_width, GameWindow::s_height); -// } -// } -// } -// else if (config == &Config::Monitor) -// { -// valueText = fmt::format("{}", config->Value + 1); -// } -// else -// { -// valueText = fmt::format("{}", config->Value); -// -// if (isSlider && config->Value >= valueMax) -// valueText = Localise("Options_Value_Max"); -// } -// } -// else -// { -// valueText = config->GetValueLocalised(Config::Language); -// } -// -// size = Scale(20.0f); -// textSize = g_newRodinFont->CalcTextSizeA(size, FLT_MAX, 0.0f, valueText.data()); -// -// auto textSquashRatio = 1.0f; -// -// if (textSize.x > max.x - min.x) -// textSquashRatio = (max.x - min.x) / textSize.x - 0.1f; -// -// auto textX = min.x + ((max.x - min.x) - (textSize.x * textSquashRatio)) / 2.0f; -// auto textY = min.y + ((max.y - min.y) - textSize.y) / 2.0f; -// -// SetGradient -// ( -// { textX, textY }, -// { textX + textSize.x, textY + textSize.y }, -// IM_COL32(192, 255, 0, 255), -// IM_COL32(128, 170, 0, 255) -// ); -// -// SetScale({ textSquashRatio, 1.0f }); -// SetOrigin({ textX, textY }); -// -// DrawTextWithOutline -// ( -// g_newRodinFont, -// size, -// { textX, textY }, -// IM_COL32(255, 255, 255, 255 * alpha), -// valueText.data(), -// 4, -// IM_COL32(0, 0, 0, 255 * alpha) -// ); -// -// SetScale({ 1.0f, 1.0f }); -// SetOrigin({ 0.0f, 0.0f }); -// -// ResetGradient(); -//} -// -//static void DrawConfigOptions() -//{ -// auto drawList = ImGui::GetBackgroundDrawList(); -// auto clipRectMin = drawList->GetClipRectMin(); -// auto clipRectMax = drawList->GetClipRectMax(); -// -// drawList->PushClipRect({ clipRectMin.x, clipRectMin.y }, { clipRectMax.x, clipRectMax.y - Scale(5.0f) }); -// -// g_selectedItem = nullptr; -// -// float gridSize = Scale(GRID_SIZE); -// float optionHeightWithPadding = gridSize * 6.0f; -// float yOffset = -g_firstVisibleRowIndex * optionHeightWithPadding; -// -// int32_t rowCount = 0; -// -// bool isStage = OptionsMenu::s_pauseMenuType == SWA::eMenuType_Stage || OptionsMenu::s_pauseMenuType == SWA::eMenuType_Hub; -// auto cmnReason = &Localise("Options_Desc_NotAvailable"); -// -// // TODO: Don't use raw numbers here! -// switch (g_categoryIndex) -// { -// case 0: // SYSTEM -// DrawConfigOption(rowCount++, yOffset, &Config::Language, !OptionsMenu::s_isPause, cmnReason); -// DrawConfigOption(rowCount++, yOffset, &Config::VoiceLanguage, OptionsMenu::s_pauseMenuType == SWA::eMenuType_WorldMap, cmnReason); -// DrawConfigOption(rowCount++, yOffset, &Config::Subtitles, true); -// DrawConfigOption(rowCount++, yOffset, &Config::Hints, !isStage, cmnReason); -// DrawConfigOption(rowCount++, yOffset, &Config::ControlTutorial, !isStage, cmnReason); -// DrawConfigOption(rowCount++, yOffset, &Config::AchievementNotifications, true); -// DrawConfigOption(rowCount++, yOffset, &Config::TimeOfDayTransition, !Config::UseArrowsForTimeOfDayTransition); -// break; -// -// case 1: // INPUT -// DrawConfigOption(rowCount++, yOffset, &Config::HorizontalCamera, true); -// DrawConfigOption(rowCount++, yOffset, &Config::VerticalCamera, true); -// DrawConfigOption(rowCount++, yOffset, &Config::Vibration, true); -// DrawConfigOption(rowCount++, yOffset, &Config::AllowBackgroundInput, true); -// DrawConfigOption(rowCount++, yOffset, &Config::ControllerIcons, true); -// break; -// -// case 2: // AUDIO -// DrawConfigOption(rowCount++, yOffset, &Config::MasterVolume, true); -// DrawConfigOption(rowCount++, yOffset, &Config::MusicVolume, true); -// DrawConfigOption(rowCount++, yOffset, &Config::EffectsVolume, true); -// DrawConfigOption(rowCount++, yOffset, &Config::ChannelConfiguration, true); -// DrawConfigOption(rowCount++, yOffset, &Config::MusicAttenuation, AudioPatches::CanAttenuate(), &Localise("Options_Desc_OSNotSupported")); -// DrawConfigOption(rowCount++, yOffset, &Config::BattleTheme, true); -// break; -// -// case 3: // VIDEO -// { -// DrawConfigOption(rowCount++, yOffset, &Config::WindowSize, -// !Config::Fullscreen, &Localise("Options_Desc_NotAvailableFullscreen"), -// 0, 0, (int32_t)GameWindow::GetDisplayModes().size() - 1, false); -// -// auto displayCount = GameWindow::GetDisplayCount(); -// auto canChangeMonitor = Config::Fullscreen && displayCount > 1; -// auto monitorReason = &Localise("Options_Desc_NotAvailableWindowed"); -// -// if (Config::Fullscreen && displayCount <= 1) -// monitorReason = &Localise("Options_Desc_NotAvailableHardware"); -// -// DrawConfigOption(rowCount++, yOffset, &Config::Monitor, canChangeMonitor, monitorReason, 0, 0, displayCount - 1, false); -// -// DrawConfigOption(rowCount++, yOffset, &Config::AspectRatio, true); -// DrawConfigOption(rowCount++, yOffset, &Config::ResolutionScale, true, nullptr, 0.25f, 1.0f, 2.0f); -// DrawConfigOption(rowCount++, yOffset, &Config::Fullscreen, true); -// DrawConfigOption(rowCount++, yOffset, &Config::VSync, true); -// DrawConfigOption(rowCount++, yOffset, &Config::FPS, true, nullptr, FPS_MIN, 120, FPS_MAX); -// DrawConfigOption(rowCount++, yOffset, &Config::Brightness, true); -// DrawConfigOption(rowCount++, yOffset, &Config::AntiAliasing, true); -// DrawConfigOption(rowCount++, yOffset, &Config::TransparencyAntiAliasing, Config::AntiAliasing != EAntiAliasing::None, &Localise("Options_Desc_NotAvailableMSAA")); -// DrawConfigOption(rowCount++, yOffset, &Config::ShadowResolution, true); -// DrawConfigOption(rowCount++, yOffset, &Config::GITextureFiltering, true); -// DrawConfigOption(rowCount++, yOffset, &Config::MotionBlur, true); -// DrawConfigOption(rowCount++, yOffset, &Config::XboxColorCorrection, true); -// DrawConfigOption(rowCount++, yOffset, &Config::CutsceneAspectRatio, true); -// DrawConfigOption(rowCount++, yOffset, &Config::UIAlignmentMode, true); -// -// break; -// } -// } -// -// auto inputState = SWA::CInputState::GetInstance(); -// -// bool upIsHeld = !g_lockedOnOption && (inputState->GetPadState().IsDown(SWA::eKeyState_DpadUp) || -// inputState->GetPadState().LeftStickVertical > 0.5f); -// -// bool downIsHeld = !g_lockedOnOption && (inputState->GetPadState().IsDown(SWA::eKeyState_DpadDown) || -// inputState->GetPadState().LeftStickVertical < -0.5f); -// -// bool scrollUp = !g_upWasHeld && upIsHeld; -// bool scrollDown = !g_downWasHeld && downIsHeld; -// -// int32_t prevSelectedRowIndex = g_selectedRowIndex; -// -// auto time = ImGui::GetTime(); -// auto fastScroll = (time - g_lastTappedTime) > 0.6; -// auto fastScrollSpeed = 1.0 / 3.5; -// auto fastScrollEncounteredEdge = false; -// static auto fastScrollSpeedUp = false; -// -// if (scrollUp || scrollDown) -// g_lastTappedTime = time; -// -// if (!upIsHeld && !downIsHeld) -// fastScrollSpeedUp = false; -// -// if (fastScrollSpeedUp) -// fastScrollSpeed /= 2; -// -// if (fastScroll) -// { -// if ((time - g_lastIncrementTime) < fastScrollSpeed) -// { -// fastScroll = false; -// } -// else -// { -// g_lastIncrementTime = time; -// -// scrollUp = upIsHeld; -// scrollDown = downIsHeld; -// } -// } -// -// if (scrollUp) -// { -// --g_selectedRowIndex; -// -// if (g_selectedRowIndex < 0) -// { -// g_selectedRowIndex = fastScroll ? 0 : rowCount - 1; -// fastScrollEncounteredEdge = fastScroll; -// fastScrollSpeedUp = false; -// } -// } -// else if (scrollDown) -// { -// ++g_selectedRowIndex; -// -// if (g_selectedRowIndex >= rowCount) -// { -// g_selectedRowIndex = fastScroll ? rowCount - 1 : 0; -// fastScrollEncounteredEdge = fastScroll; -// fastScrollSpeedUp = false; -// } -// } -// -// if ((scrollUp || scrollDown) && !fastScrollEncounteredEdge) -// { -// g_rowSelectionTime = time; -// g_prevSelectedRowIndex = prevSelectedRowIndex; -// Game_PlaySound("sys_worldmap_cursor"); -// } -// -// g_upWasHeld = upIsHeld; -// g_downWasHeld = downIsHeld; -// -// int32_t visibleRowCount = int32_t(floor((clipRectMax.y - clipRectMin.y) / optionHeightWithPadding)); -// -// bool disableMoveAnimation = false; -// -// if (g_firstVisibleRowIndex > g_selectedRowIndex) -// { -// g_firstVisibleRowIndex = g_selectedRowIndex; -// disableMoveAnimation = true; -// -// if (g_selectedRowIndex > 0) -// fastScrollSpeedUp = true; -// } -// -// if (g_firstVisibleRowIndex + visibleRowCount - 1 < g_selectedRowIndex) -// { -// g_firstVisibleRowIndex = std::max(0, g_selectedRowIndex - visibleRowCount + 1); -// disableMoveAnimation = true; -// -// if (g_selectedRowIndex < rowCount - 1) -// fastScrollSpeedUp = true; -// } -// -// if (disableMoveAnimation) -// g_prevSelectedRowIndex = g_selectedRowIndex; -// -// drawList->PopClipRect(); -// -// // Pop clip rect from DrawCategories -// drawList->PopClipRect(); -// -// // Draw scroll bar -// if (rowCount > visibleRowCount) -// { -// float totalHeight = (clipRectMax.y - clipRectMin.y) - Scale(2.0f); -// float heightRatio = float(visibleRowCount) / float(rowCount); -// -// float offsetRatio = float(g_firstVisibleRowIndex) / float(rowCount); -// offsetRatio = Lerp(g_prevOffsetRatio, offsetRatio, 1.0f - exp(-16.0f * ImGui::GetIO().DeltaTime)); -// g_prevOffsetRatio = offsetRatio; -// -// float minY = offsetRatio * totalHeight + clipRectMin.y; -// -// drawList->AddRectFilled -// ( -// { clipRectMax.x, minY }, -// { clipRectMax.x + gridSize - Scale(1.0f), minY + totalHeight * heightRatio}, -// IM_COL32(0, 128, 0, 255) -// ); -// } -//} - -static void DrawSettingsPanel(ImVec2 settingsMin, ImVec2 settingsMax) +void OptionsMenu::Open(bool isPause) { - auto drawList = ImGui::GetBackgroundDrawList(); - - SetProceduralOrigin(settingsMin); - DrawContainer(settingsMin, settingsMax, true); - -// if (DrawCategories()) -// { -// DrawConfigOptions(); -// -// g_isControlsVisible = true; -// } -// else -// { -// ResetSelection(); -// } - - ResetProceduralOrigin(); + g_commonMenu = CommonMenu(Localise("Options_Header_Name").c_str(), nullptr, isPause); + g_appearTime = ImGui::GetTime(); - // Pop clip rect from DrawContainer - drawList->PopClipRect(); + s_state = OptionsMenuState::Opening; + s_isVisible = true; + s_isPause = isPause; } -static void DrawInfoPanel(ImVec2 infoMin, ImVec2 infoMax) -{ - auto drawList = ImGui::GetBackgroundDrawList(); - - SetProceduralOrigin(infoMin); - DrawContainer(infoMin, infoMax, false); - - auto clipRectMin = drawList->GetClipRectMin(); - auto clipRectMax = drawList->GetClipRectMax(); - - if (g_selectedItem) - { - auto desc = g_selectedItem->GetDescription(Config::Language); - auto thumbnail = GetThumbnail(g_selectedItem); - - float aspectRatio = float(thumbnail->width) / thumbnail->height; - float thumbnailHeight = (clipRectMax.x - clipRectMin.x) / aspectRatio; - - ImVec2 thumbnailMin = { clipRectMin.x, clipRectMin.y + Scale(GRID_SIZE / 2.0f) }; - ImVec2 thumbnailMax = { clipRectMax.x, thumbnailMin.y + thumbnailHeight }; - - if (g_isStage) - { - drawList->AddImage(thumbnail, thumbnailMin, thumbnailMax); - } - else - { - float time = g_appearTime + CONTAINER_FULL_DURATION / 60.0; - - drawList->AddImage( - thumbnail, - thumbnailMin, - thumbnailMax, - { 0.0f, 0.0f }, - { 1.0f, 1.0f }, - IM_COL32(255, 255, 255, 255 * TVStatic::ComputeThumbnailAlpha(time))); - - TVStatic::Draw( - { (thumbnailMin.x + thumbnailMax.x) / 2.0f, (thumbnailMin.y + thumbnailMax.y) / 2.0f }, - { (thumbnailMax.x - thumbnailMin.x), (thumbnailMax.y - thumbnailMin.y) }, - time); - } - - if (g_inaccessibleReason) - { - desc += "\n\n" + *g_inaccessibleReason; - } - else - { - // Specialised description for resolution scale. - if (g_selectedItem == &Config::ResolutionScale) - { - char buf[100]; - auto resScale = round(*(float*)g_selectedItem->GetValue() * 1000) / 1000; - - std::snprintf(buf, sizeof(buf), desc.c_str(), - (int)((float)Video::s_viewportWidth * resScale), - (int)((float)Video::s_viewportHeight * resScale)); - - desc = buf; - } - - const auto& valueDescription = g_selectedItem->GetValueDescription(Config::Language); - - if (!valueDescription.empty()) - desc += "\n\n" + valueDescription; - } - - clipRectMin = { clipRectMin.x, thumbnailMax.y }; - - auto fontSize = Scale(28.0f); - - /* Extra padding between the start - of the description text and the - bottom of the thumbnail. */ - auto offsetY = Scale(24.0f); - - auto textX = clipRectMin.x - Scale(0.5f); - auto textY = thumbnailMax.y + offsetY; - float lineWidth = clipRectMax.x - clipRectMin.x; - - auto textSize = MeasureCentredParagraph(g_rodinFont, fontSize, lineWidth, 5.0f, desc.c_str()); - - drawList->PushClipRect(clipRectMin, clipRectMax, false); - - static auto isScrolling = false; - static auto isManualScrolling = false; - static auto scrollOffset = 0.0f; - static auto scrollTimer = 0.0f; - static auto scrollDirection = 1.0f; - auto scrollMax = textSize.y - (clipRectMax.y - textY); - auto scrollSpeed = Scale(50); - - if (scrollMax > 0.0f) - { - auto vert = -g_rightStickY; - - if (fabs(vert) > 0.25f) - { - isManualScrolling = true; - scrollOffset += vert * scrollSpeed * App::s_deltaTime; - } - else if (isManualScrolling && fabs(vert) <= 0.25f) - { - isScrolling = false; - isManualScrolling = false; - scrollTimer = 0.0f; - scrollDirection = vert > 0.0f ? 1.0f : -1.0f; - } - - if (!isManualScrolling) - { - if (!isScrolling) - { - scrollTimer += App::s_deltaTime; - - if (scrollTimer >= INFO_TEXT_MARQUEE_DELAY) - isScrolling = true; - } - - if (isScrolling) - { - scrollOffset += scrollSpeed * scrollDirection * App::s_deltaTime; - - if (scrollOffset >= scrollMax) - { - isScrolling = false; - scrollOffset = scrollMax; - scrollTimer = 0.0f; - scrollDirection = -1.0f; - } - else if (scrollOffset <= 0.0f) - { - isScrolling = false; - scrollOffset = 0; - scrollTimer = 0.0f; - scrollDirection = 1.0f; - } - } - } - - scrollOffset = std::clamp(scrollOffset, 0.0f, scrollMax); - } - else - { - isScrolling = false; - scrollOffset = 0.0f; - scrollTimer = 0.0f; - scrollDirection = 1.0f; - } - - SetVerticalMarqueeFade({ clipRectMin.x, clipRectMin.y + Scale(5.5f) }, clipRectMax, Scale(10), Scale(10)); - - DrawTextParagraph - ( - g_rodinFont, - fontSize, - lineWidth, - { textX, textY - scrollOffset }, - 5.0f, - desc.c_str(), - - [=](const char* str, ImVec2 pos) - { - DrawTextBasic(g_rodinFont, fontSize, pos, IM_COL32_WHITE, str); - } - ); - - ResetMarqueeFade(); - - drawList->PopClipRect(); - - // Reset parameters on new selected row. - if (ImGui::GetTime() - g_rowSelectionTime <= 0.0f) - { - isScrolling = false; - scrollOffset = 0.0f; - scrollTimer = 0.0f; - scrollDirection = 1.0f; - } - } - - ResetProceduralOrigin(); - - // Pop clip rect from DrawContainer - drawList->PopClipRect(); -} - -static void SetOptionsMenuVisible(bool isVisible) -{ - OptionsMenu::s_isVisible = isVisible; -// *SWA::SGlobals::ms_IsRenderHud = !isVisible; -} - -static bool DrawMilesElectric() -{ - auto drawList = ImGui::GetBackgroundDrawList(); - auto& res = ImGui::GetIO().DisplaySize; - - // Compensate for the lack of CSD UI dimming the background. - if (g_isStage) - drawList->AddRectFilled({ 0.0f, 0.0f }, res, IM_COL32(0, 0, 0, 127)); - - auto scaleMotion = ComputeMotion(g_appearTime, 0, MILES_ELECTRIC_SCALE_DURATION); - - if (scaleMotion >= 1.0) - { - if (g_isClosing) - SetOptionsMenuVisible(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::GetBackgroundDrawList(); - 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; - - 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(); - - g_rodinFont = ImFontAtlasSnapshot::GetFont("FOT-RodinPro-DB.otf"); - g_newRodinFont = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-UB.otf"); - - LoadThumbnails(); - - g_upMilesElectric = LOAD_ZSTD_TEXTURE(g_miles_electric); - - TVStatic::Init(); -} - -//void OptionsMenu::Draw() -//{ -// if (!s_isVisible) -// 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; -// -// if (g_isStage) -// { -// if (!DrawMilesElectric()) -// return; -// } -// -// if (!g_isClosing) -// { -// auto drawList = ImGui::GetBackgroundDrawList(); -// auto& res = ImGui::GetIO().DisplaySize; -// -// if (g_isStage) -// drawList->AddRectFilled({ 0.0f, 0.0f }, res, IM_COL32(0, 0, 0, 223)); -// -// DrawScanlineBars(); -// -// float settingsGridCount = floor(Lerp(SETTINGS_NARROW_GRID_COUNT, SETTINGS_WIDE_GRID_COUNT, g_aspectRatioNarrowScale)); -// float paddingGridCount = Lerp(PADDING_NARROW_GRID_COUNT, PADDING_WIDE_GRID_COUNT, g_aspectRatioNarrowScale); -// float infoGridCount = floor(Lerp(INFO_NARROW_GRID_COUNT, INFO_WIDE_GRID_COUNT, g_aspectRatioNarrowScale)); -// float totalGridCount = settingsGridCount + paddingGridCount + infoGridCount; -// -// float minX = round(g_aspectRatioOffsetX + Scale((1280.0f - (GRID_SIZE * totalGridCount)) / 2.0f)); -// float maxX = res.x - minX; -// float minY = round(g_aspectRatioOffsetY + Scale(CONTAINER_POS_Y)); -// float maxY = round(g_aspectRatioOffsetY + Scale((720.0f - CONTAINER_POS_Y + 1.0f))); -// -// DrawSettingsPanel( -// { minX, minY }, -// { minX + Scale(settingsGridCount * GRID_SIZE), maxY } -// ); -// -// DrawInfoPanel( -// { maxX - Scale(infoGridCount * GRID_SIZE) - 1.0f, minY }, -// { maxX - 1.0f, maxY } -// ); -// -// if (g_isStage) -// DrawFadeTransition(); -// } -// -// s_isRestartRequired = Config::Language != App::s_language; -//} -// -//void OptionsMenu::Open(bool isPause, SWA::EMenuType pauseMenuType) -//{ -// 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 }; -// g_categoryAnimMax = { 0.0f, 0.0f }; -// g_selectedItem = nullptr; -// g_titleAnimBegin = true; -// -// /* 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; -// -// ResetSelection(); -// SetOptionsMenuVisible(true); -// -// std::array buttons = -// { -// Button("Common_Switch", 115.0f, EButtonIcon::LBRB, EButtonAlignment::Left, &g_isControlsVisible), -// Button("Common_Reset", 110.0f, EButtonIcon::X, &g_canReset), -// Button("Common_Select", 115.0f, EButtonIcon::A, &g_isControlsVisible), -// Button("Common_Back", 65.0f, EButtonIcon::B, &g_isControlsVisible) -// }; -// -// ButtonGuide::Open(buttons); -// ButtonGuide::SetSideMargins(250); -// -// hid::SetProhibitedInputs(XAMINPUT_GAMEPAD_START, false, true); -//} - void OptionsMenu::Close() { - if (!g_isClosing) + if (s_state != OptionsMenuState::Closing) { g_appearTime = ImGui::GetTime(); - g_isControlsVisible = false; - g_isClosing = true; + s_state = OptionsMenuState::Closing; - ButtonGuide::Close(); Config::Save(); - - hid::SetProhibitedInputs(); } - // Skip Miles Electric animation at main menu. - if (!g_isStage) - SetOptionsMenuVisible(false); + s_isVisible = false; } bool OptionsMenu::CanClose() { - return !g_lockedOnOption && g_isControlsVisible; + return true; } diff --git a/MarathonRecomp/ui/options_menu.h b/MarathonRecomp/ui/options_menu.h index 5798bcca1..53bfefd5d 100644 --- a/MarathonRecomp/ui/options_menu.h +++ b/MarathonRecomp/ui/options_menu.h @@ -1,20 +1,22 @@ #pragma once -#include +enum class OptionsMenuState +{ + Opening, + Idle, + Closing +}; class OptionsMenu { public: + static inline OptionsMenuState s_state = OptionsMenuState::Opening; static inline bool s_isVisible = false; static inline bool s_isPause = false; static inline bool s_isRestartRequired = false; -// static inline SWA::EMenuType s_pauseMenuType; - - static void Init(); -// static void Draw(); -// static void Open(bool isPause = false, SWA::EMenuType pauseMenuType = SWA::eMenuType_WorldMap); + static void Draw(); + static void Open(bool isPause = false); static void Close(); - static bool CanClose(); }; diff --git a/MarathonRecomp/ui/options_menu_thumbnails.cpp b/MarathonRecomp/ui/options_menu_thumbnails.cpp deleted file mode 100644 index 33cb64fcc..000000000 --- a/MarathonRecomp/ui/options_menu_thumbnails.cpp +++ /dev/null @@ -1,208 +0,0 @@ -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define VALUE_THUMBNAIL_MAP(type) std::unordered_map> - -static std::unique_ptr g_defaultThumbnail; - -static std::unique_ptr g_controlTutorialXBThumbnail; -static std::unique_ptr g_controlTutorialPSThumbnail; -static std::unique_ptr g_vibrationXBThumbnail; -static std::unique_ptr g_vibrationPSThumbnail; -static std::unique_ptr g_backgroundInputXBThumbnail; -static std::unique_ptr g_backgroundInputPSThumbnail; - -static std::unordered_map> g_configThumbnails; - -static VALUE_THUMBNAIL_MAP(EChannelConfiguration) g_channelConfigurationThumbnails; -static VALUE_THUMBNAIL_MAP(EAntiAliasing) g_msaaAntiAliasingThumbnails; -static VALUE_THUMBNAIL_MAP(bool) g_vsyncThumbnails; -static VALUE_THUMBNAIL_MAP(bool) g_transparencyAntiAliasingThumbnails; -static VALUE_THUMBNAIL_MAP(EShadowResolution) g_shadowResolutionThumbnails; -static VALUE_THUMBNAIL_MAP(ECutsceneAspectRatio) g_cutsceneAspectRatioThumbnails; -static VALUE_THUMBNAIL_MAP(EUIAlignmentMode) g_uiAlignmentThumbnails; - -void LoadThumbnails() -{ - g_defaultThumbnail = LOAD_ZSTD_TEXTURE(g_default); - - g_controlTutorialXBThumbnail = LOAD_ZSTD_TEXTURE(g_control_tutorial_xb); - g_controlTutorialPSThumbnail = LOAD_ZSTD_TEXTURE(g_control_tutorial_ps); - g_vibrationXBThumbnail = LOAD_ZSTD_TEXTURE(g_vibration_xb); - g_vibrationPSThumbnail = LOAD_ZSTD_TEXTURE(g_vibration_ps); - g_backgroundInputXBThumbnail = LOAD_ZSTD_TEXTURE(g_allow_background_input_xb); - g_backgroundInputPSThumbnail = LOAD_ZSTD_TEXTURE(g_allow_background_input_ps); - - g_configThumbnails[&Config::Language] = LOAD_ZSTD_TEXTURE(g_language); - g_configThumbnails[&Config::VoiceLanguage] = LOAD_ZSTD_TEXTURE(g_voice_language); - g_configThumbnails[&Config::Hints] = LOAD_ZSTD_TEXTURE(g_hints); - g_configThumbnails[&Config::AchievementNotifications] = LOAD_ZSTD_TEXTURE(g_achievement_notifications); - - g_configThumbnails[&Config::HorizontalCamera] = LOAD_ZSTD_TEXTURE(g_horizontal_camera); - g_configThumbnails[&Config::VerticalCamera] = LOAD_ZSTD_TEXTURE(g_vertical_camera); - g_configThumbnails[&Config::ControllerIcons] = LOAD_ZSTD_TEXTURE(g_controller_icons); - g_configThumbnails[&Config::MasterVolume] = LOAD_ZSTD_TEXTURE(g_master_volume); - g_configThumbnails[&Config::MusicVolume] = LOAD_ZSTD_TEXTURE(g_music_volume); - g_configThumbnails[&Config::EffectsVolume] = LOAD_ZSTD_TEXTURE(g_effects_volume); - - g_channelConfigurationThumbnails[EChannelConfiguration::Stereo] = LOAD_ZSTD_TEXTURE(g_channel_stereo); - g_channelConfigurationThumbnails[EChannelConfiguration::Surround] = LOAD_ZSTD_TEXTURE(g_channel_surround); - - g_configThumbnails[&Config::MusicAttenuation] = LOAD_ZSTD_TEXTURE(g_music_attenuation); - g_configThumbnails[&Config::WindowSize] = LOAD_ZSTD_TEXTURE(g_window_size); - g_configThumbnails[&Config::Monitor] = LOAD_ZSTD_TEXTURE(g_monitor); - g_configThumbnails[&Config::AspectRatio] = LOAD_ZSTD_TEXTURE(g_aspect_ratio); - g_configThumbnails[&Config::Fullscreen] = LOAD_ZSTD_TEXTURE(g_fullscreen); - - g_vsyncThumbnails[false] = LOAD_ZSTD_TEXTURE(g_vsync_off); - g_vsyncThumbnails[true] = LOAD_ZSTD_TEXTURE(g_vsync_on); - - g_configThumbnails[&Config::FPS] = LOAD_ZSTD_TEXTURE(g_fps); - g_configThumbnails[&Config::Brightness] = LOAD_ZSTD_TEXTURE(g_brightness); - - g_msaaAntiAliasingThumbnails[EAntiAliasing::None] = LOAD_ZSTD_TEXTURE(g_antialiasing_none); - g_msaaAntiAliasingThumbnails[EAntiAliasing::MSAA2x] = LOAD_ZSTD_TEXTURE(g_antialiasing_2x); - g_msaaAntiAliasingThumbnails[EAntiAliasing::MSAA4x] = LOAD_ZSTD_TEXTURE(g_antialiasing_4x); - g_msaaAntiAliasingThumbnails[EAntiAliasing::MSAA8x] = LOAD_ZSTD_TEXTURE(g_antialiasing_8x); - - g_transparencyAntiAliasingThumbnails[false] = LOAD_ZSTD_TEXTURE(g_transparency_antialiasing_false); - g_transparencyAntiAliasingThumbnails[true] = LOAD_ZSTD_TEXTURE(g_transparency_antialiasing_true); - - g_shadowResolutionThumbnails[EShadowResolution::x512] = LOAD_ZSTD_TEXTURE(g_shadow_resolution_x512); - g_shadowResolutionThumbnails[EShadowResolution::x1024] = LOAD_ZSTD_TEXTURE(g_shadow_resolution_x1024); - g_shadowResolutionThumbnails[EShadowResolution::x2048] = LOAD_ZSTD_TEXTURE(g_shadow_resolution_x2048); - g_shadowResolutionThumbnails[EShadowResolution::x4096] = LOAD_ZSTD_TEXTURE(g_shadow_resolution_x4096); - g_shadowResolutionThumbnails[EShadowResolution::x8192] = LOAD_ZSTD_TEXTURE(g_shadow_resolution_x8192); - - g_cutsceneAspectRatioThumbnails[ECutsceneAspectRatio::Original] = LOAD_ZSTD_TEXTURE(g_movie_scale_fit); - g_cutsceneAspectRatioThumbnails[ECutsceneAspectRatio::Unlocked] = LOAD_ZSTD_TEXTURE(g_movie_scale_fill); - - g_uiAlignmentThumbnails[EUIAlignmentMode::Centre] = LOAD_ZSTD_TEXTURE(g_ui_alignment_centre); - g_uiAlignmentThumbnails[EUIAlignmentMode::Edge] = LOAD_ZSTD_TEXTURE(g_ui_alignment_edge); -} - -template -bool TryGetValueThumbnail(const IConfigDef* cfg, VALUE_THUMBNAIL_MAP(T)* thumbnails, GuestTexture** texture) -{ - if (!texture) - return false; - - if (!cfg->GetValue()) - return false; - - T value = *(T*)cfg->GetValue(); - - auto findResult = thumbnails->find(value); - - if (findResult != thumbnails->end()) - { - *texture = findResult->second.get(); - return true; - } - - return false; -} - -GuestTexture* GetThumbnail(const IConfigDef* cfg) -{ - auto findResult = g_configThumbnails.find(cfg); - if (findResult == g_configThumbnails.end()) - { - auto texture = g_defaultThumbnail.get(); - - bool isPlayStation = Config::ControllerIcons == EControllerIcons::PlayStation; - - if (Config::ControllerIcons == EControllerIcons::Auto) - isPlayStation = hid::g_inputDeviceController == hid::EInputDevice::PlayStation; - - if (cfg == &Config::ControlTutorial) - { - texture = isPlayStation ? g_controlTutorialPSThumbnail.get() : g_controlTutorialXBThumbnail.get(); - } - else if (cfg == &Config::Vibration) - { - texture = isPlayStation ? g_vibrationPSThumbnail.get() : g_vibrationXBThumbnail.get(); - } - else if (cfg == &Config::AllowBackgroundInput) - { - texture = isPlayStation ? g_backgroundInputPSThumbnail.get() : g_backgroundInputXBThumbnail.get(); - } - else if (cfg == &Config::AntiAliasing) - { - TryGetValueThumbnail(cfg, &g_msaaAntiAliasingThumbnails, &texture); - } - else if (cfg == &Config::TransparencyAntiAliasing) - { - TryGetValueThumbnail(cfg, &g_transparencyAntiAliasingThumbnails, &texture); - } - else if (cfg == &Config::ShadowResolution) - { - TryGetValueThumbnail(cfg, &g_shadowResolutionThumbnails, &texture); - } - else if (cfg == &Config::CutsceneAspectRatio) - { - TryGetValueThumbnail(cfg, &g_cutsceneAspectRatioThumbnails, &texture); - } - else if (cfg == &Config::VSync) - { - TryGetValueThumbnail(cfg, &g_vsyncThumbnails, &texture); - } - else if (cfg == &Config::ChannelConfiguration) - { - TryGetValueThumbnail(cfg, &g_channelConfigurationThumbnails, &texture); - } - else if (cfg == &Config::UIAlignmentMode) - { - TryGetValueThumbnail(cfg, &g_uiAlignmentThumbnails, &texture); - } - - return texture; - } - - return findResult->second.get(); -} diff --git a/MarathonRecomp/ui/options_menu_thumbnails.h b/MarathonRecomp/ui/options_menu_thumbnails.h deleted file mode 100644 index 824581586..000000000 --- a/MarathonRecomp/ui/options_menu_thumbnails.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include -#include - -void LoadThumbnails(); - -GuestTexture* GetThumbnail(const IConfigDef* cfg); diff --git a/MarathonRecomp/ui/tv_static.cpp b/MarathonRecomp/ui/tv_static.cpp deleted file mode 100644 index 4178356d2..000000000 --- a/MarathonRecomp/ui/tv_static.cpp +++ /dev/null @@ -1,392 +0,0 @@ -#include "tv_static.h" -#include "imgui_utils.h" - -#include -#include -#include -#include -#include - -namespace -{ - struct FloatLinear - { - float value; - float time; - - static float Sample(const FloatLinear& a, const FloatLinear& b, float time) - { - const float t = std::clamp((time - a.time) / (b.time - a.time), 0.0f, 1.0f); - return (b.value - a.value) * t + a.value; - } - }; - - struct FloatHermite - { - float value; - float time; - float inTangent; - float outTangent; - - static float Sample(const FloatHermite& a, const FloatHermite& b, float time) - { - const float t = std::clamp((time - a.time) / (b.time - a.time), 0.0f, 1.0f); - - float valueDelta = b.value - a.value; - float frameDelta = b.time - a.time; - - float biasSquaric = t * t; - float biasCubic = biasSquaric * t; - - float valueCubic = (a.outTangent + a.inTangent) * frameDelta - valueDelta * 2.0f; - float valueSquaric = valueDelta * 3.0f - (a.inTangent * 2.0f + a.outTangent) * frameDelta; - float valueLinear = frameDelta * a.inTangent; - - return valueCubic * biasCubic + valueSquaric * biasSquaric + valueLinear * t + a.value; - } - }; -} - -template -static auto Sample(const std::array& keys, float time) -{ - T firstKey = keys[0]; - T lastKey = keys[N - 1]; - - if (time < firstKey.time) - return firstKey.value; - - if (time > lastKey.time) - return lastKey.value; - - size_t keyIndex = 0; - for (auto key : keys) - { - if (key.time >= time) - break; - - keyIndex++; - } - - if (keyIndex >= N) - return keys[N - 1].value; - - return T::Sample(keys[keyIndex - 1], keys[keyIndex], time); -} - -static std::unique_ptr g_flashTexture; -static std::unique_ptr g_noiseTexture; - -static constexpr float FRAME_OFFSET = 65.0f; -static constexpr float FRAME_SCALE = 1.0f / 60.0f; -static constexpr float FRAME_DURATION = 32.0f; - -static std::array g_flashScaleX = -{ - FloatHermite{ 0, 67 * FRAME_SCALE, 0, 0.673736f }, - FloatHermite{ 2, 70 * FRAME_SCALE, -0.003465f, -0.682543f }, - FloatHermite{ 0, 73 * FRAME_SCALE, 0, 0 }, -}; -static std::array g_flashScaleY = -{ - FloatHermite{ 0, 67 * FRAME_SCALE, 0, 0.67238f }, - FloatHermite{ 2, 70 * FRAME_SCALE, -0.001741f, -0.664096f }, - FloatHermite{ 0, 73 * FRAME_SCALE, 0, 0 }, -}; -static std::array g_flashColor = -{ - FloatLinear{ 255.0f, 67 * FRAME_SCALE }, - FloatLinear{ 160.0f, 70 * FRAME_SCALE }, - FloatLinear{ 255.0f, 73 * FRAME_SCALE }, -}; -static std::array g_flashAlpha = -{ - FloatLinear{ 255.0f, 70 * FRAME_SCALE }, - FloatLinear{ 0.0f, 73 * FRAME_SCALE }, -}; -static std::array g_noiseScale = -{ - FloatLinear{ 0.0f, 70 * FRAME_SCALE }, - FloatLinear{ 1.0f, 75 * FRAME_SCALE }, -}; -static std::array g_noiseTL_gradTL = -{ - FloatLinear{ 0.0f, 77 * FRAME_SCALE }, - FloatLinear{ (192 / 255.0f), 85 * FRAME_SCALE }, - FloatLinear{ 0.0f, 90 * FRAME_SCALE }, -}; -static std::array g_noiseTL_gradBL = -{ - FloatLinear{ 0.0f, 73 * FRAME_SCALE }, - FloatLinear{ (223 / 255.0f), 85 * FRAME_SCALE }, - FloatLinear{ 0.0f, 94 * FRAME_SCALE }, -}; -static std::array g_noiseTL_gradTR = -{ - FloatLinear{ 0.0f, 74 * FRAME_SCALE }, - FloatLinear{ (223 / 255.0f), 85 * FRAME_SCALE }, - FloatLinear{ 0.0f, 93 * FRAME_SCALE }, -}; -static std::array g_noiseTL_gradBR = -{ - FloatLinear{ 0.0f, 70 * FRAME_SCALE }, - FloatLinear{ 1.0f, 85 * FRAME_SCALE }, - FloatLinear{ 0.0f, 97 * FRAME_SCALE }, -}; -static std::array g_noiseTR_gradTL = -{ - FloatLinear{ 0.0f, 74 * FRAME_SCALE }, - FloatLinear{ (223 / 255.0f), 85 * FRAME_SCALE }, - FloatLinear{ 0.0f, 93 * FRAME_SCALE }, -}; -static std::array g_noiseTR_gradBL = -{ - FloatLinear{ 0.0f, 70 * FRAME_SCALE }, - FloatLinear{ 1.0f, 85 * FRAME_SCALE }, - FloatLinear{ 0.0f, 97 * FRAME_SCALE }, -}; -static std::array g_noiseTR_gradTR = -{ - FloatLinear{ 0.0f, 77 * FRAME_SCALE }, - FloatLinear{ (192 / 255.0f), 85 * FRAME_SCALE }, - FloatLinear{ 0.0f, 90 * FRAME_SCALE }, -}; -static std::array g_noiseTR_gradBR = -{ - FloatLinear{ 0.0f, 73 * FRAME_SCALE }, - FloatLinear{ (223 / 255.0f), 85 * FRAME_SCALE }, - FloatLinear{ 0.0f, 94 * FRAME_SCALE }, -}; -static std::array g_noiseBL_gradTL = -{ - FloatLinear{ 0.0f, 73 * FRAME_SCALE }, - FloatLinear{ (223 / 255.0f), 85 * FRAME_SCALE }, - FloatLinear{ 0.0f, 94 * FRAME_SCALE }, -}; -static std::array g_noiseBL_gradBL = -{ - FloatLinear{ 0.0f, 77 * FRAME_SCALE }, - FloatLinear{ (192 / 255.0f), 85 * FRAME_SCALE }, - FloatLinear{ 0.0f, 90 * FRAME_SCALE }, -}; -static std::array g_noiseBL_gradTR = -{ - FloatLinear{ 0.0f, 70 * FRAME_SCALE }, - FloatLinear{ 1.0f, 85 * FRAME_SCALE }, - FloatLinear{ 0.0f, 97 * FRAME_SCALE }, -}; -static std::array g_noiseBL_gradBR = -{ - FloatLinear{ 0.0f, 74 * FRAME_SCALE }, - FloatLinear{ (223 / 255.0f), 85 * FRAME_SCALE }, - FloatLinear{ 0.0f, 93 * FRAME_SCALE }, -}; -static std::array g_noiseBR_gradTL = -{ - FloatLinear{ 0.0f, 70 * FRAME_SCALE }, - FloatLinear{ 1.0f, 85 * FRAME_SCALE }, - FloatLinear{ 0.0f, 97 * FRAME_SCALE }, -}; -static std::array g_noiseBR_gradBL = -{ - FloatLinear{ 0.0f, 74 * FRAME_SCALE }, - FloatLinear{ (223 / 255.0f), 85 * FRAME_SCALE }, - FloatLinear{ 0.0f, 93 * FRAME_SCALE }, -}; -static std::array g_noiseBR_gradTR = -{ - FloatLinear{ 0.0f, 73 * FRAME_SCALE }, - FloatLinear{ (223 / 255.0f), 85 * FRAME_SCALE }, - FloatLinear{ 0.0f, 94 * FRAME_SCALE }, -}; -static std::array g_noiseBR_gradBR = -{ - FloatLinear{ 0.0f, 77 * FRAME_SCALE }, - FloatLinear{ (192 / 255.0f), 85 * FRAME_SCALE }, - FloatLinear{ 0.0f, 90 * FRAME_SCALE }, -}; -static std::array g_thumbnailAlpha = -{ - FloatLinear{ 0.0f, 85 * FRAME_SCALE }, - FloatLinear{ 1.0f, 90 * FRAME_SCALE }, -}; - -static std::pair ComputeRect(const ImVec2& center, const ImVec2& scale) -{ - ImVec2 min = { center.x - scale.x / 2.0f, center.y - scale.y / 2.0f }; - ImVec2 max = { center.x + scale.x / 2.0f, center.y + scale.y / 2.0f }; - - return std::make_pair(min, max); -} - -static void DrawFlash(const ImVec2& center, const ImVec2& resolution, float time) -{ - auto drawList = ImGui::GetBackgroundDrawList(); - - float baseScale = resolution.y / 135.0f * 100.0f; - auto [min, max] = ComputeRect(center, { Sample(g_flashScaleX, time) * baseScale, Sample(g_flashScaleY, time) * baseScale}); - - float color = Sample(g_flashColor, time); - float alpha = Sample(g_flashAlpha, time); - - drawList->AddImage(g_flashTexture.get(), min, max, { 0.0f, 0.0f }, { 1.0f, 1.0f }, IM_COL32(color, 255, color, alpha)); -} - -static void PrimRectUVColorCorners(ImDrawList* This, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, - ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left, bool reverse_order = false) -{ - ImVec2 a(p_min), c(p_max), uv_a(uv_min), uv_c(uv_max); - ImVec2 b(c.x, a.y), d(a.x, c.y), uv_b(uv_c.x, uv_a.y), uv_d(uv_a.x, uv_c.y); - - if (reverse_order) - { - ImVec2 _a = a; a = b; b = _a; - ImVec2 _c = c; c = d; d = _c; - - ImVec2 _uv_a = uv_a; uv_a = uv_b; uv_b = _uv_a; - ImVec2 _uv_c = uv_c; uv_c = uv_d; uv_d = _uv_c; - } - - ImDrawIdx idx = (ImDrawIdx)This->_VtxCurrentIdx; - This->_IdxWritePtr[0] = idx; This->_IdxWritePtr[1] = (ImDrawIdx)(idx + 1); This->_IdxWritePtr[2] = (ImDrawIdx)(idx + 2); - This->_IdxWritePtr[3] = idx; This->_IdxWritePtr[4] = (ImDrawIdx)(idx + 2); This->_IdxWritePtr[5] = (ImDrawIdx)(idx + 3); - This->_VtxWritePtr[0].pos = a; This->_VtxWritePtr[0].uv = uv_a; This->_VtxWritePtr[0].col = reverse_order ? col_upr_right : col_upr_left; - This->_VtxWritePtr[1].pos = b; This->_VtxWritePtr[1].uv = uv_b; This->_VtxWritePtr[1].col = reverse_order ? col_upr_left : col_upr_right; - This->_VtxWritePtr[2].pos = c; This->_VtxWritePtr[2].uv = uv_c; This->_VtxWritePtr[2].col = reverse_order ? col_bot_left : col_bot_right; - This->_VtxWritePtr[3].pos = d; This->_VtxWritePtr[3].uv = uv_d; This->_VtxWritePtr[3].col = reverse_order ? col_bot_right : col_bot_left; - This->_VtxWritePtr += 4; - This->_VtxCurrentIdx += 4; - This->_IdxWritePtr += 6; -} - -static void AddImageGradient(ImDrawList* DrawList, ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, - ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left, bool reverse_order = false) -{ - if (((col_upr_left | col_upr_right | col_bot_right | col_bot_left) & IM_COL32_A_MASK) == 0) - return; - - const bool push_texture_id = user_texture_id && (user_texture_id != DrawList->_CmdHeader.TextureId); - if (push_texture_id) - DrawList->PushTextureID(user_texture_id); - - DrawList->PrimReserve(6, 4); - - const ImVec2 uv = DrawList->_Data->TexUvWhitePixel; - - PrimRectUVColorCorners(DrawList, p_min, p_max, - user_texture_id ? uv_min : uv, - user_texture_id ? uv_max : uv, - col_upr_left, col_upr_right, col_bot_right, col_bot_left, - reverse_order); - - if (push_texture_id) - DrawList->PopTextureID(); -} - -static void DrawStatic(const ImVec2& center, const ImVec2& resolution, float time) -{ - auto drawList = ImGui::GetBackgroundDrawList(); - - const float scaleFactor = Sample(g_noiseScale, time); - - const ImVec2 scale2D = { 1, scaleFactor }; - - auto [min, max] = ComputeRect(center, {scale2D.x * resolution.x, scale2D.y * resolution.y}); - - // Texture pixel size divided by image resolution (which must be divisible by 4) to get proper UV clipping. - ImVec2 UV_MAX = { 270.0f / 272.0f, - 135.0f / 136.0f }; - - // Corner points - - ImVec2 topCenter = { center.x, min.y }; - ImVec2 bottomCenter = { center.x, max.y }; - - ImVec2 centerLeft = { min.x, center.y }; - ImVec2 centerRight = { max.x, center.y }; - - bool flipUV = time >= (73 * FRAME_SCALE); - - const float UV_1 = flipUV ? 0.0f : 1.0f; - const float UV_0 = flipUV ? 1.0f : 0.0f; - - // Top Left - AddImageGradient( - drawList, - g_noiseTexture.get(), - min, - center, - ImVec2(UV_0 * UV_MAX.x, 0.0f * UV_MAX.y), - ImVec2(0.5f * UV_MAX.x, 0.5f * UV_MAX.y), - IM_COL32(255, 255, 255, 255 * Sample(g_noiseTL_gradTL, time)), - IM_COL32(255, 255, 255, 255 * Sample(g_noiseTL_gradTR, time)), - IM_COL32(255, 255, 255, 255 * Sample(g_noiseTL_gradBR, time)), - IM_COL32(255, 255, 255, 255 * Sample(g_noiseTL_gradBL, time)), - true); - - // Top Right - AddImageGradient( - drawList, - g_noiseTexture.get(), - topCenter, - centerRight, - ImVec2(0.5f * UV_MAX.x, 0.0f * UV_MAX.y), - ImVec2(UV_1 * UV_MAX.x, 0.5f * UV_MAX.y), - IM_COL32(255, 255, 255, 255 * Sample(g_noiseTR_gradTL, time)), - IM_COL32(255, 255, 255, 255 * Sample(g_noiseTR_gradTR, time)), - IM_COL32(255, 255, 255, 255 * Sample(g_noiseTR_gradBR, time)), - IM_COL32(255, 255, 255, 255 * Sample(g_noiseTR_gradBL, time)), - true); - - // Bottom Right - AddImageGradient( - drawList, - g_noiseTexture.get(), - center, - max, - ImVec2(0.5f * UV_MAX.x, 0.5f * UV_MAX.y), - ImVec2(UV_1 * UV_MAX.x, 1.0f * UV_MAX.y), - IM_COL32(255, 255, 255, 255 * Sample(g_noiseBR_gradTL, time)), - IM_COL32(255, 255, 255, 255 * Sample(g_noiseBR_gradTR, time)), - IM_COL32(255, 255, 255, 255 * Sample(g_noiseBR_gradBR, time)), - IM_COL32(255, 255, 255, 255 * Sample(g_noiseBR_gradBL, time)), - true); - - // Bottom Left - AddImageGradient( - drawList, - g_noiseTexture.get(), - centerLeft, - bottomCenter, - ImVec2(UV_0 * UV_MAX.x, 0.5f * UV_MAX.y), - ImVec2(0.5f * UV_MAX.x, 1.0f * UV_MAX.y), - IM_COL32(255, 255, 255, 255 * Sample(g_noiseBL_gradTL, time)), - IM_COL32(255, 255, 255, 255 * Sample(g_noiseBL_gradTR, time)), - IM_COL32(255, 255, 255, 255 * Sample(g_noiseBL_gradBR, time)), - IM_COL32(255, 255, 255, 255 * Sample(g_noiseBL_gradBL, time)), - true); -} - -void TVStatic::Init() -{ - g_flashTexture = LOAD_ZSTD_TEXTURE(g_options_static_flash); - g_noiseTexture = LOAD_ZSTD_TEXTURE(g_options_static); -} - -static float ComputeTime(double appearTime) -{ - return (ImGui::GetTime() - appearTime) + FRAME_OFFSET * FRAME_SCALE; -} - -float TVStatic::ComputeThumbnailAlpha(double appearTime) -{ - return Sample(g_thumbnailAlpha, ComputeTime(appearTime)); -} - -void TVStatic::Draw(const ImVec2& center, const ImVec2& resolution, double appearTime) -{ - float time = ComputeTime(appearTime); - - DrawStatic(center, resolution, time); - DrawFlash(center, resolution, time); -} diff --git a/MarathonRecomp/ui/tv_static.h b/MarathonRecomp/ui/tv_static.h deleted file mode 100644 index 3cf830684..000000000 --- a/MarathonRecomp/ui/tv_static.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -struct TVStatic -{ - static void Init(); - static float ComputeThumbnailAlpha(double appearTime); - static void Draw(const ImVec2& center, const ImVec2& resolution, double appearTime); -}; diff --git a/MarathonRecompLib/config/Marathon.toml b/MarathonRecompLib/config/Marathon.toml index b8fcc0881..df7a6f1c4 100644 --- a/MarathonRecompLib/config/Marathon.toml +++ b/MarathonRecompLib/config/Marathon.toml @@ -575,3 +575,13 @@ registers = ["r29"] name = "SonicGaugeRestorationGaugeGemSpriteResetFix" address = 0x82177DFC registers = ["r31"] + +[[midasm_hook]] +name = "GameImp_PauseMenu_AddQuitPrefix" +address = 0x8217DF60 +registers = ["r1", "r30"] + +[[midasm_hook]] +name = "PauseAdapter_DoAction" +address = 0x82170F3C +registers = ["r11"] From bbddfc4402eb27613a8b9f1418f5c880e2e4e06f Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Tue, 7 Oct 2025 08:11:54 +0100 Subject: [PATCH 02/86] Implemented option controls --- MarathonRecomp/CMakeLists.txt | 5 +- MarathonRecomp/gpu/video.cpp | 18 +- MarathonRecomp/kernel/xam.cpp | 2 +- MarathonRecomp/locale/config_locale.cpp | 437 ++++---- MarathonRecomp/locale/locale.cpp | 258 +++-- .../patches/MainMenuTask_patches.cpp | 23 +- MarathonRecomp/patches/TitleTask_patches.cpp | 72 +- MarathonRecomp/ui/common_menu.cpp | 129 +-- MarathonRecomp/ui/common_menu.h | 28 +- MarathonRecomp/ui/imgui_utils.cpp | 73 +- MarathonRecomp/ui/imgui_utils.h | 21 +- MarathonRecomp/ui/installer_wizard.cpp | 57 +- MarathonRecomp/ui/message_window.cpp | 12 +- MarathonRecomp/ui/options_menu.cpp | 935 +++++++++++++++++- MarathonRecomp/ui/options_menu.h | 31 +- MarathonRecomp/user/config.cpp | 30 +- MarathonRecomp/user/config.h | 6 +- MarathonRecomp/user/config_def.h | 39 +- 18 files changed, 1615 insertions(+), 561 deletions(-) diff --git a/MarathonRecomp/CMakeLists.txt b/MarathonRecomp/CMakeLists.txt index d9a29b278..089f40d4b 100644 --- a/MarathonRecomp/CMakeLists.txt +++ b/MarathonRecomp/CMakeLists.txt @@ -542,11 +542,14 @@ BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/font/im_fo BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/achievements_menu/trophy.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/achievements_menu/trophy.dds" ARRAY_NAME "g_trophy" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/controller.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/controller.dds" ARRAY_NAME "g_controller" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/general_window.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/general_window.dds" ARRAY_NAME "g_general_window" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/hedge-dev.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/hedge-dev.dds" ARRAY_NAME "g_hedgedev" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/sonicnext-dev.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/sonicnext-dev.dds" ARRAY_NAME "g_sonicnextdev" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/kbm.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/kbm.dds" ARRAY_NAME "g_kbm" COMPRESSION_TYPE "zstd") 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/main_menu7.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/main_menu7.dds" ARRAY_NAME "g_main_menu7" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/main_menu8.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/main_menu8.dds" ARRAY_NAME "g_main_menu8" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/main_menu9.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/main_menu9.dds" ARRAY_NAME "g_main_menu9" 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") diff --git a/MarathonRecomp/gpu/video.cpp b/MarathonRecomp/gpu/video.cpp index ad2c21972..42b953751 100644 --- a/MarathonRecomp/gpu/video.cpp +++ b/MarathonRecomp/gpu/video.cpp @@ -1588,6 +1588,7 @@ static void CreateImGuiBackend() AchievementOverlay::Init(); ButtonGuide::Init(); MessageWindow::Init(); + OptionsMenu::Init(); InstallerWizard::Init(); ImGui_ImplSDL2_InitForOther(GameWindow::s_pWindow); @@ -3288,23 +3289,6 @@ void Video::ComputeViewportDimensions() break; } - case EAspectRatio::Narrow: - case EAspectRatio::OriginalNarrow: - { - if (aspectRatio > NARROW_ASPECT_RATIO) - { - s_viewportWidth = height * 4 / 3; - s_viewportHeight = height; - } - else - { - s_viewportWidth = width; - s_viewportHeight = width * 3 / 4; - } - - break; - } - default: s_viewportWidth = width; s_viewportHeight = height; diff --git a/MarathonRecomp/kernel/xam.cpp b/MarathonRecomp/kernel/xam.cpp index 12eea8337..8e13a58ec 100644 --- a/MarathonRecomp/kernel/xam.cpp +++ b/MarathonRecomp/kernel/xam.cpp @@ -517,7 +517,7 @@ uint32_t XamInputSetState(uint32_t userIndex, uint32_t flags, XAMINPUT_VIBRATION if (userIndex != 0) return ERROR_NO_SUCH_USER; - if (!hid::IsInputDeviceController() || !Config::Vibration) + if (!hid::IsInputDeviceController()) return ERROR_SUCCESS; ByteSwapInplace(vibration->wLeftMotorSpeed); diff --git a/MarathonRecomp/locale/config_locale.cpp b/MarathonRecomp/locale/config_locale.cpp index 0c15f5672..df24f90ac 100644 --- a/MarathonRecomp/locale/config_locale.cpp +++ b/MarathonRecomp/locale/config_locale.cpp @@ -48,12 +48,12 @@ CONFIG_DEFINE_LOCALE(Language) { - { ELanguage::English, { "Language", "Change the language used for text and logos." } }, - { ELanguage::Japanese, { "言語", "ゲーム内の\u200B表示言語を\u200B変更できます" } }, - { ELanguage::German, { "Sprache", "Ändere die Sprache für Text und Logos." } }, - { ELanguage::French, { "Langue", "Modifie la langue utilisée pour le texte et logos." } }, - { ELanguage::Spanish, { "Idioma", "Cambia el idioma utilizado para el texto y los logos." } }, - { ELanguage::Italian, { "Lingua", "Cambia la lingua utilizzata per i testi e i logo." } } + { ELanguage::English, { "Language", "Change the language used for text." } }, + { ELanguage::Japanese, { "言語", "DUMMY" } }, + { ELanguage::German, { "Sprache", "Ändere die Sprache für Text." } }, + { ELanguage::French, { "Langue", "Modifie la langue utilisée pour le texte." } }, + { ELanguage::Spanish, { "Idioma", "Cambia el idioma utilizado para el texto." } }, + { ELanguage::Italian, { "Lingua", "Cambia la lingua utilizzata per i testi." } } }; // Notes: do not localise this. @@ -62,12 +62,12 @@ CONFIG_DEFINE_ENUM_LOCALE(ELanguage) { ELanguage::English, { - { ELanguage::English, { "ENGLISH", "" } }, + { ELanguage::English, { "English", "" } }, { ELanguage::Japanese, { "日本語", "" } }, - { ELanguage::German, { "DEUTSCH", "" } }, - { ELanguage::French, { "FRANÇAIS", "" } }, - { ELanguage::Spanish, { "ESPAÑOL", "" } }, - { ELanguage::Italian, { "ITALIANO", "" } } + { ELanguage::German, { "Deutsch", "" } }, + { ELanguage::French, { "Français", "" } }, + { ELanguage::Spanish, { "Español", "" } }, + { ELanguage::Italian, { "Italiano", "" } } } } }; @@ -77,8 +77,8 @@ CONFIG_DEFINE_ENUM_LOCALE(EVoiceLanguage) { ELanguage::English, { - { EVoiceLanguage::English, { "ENGLISH", "" } }, - { EVoiceLanguage::Japanese, { "JAPANESE", "" } } + { EVoiceLanguage::English, { "English", "" } }, + { EVoiceLanguage::Japanese, { "Japanese", "" } } } }, { @@ -91,29 +91,29 @@ CONFIG_DEFINE_ENUM_LOCALE(EVoiceLanguage) { ELanguage::German, { - { EVoiceLanguage::English, { "ENGLISCH", "" } }, - { EVoiceLanguage::Japanese, { "JAPANISCH", "" } } + { EVoiceLanguage::English, { "Englisch", "" } }, + { EVoiceLanguage::Japanese, { "Japanisch", "" } } } }, { ELanguage::French, { - { EVoiceLanguage::English, { "ANGLAIS", "" } }, - { EVoiceLanguage::Japanese, { "JAPONAIS", "" } } + { EVoiceLanguage::English, { "Anglais", "" } }, + { EVoiceLanguage::Japanese, { "Japonais", "" } } } }, { ELanguage::Spanish, { - { EVoiceLanguage::English, { "INGLÉS", "" } }, - { EVoiceLanguage::Japanese, { "JAPONÉS", "" } } + { EVoiceLanguage::English, { "Inglés", "" } }, + { EVoiceLanguage::Japanese, { "Japonés", "" } } } }, { ELanguage::Italian, { - { EVoiceLanguage::English, { "INGLESE", "" } }, - { EVoiceLanguage::Japanese, { "GIAPPONESE", "" } } + { EVoiceLanguage::English, { "Inglese", "" } }, + { EVoiceLanguage::Japanese, { "Giapponese", "" } } } }, }; @@ -121,7 +121,7 @@ CONFIG_DEFINE_ENUM_LOCALE(EVoiceLanguage) CONFIG_DEFINE_LOCALE(Hints) { { ELanguage::English, { "Hints", "Show hints during gameplay." } }, - { ELanguage::Japanese, { "ヒントリング", "ゲーム内に\u200Bヒントリングを\u200B表示するか\u200B選択できます" } }, + { ELanguage::Japanese, { "ヒントリング", "ゲーム内にヒントリングを表示するか選択できます" } }, { ELanguage::German, { "Hinweise", "Zeige Hinweise während des Spiels." } }, { ELanguage::French, { "Indices", "Affiche les indices pendant le jeu." } }, { ELanguage::Spanish, { "Pistas", "Muestra pistas durante el juego." } }, @@ -130,38 +130,38 @@ CONFIG_DEFINE_LOCALE(Hints) CONFIG_DEFINE_LOCALE(ControlTutorial) { - { ELanguage::English, { "Control Tutorial", "Show controller hints during gameplay.\n\nThe Werehog Critical Attack prompt will be unaffected." } }, - { ELanguage::Japanese, { "アクションナビ", "ゲーム内に\u200Bアクションナビを\u200B表示するか\u200B選択できます\n\n「チャンスアタック」の\u200B表示は\u200B影響されません" } }, - { ELanguage::German, { "Steuerungsanleitung", "Zeige Steuerungshinweise während des Spiels.\n\nKritische Angriffe des Werehogs werden hiervon nicht beeinflusst." } }, - { ELanguage::French, { "Indication des commandes", "Affiche les indications des commandes pendant le jeu.\n\nCeci n'affecte pas les Coup critique du Werehog." } }, - { ELanguage::Spanish, { "Tutorial de controles", "Muestra pistas de controles durante el juego.\n\nEl indicador de ataque crítico del Werehog no se verá afectado." } }, - { ELanguage::Italian, { "Tutorial dei comandi", "Mostra i tutorial dei comandi durante il gioco.\n\nIl tutorial per l'attacco critico del Werehog non verrà influenzato da questa opzione." } } + { ELanguage::English, { "Control Tutorial", "Show controller hints during gameplay." } }, + { ELanguage::Japanese, { "アクションナビ", "ゲーム内にアクションナビを表示するか選択できます" } }, + { ELanguage::German, { "Steuerungsanleitung", "Zeige Steuerungshinweise während des Spiels." } }, + { ELanguage::French, { "Indication des commandes", "Affiche les indications des commandes pendant le jeu." } }, + { ELanguage::Spanish, { "Tutorial de controles", "Muestra pistas de controles durante el juego." } }, + { ELanguage::Italian, { "Tutorial dei comandi", "Mostra i tutorial dei comandi durante il gioco." } } }; CONFIG_DEFINE_LOCALE(Autosave) { { ELanguage::English, { "Autosave", "Save the game automatically at manual save points." } }, - { ELanguage::Japanese, { "", "" } }, - { ELanguage::German, { "", "" } }, - { ELanguage::French, { "", "" } }, - { ELanguage::Spanish, { "", "" } }, - { ELanguage::Italian, { "", "" } } + { ELanguage::Japanese, { "DUMMY", "DUMMY" } }, + { ELanguage::German, { "DUMMY", "DUMMY" } }, + { ELanguage::French, { "DUMMY", "DUMMY" } }, + { ELanguage::Spanish, { "DUMMY", "DUMMY" } }, + { ELanguage::Italian, { "DUMMY", "DUMMY" } } }; CONFIG_DEFINE_LOCALE(AchievementNotifications) { - { ELanguage::English, { "Achievement Notifications", "Show notifications for unlocking achievements.\n\nAchievements will still be rewarded with notifications disabled." } }, - { ELanguage::Japanese, { "実績通知", "実績通知の\u200B有無を\u200B選択できます\n\nオフにしても\u200B実績は\u200B付与されます" } }, - { ELanguage::German, { "Erfolgsbenachrichtigungen", "Zeige Benachrichtigungen für das Freischalten von Erfolgen.\n\nErfolge werden weiterhin freigeschaltet, auch wenn die Benachrichtigungen ausgeschaltet sind." } }, - { ELanguage::French, { "Notification des succès", "Affiche les notifications pour le déverrouillage des succès.\n\nLes succès seront toujours obtenus même si les notifications sont désactivées." } }, - { ELanguage::Spanish, { "Notificaciones de logros", "Muestra notificaciones al desbloquear logros.\n\nLos logros se seguirán obteniendo aunque las notificaciones estén desactivadas." } }, - { ELanguage::Italian, { "Notifiche obiettivi", "Mostra delle notifiche quando sblocchi degli obiettivi.\n\nGli obiettivi verranno comunque assegnati anche con le notifiche disattivate." } } + { ELanguage::English, { "Achievement Notifications", "Show notifications for unlocking achievements. Achievements will still be rewarded with notifications disabled." } }, + { ELanguage::Japanese, { "実績通知", "実績通知の有無を選択できます オフにしても実績は付与されます" } }, + { ELanguage::German, { "Erfolgsbenachrichtigungen", "Zeige Benachrichtigungen für das Freischalten von Erfolgen. Erfolge werden weiterhin freigeschaltet, auch wenn die Benachrichtigungen ausgeschaltet sind." } }, + { ELanguage::French, { "Notification des succès", "Affiche les notifications pour le déverrouillage des succès. Les succès seront toujours obtenus même si les notifications sont désactivées." } }, + { ELanguage::Spanish, { "Notificaciones de logros", "Muestra notificaciones al desbloquear logros. Los logros se seguirán obteniendo aunque las notificaciones estén desactivadas." } }, + { ELanguage::Italian, { "Notifiche obiettivi", "Mostra delle notifiche quando sblocchi degli obiettivi. Gli obiettivi verranno comunque assegnati anche con le notifiche disattivate." } } }; CONFIG_DEFINE_LOCALE(ControllerIcons) { { ELanguage::English, { "Controller Icons", "Change the icons to match your controller." } }, - { ELanguage::Japanese, { "コントローラーアイコン", "ゲーム内の\u200Bコントローラーアイコン\u200Bを変更できます" } }, + { ELanguage::Japanese, { "コントローラーアイコン", "ゲーム内のコントローラーアイコンを変更できます" } }, { ELanguage::German, { "Controllersymbole", "Ändere die Controllersymbole, um sie auf dein Modell anzupassen." } }, { ELanguage::French, { "Icône des boutons", "Modifie les icônes pour les faire correspondre à votre manette." } }, { ELanguage::Spanish, { "Iconos del mando", "Cambia los iconos para que coincidan con tu mando." } }, @@ -173,49 +173,49 @@ CONFIG_DEFINE_ENUM_LOCALE(EControllerIcons) { ELanguage::English, { - { EControllerIcons::Auto, { "AUTO", "Auto: the game will determine which icons to use based on the current input device." } }, - { EControllerIcons::Xbox, { "XBOX", "" } }, - { EControllerIcons::PlayStation, { "PLAYSTATION", "" } } + { EControllerIcons::Auto, { "Auto", "Auto: the game will determine which icons to use based on the current input device." } }, + { EControllerIcons::Xbox, { "Xbox", "" } }, + { EControllerIcons::PlayStation, { "PlayStation", "" } } } }, { ELanguage::Japanese, { - { EControllerIcons::Auto, { "自動検出", "\u2005自動検出: コントローラーアイコン\u200Bを使用している\u200B\u2005入\u2005力\u2005デバイスに\u200B応じて\u200B自動的に\u200B決定します" } }, - { EControllerIcons::Xbox, { "XBOX", "" } }, - { EControllerIcons::PlayStation, { "PLAYSTATION", "" } } + { EControllerIcons::Auto, { "自動検出", "自動検出: コントローラーアイコンを使用している入力デバイスに応じて自動的に決定します" } }, + { EControllerIcons::Xbox, { "Xbox", "" } }, + { EControllerIcons::PlayStation, { "PlayStation", "" } } } }, { ELanguage::German, { - { EControllerIcons::Auto, { "AUTO", "Auto: Das Spiel erkennt automatisch deinen Controller um die Symbole dementsprechend anzupassen." } }, - { EControllerIcons::Xbox, { "XBOX", "" } }, - { EControllerIcons::PlayStation, { "PLAYSTATION", "" } } + { EControllerIcons::Auto, { "Auto", "Auto: Das Spiel erkennt automatisch deinen Controller um die Symbole dementsprechend anzupassen." } }, + { EControllerIcons::Xbox, { "Xbox", "" } }, + { EControllerIcons::PlayStation, { "PlayStation", "" } } } }, { ELanguage::French, { - { EControllerIcons::Auto, { "AUTO", "Auto : le jeu déterminera automatiquement quelles icônes utiliser en fonction du périphérique d'entrée." } }, - { EControllerIcons::Xbox, { "XBOX", "" } }, - { EControllerIcons::PlayStation, { "PLAYSTATION", "" } } + { EControllerIcons::Auto, { "Auto", "Auto : le jeu déterminera automatiquement quelles icônes utiliser en fonction du périphérique d'entrée." } }, + { EControllerIcons::Xbox, { "Xbox", "" } }, + { EControllerIcons::PlayStation, { "PlayStation", "" } } } }, { ELanguage::Spanish, { - { EControllerIcons::Auto, { "AUTO", "Auto: el juego determinará de forma automática qué iconos utilizar dependiendo del dispositivo de entrada actual." } }, - { EControllerIcons::Xbox, { "XBOX", "" } }, - { EControllerIcons::PlayStation, { "PLAYSTATION", "" } } + { EControllerIcons::Auto, { "Auto", "Auto: el juego determinará de forma automática qué iconos utilizar dependiendo del dispositivo de entrada actual." } }, + { EControllerIcons::Xbox, { "Xbox", "" } }, + { EControllerIcons::PlayStation, { "PlayStation", "" } } } }, { ELanguage::Italian, { - { EControllerIcons::Auto, { "AUTO", "Auto: il gioco determinerà quali icone da utilizzare in base al dispositivo di input attuale." } }, - { EControllerIcons::Xbox, { "XBOX", "" } }, - { EControllerIcons::PlayStation, { "PLAYSTATION", "" } } + { EControllerIcons::Auto, { "Auto", "Auto: il gioco determinerà quali icone da utilizzare in base al dispositivo di input attuale." } }, + { EControllerIcons::Xbox, { "Xbox", "" } }, + { EControllerIcons::PlayStation, { "PlayStation", "" } } } } }; @@ -223,7 +223,7 @@ CONFIG_DEFINE_ENUM_LOCALE(EControllerIcons) CONFIG_DEFINE_LOCALE(HorizontalCamera) { { ELanguage::English, { "Horizontal Camera", "Change how the camera moves left and right." } }, - { ELanguage::Japanese, { "カメラの左右", "カメラ左右の\u200B動く方向を\u200B選択できます" } }, + { ELanguage::Japanese, { "カメラの左右", "カメラ左右の動く方向を選択できます" } }, { ELanguage::German, { "Horizontale Kamera", "Ändere wie sich die Kamera nach links und rechts bewegt." } }, { ELanguage::French, { "Caméra horizontale", "Modifie la rotation horizontale de la caméra." } }, { ELanguage::Spanish, { "Cámara horizontal", "Cambia cómo se mueve la camara hacia la izquierda y la derecha." } }, @@ -233,7 +233,7 @@ CONFIG_DEFINE_LOCALE(HorizontalCamera) CONFIG_DEFINE_LOCALE(VerticalCamera) { { ELanguage::English, { "Vertical Camera", "Change how the camera moves up and down." } }, - { ELanguage::Japanese, { "カメラの上下", "カメラ上下の\u200B動く方向を\u200B選択できます" } }, + { ELanguage::Japanese, { "カメラの上下", "カメラ上下の動く方向を選択できます" } }, { ELanguage::German, { "Vertikale Kamera", "Ändere wie sich die Kamera nach oben und unten bewegt." } }, { ELanguage::French, { "Caméra verticale", "Modifie la rotation verticale de la caméra." } }, { ELanguage::Spanish, { "Cámara vertical", "Cambia cómo se mueve la camara hacia arriba y abajo." } }, @@ -245,8 +245,8 @@ CONFIG_DEFINE_ENUM_LOCALE(ECameraRotationMode) { ELanguage::English, { - { ECameraRotationMode::Normal, { "NORMAL", "" } }, - { ECameraRotationMode::Reverse, { "REVERSE", "" } } + { ECameraRotationMode::Normal, { "Normal", "" } }, + { ECameraRotationMode::Reverse, { "Reverse", "" } } } }, { @@ -259,47 +259,37 @@ CONFIG_DEFINE_ENUM_LOCALE(ECameraRotationMode) { ELanguage::German, { - { ECameraRotationMode::Normal, { "NORMAL", "" } }, - { ECameraRotationMode::Reverse, { "INVERTIERT", "" } } + { ECameraRotationMode::Normal, { "Normal", "" } }, + { ECameraRotationMode::Reverse, { "Invertiert", "" } } } }, { ELanguage::French, { - { ECameraRotationMode::Normal, { "NORMALE", "" } }, - { ECameraRotationMode::Reverse, { "INVERSÉE", "" } } + { ECameraRotationMode::Normal, { "Normale", "" } }, + { ECameraRotationMode::Reverse, { "Inversée", "" } } } }, { ELanguage::Spanish, { - { ECameraRotationMode::Normal, { "NORMAL", "" } }, - { ECameraRotationMode::Reverse, { "INVERTIDO", "" } } + { ECameraRotationMode::Normal, { "Normal", "" } }, + { ECameraRotationMode::Reverse, { "Invertido", "" } } } }, { ELanguage::Italian, { - { ECameraRotationMode::Normal, { "NORMALE", "" } }, - { ECameraRotationMode::Reverse, { "INVERTITA", "" } } + { ECameraRotationMode::Normal, { "Normale", "" } }, + { ECameraRotationMode::Reverse, { "Invertita", "" } } } } }; -CONFIG_DEFINE_LOCALE(Vibration) -{ - { ELanguage::English, { "Vibration", "Toggle controller vibration." } }, - { ELanguage::Japanese, { "振動", "振動の\u200B有無を\u200B選択できます" } }, - { ELanguage::German, { "Vibration", "Schalte die Controllervibration an oder aus." } }, - { ELanguage::French, { "Vibration", "Active les vibrations de la manette." } }, - { ELanguage::Spanish, { "Vibración", "Activa o desactiva la vibración del mando." } }, - { ELanguage::Italian, { "Vibrazione", "Attiva/disattiva la vibrazione del controller." } } -}; - CONFIG_DEFINE_LOCALE(AllowBackgroundInput) { { ELanguage::English, { "Allow Background Input", "Allow controller input whilst the game window is unfocused." } }, - { ELanguage::Japanese, { "バックグラウンド入力", "フォーカスされていない\u200Bゲームに\u200B入力できるか\u200B選択できます" } }, + { ELanguage::Japanese, { "バックグラウンド入力", "フォーカスされていないゲームに入力できるか選択できます" } }, { ELanguage::German, { "Erlaube Hintergrundeingaben", "Erlaube Eingaben deines Controllers auch wenn das Spielfenster nicht fokussiert ist." } }, { ELanguage::French, { "Manette en arrière plan", "Permet d'utiliser la manette dans le jeu lorsque qu'il n'est pas au premier plan." } }, { ELanguage::Spanish, { "Control en segundo plano", "Permite controlar el juego con un mando mientras la ventana esté en segundo plano." } }, @@ -309,7 +299,7 @@ CONFIG_DEFINE_LOCALE(AllowBackgroundInput) CONFIG_DEFINE_LOCALE(MasterVolume) { { ELanguage::English, { "Master Volume", "Adjust the overall volume." } }, - { ELanguage::Japanese, { "マスターボリューム", "全体ボリュームの\u200B大きさを\u200B調整できます" } }, + { ELanguage::Japanese, { "マスターボリューム", "全体ボリュームの大きさを調整できます" } }, { ELanguage::German, { "Gesamtlautstärke", "Passe die Gesamtlautstärke an." } }, { ELanguage::French, { "Volume général", "Réglage du volume général." } }, { ELanguage::Spanish, { "Volumen maestro", "Ajusta el volumen general." } }, @@ -319,7 +309,7 @@ CONFIG_DEFINE_LOCALE(MasterVolume) CONFIG_DEFINE_LOCALE(MusicVolume) { { ELanguage::English, { "Music Volume", "Adjust the volume for the music." } }, - { ELanguage::Japanese, { "BGMボリューム", "BGMボリュームの\u200B大きさを\u200B調整できます" } }, + { ELanguage::Japanese, { "BGMボリューム", "BGMボリュームの大きさを調整できます" } }, { ELanguage::German, { "Musiklautstärke", "Passe die Lautstärke der Musik an." } }, { ELanguage::French, { "Volume de la musique", "Réglage du volume de la musique." } }, { ELanguage::Spanish, { "Volumen de la música", "Ajusta el volumen de la música." } }, @@ -329,7 +319,7 @@ CONFIG_DEFINE_LOCALE(MusicVolume) CONFIG_DEFINE_LOCALE(EffectsVolume) { { ELanguage::English, { "Effects Volume", "Adjust the volume for sound effects." } }, - { ELanguage::Japanese, { "SEボリューム", "SEボリュームの\u200B大きさを\u200B調整できます" } }, + { ELanguage::Japanese, { "SEボリューム", "SEボリュームの大きさを調整できます" } }, { ELanguage::German, { "Soundeffektlautstärke", "Passe die Lautstärke der Soundeffekte an." } }, { ELanguage::French, { "Volume des effets sonores", "Réglage du volume des effets sonores." } }, { ELanguage::Spanish, { "Volumen de efectos", "Ajusta el volumen de los efectos de sonido." } }, @@ -339,7 +329,7 @@ CONFIG_DEFINE_LOCALE(EffectsVolume) CONFIG_DEFINE_LOCALE(MusicAttenuation) { { ELanguage::English, { "Music Attenuation", "Fade out the game's music when external media is playing." } }, - { ELanguage::Japanese, { "BGM減衰", "外部メディアを\u200B再生すると\u200Bゲームの\u200B音楽を\u200Bフェードアウト\u200Bします" } }, + { ELanguage::Japanese, { "BGM減衰", "外部メディアを再生するとゲームの音楽をフェードアウトします" } }, { ELanguage::German, { "Musikdämpfung", "Stelle die Musik des Spiels stumm während externe Medien abgespielt werden." } }, { ELanguage::French, { "Atténuation audio", "Abaisse le volume des musiques du jeu lorsqu'un média externe est en cours de lecture." } }, { ELanguage::Spanish, { "Atenuación de música", "Atenúa la música del juego cuando un reproductor multimedia se encuentra activo." } }, @@ -349,7 +339,7 @@ CONFIG_DEFINE_LOCALE(MusicAttenuation) CONFIG_DEFINE_LOCALE(ChannelConfiguration) { { ELanguage::English, { "Channel Configuration", "Change the output mode for your audio device." } }, - { ELanguage::Japanese, { "チャンネル設定", "オーディオデバイスの\u200B\u2005出\u2005力\u2005モード\u200Bを変更できます" } }, + { ELanguage::Japanese, { "チャンネル設定", "オーディオデバイスの出力モードを変更できます" } }, { ELanguage::German, { "Kanalkonfiguration", "Ändere den Ausgabemodus für dein Audioausgabegerät." } }, { ELanguage::French, { "Configuration sortie audio", "Modifie le mode de sortie pour votre périphérique audio." } }, { ELanguage::Spanish, { "Configuración de canales", "Cambia el modo de salida para tu dispositivo de audio." } }, @@ -361,8 +351,8 @@ CONFIG_DEFINE_ENUM_LOCALE(EChannelConfiguration) { ELanguage::English, { - { EChannelConfiguration::Stereo, { "STEREO", "" } }, - { EChannelConfiguration::Surround, { "SURROUND", "" } } + { EChannelConfiguration::Stereo, { "Stereo", "" } }, + { EChannelConfiguration::Surround, { "Surround", "" } } } }, { @@ -375,29 +365,29 @@ CONFIG_DEFINE_ENUM_LOCALE(EChannelConfiguration) { ELanguage::German, { - { EChannelConfiguration::Stereo, { "STEREO", "" } }, - { EChannelConfiguration::Surround, { "SURROUND", "" } } + { EChannelConfiguration::Stereo, { "Stereo", "" } }, + { EChannelConfiguration::Surround, { "Surround", "" } } } }, { ELanguage::French, { - { EChannelConfiguration::Stereo, { "STÉRÉO", "" } }, - { EChannelConfiguration::Surround, { "SURROUND", "" } } + { EChannelConfiguration::Stereo, { "Stéréo", "" } }, + { EChannelConfiguration::Surround, { "Surround", "" } } } }, { ELanguage::Spanish, { - { EChannelConfiguration::Stereo, { "ESTÉREO", "" } }, - { EChannelConfiguration::Surround, { "ENVOLVENTE", "" } } + { EChannelConfiguration::Stereo, { "Estéreo", "" } }, + { EChannelConfiguration::Surround, { "Envolvente", "" } } } }, { ELanguage::Italian, { - { EChannelConfiguration::Stereo, { "STEREO", "" } }, - { EChannelConfiguration::Surround, { "SURROUND", "" } } + { EChannelConfiguration::Stereo, { "Stereo", "" } }, + { EChannelConfiguration::Surround, { "Surround", "" } } } } }; @@ -405,7 +395,7 @@ CONFIG_DEFINE_ENUM_LOCALE(EChannelConfiguration) CONFIG_DEFINE_LOCALE(VoiceLanguage) { { ELanguage::English, { "Voice Language", "Change the language used for character voices." } }, - { ELanguage::Japanese, { "音声言語", "ゲーム内の\u200B音声言語を\u200B変更できます" } }, + { ELanguage::Japanese, { "音声言語", "ゲーム内の音声言語を変更できます" } }, { ELanguage::German, { "Stimmeinstellung", "Ändere die Sprache, die für Charakterstimmen benutzt wird." } }, { ELanguage::French, { "Langue de doublage", "Modifie la langue utilisée pour la voix des personnages." } }, { ELanguage::Spanish, { "Idioma de voz", "Cambia el idioma utilizado para las voces de los personajes." } }, @@ -415,27 +405,17 @@ CONFIG_DEFINE_LOCALE(VoiceLanguage) CONFIG_DEFINE_LOCALE(Subtitles) { { ELanguage::English, { "Subtitles", "Show subtitles during dialogue." } }, - { ELanguage::Japanese, { "字幕", "字幕の表示を\u200B選択できます" } }, + { ELanguage::Japanese, { "字幕", "字幕の表示を選択できます" } }, { ELanguage::German, { "Untertitel", "Zeige Untertitel bei Dialogen." } }, { ELanguage::French, { "Sous-titres", "Affiche les sous-titres pendant les dialogues." } }, { ELanguage::Spanish, { "Subtítulos", "Muestra subtítulos durante los diálogos." } }, { ELanguage::Italian, { "Sottotitoli", "Mostra i sottotitoli durante i dialoghi." } } }; -CONFIG_DEFINE_LOCALE(BattleTheme) -{ - { ELanguage::English, { "Battle Theme", "Play the Werehog battle theme during combat.\n\nThis option will apply the next time you're in combat.\n\nExorcism missions and miniboss themes will be unaffected." } }, - { ELanguage::Japanese, { "バトルテーマ", "バトル中に\u200Bウェアホッグの\u200Bバトルテーマを\u200B再生するか\u200B選択できます\n\nこのオプションは\u200B次回のバトルから\u200B適用されます\n\n\u2005除\u2005霊\u2005ミッションと\u200Bミニボステーマには\u200B\u2005影響\u2005しません" } }, - { ELanguage::German, { "Kampfmusik", "Spiele die Kampfmusik des Werehogs während dem Kämpfen ab.\n\nDiese Option tritt das nächste Mal, wenn du in einen Kampf gerätst, in Kraft.\n\nExorzismen und Mini-Bosse werden hiervon nicht beeinflusst." } }, - { ELanguage::French, { "Thème de combat", "Joue le thème de combat du Werehog pendant ces derniers.\n\nCette option s'appliquera la prochaine fois que vous serez en combat.\n\nLes missions d'exorcisme et les thèmes des miniboss ne seront pas affectés." } }, - { ELanguage::Spanish, { "Tema de batalla", "Reproduce el tema de batalla del Werehog durante el combate.\n\nEsta opción se aplicará la próxima vez que entres en combate.\n\nLas misiones de exorcismo y los temas de los minijefes no se verán afectados." } }, - { ELanguage::Italian, { "Musica di combattimento", "Riproduci la musica di combattimento del Werehog quando inizi una battaglia.\n\nQuesta opzione verrà applicata la prossima volta che sei in battaglia.\n\nLa traccia musicale verrà riprodotta ugualmente nelle missioni di Esorcismo e i miniboss." } } -}; - CONFIG_DEFINE_LOCALE(WindowSize) { { ELanguage::English, { "Window Size", "Adjust the size of the game window in windowed mode." } }, - { ELanguage::Japanese, { "ウィンドウサイズ", "ウィンドウ\u200Bモードでの\u200Bゲームの\u200Bウィンドウサイズを\u200B調整できます" } }, + { ELanguage::Japanese, { "ウィンドウサイズ", "ウィンドウモードでのゲームのウィンドウサイズを調整できます" } }, { ELanguage::German, { "Fenstergröße", "Ändere die Größe des Spielfensters im Fenstermodus." } }, { ELanguage::French, { "Taille de la fenêtre", "Modifie la taille de la fenêtre de jeu en mode fenêtré." } }, { ELanguage::Spanish, { "Tamaño de ventana", "Ajusta el tamaño de la ventana de juego." } }, @@ -445,7 +425,7 @@ CONFIG_DEFINE_LOCALE(WindowSize) CONFIG_DEFINE_LOCALE(Monitor) { { ELanguage::English, { "Monitor", "Change which monitor to display the game on." } }, - { ELanguage::Japanese, { "モニター選択", "ゲームを表示する\u200Bモニターを\u200B変更できます" } }, + { ELanguage::Japanese, { "モニター選択", "ゲームを表示するモニターを変更できます" } }, { ELanguage::German, { "Monitor", "Ändere auf welchem Monitor das Spiel angezeigt wird." } }, { ELanguage::French, { "Moniteur", "Change le moniteur sur lequel le jeu sera affiché." } }, { ELanguage::Spanish, { "Pantalla", "Cambia la pantalla en la cuál se muestra el juego." } }, @@ -455,7 +435,7 @@ CONFIG_DEFINE_LOCALE(Monitor) CONFIG_DEFINE_LOCALE(AspectRatio) { { ELanguage::English, { "Aspect Ratio", "Change the aspect ratio." } }, - { ELanguage::Japanese, { "アスペクト比", "アスペクト比を\u200B変更できます" } }, + { ELanguage::Japanese, { "アスペクト比", "アスペクト比を変更できます" } }, { ELanguage::German, { "Seitenverhältnis", "Verändere das Seitenverhältnis." } }, { ELanguage::French, { "Format d'image", "Modifie le format d'image." } }, { ELanguage::Spanish, { "Relación de aspecto", "Cambia la relación de aspecto." } }, @@ -467,73 +447,61 @@ CONFIG_DEFINE_ENUM_LOCALE(EAspectRatio) { ELanguage::English, { - { EAspectRatio::Auto, { "AUTO", "Auto: the aspect ratio will dynamically adjust to the window size." } }, - { EAspectRatio::Wide, { "16:9", "16:9: locks the game to a widescreen aspect ratio." } }, - { EAspectRatio::Narrow, { "4:3", "4:3: locks the game to a narrow aspect ratio." } }, - { EAspectRatio::OriginalNarrow, { "ORIGINAL 4:3", "Original 4:3: locks the game to a narrow aspect ratio and retains parity with the game's original implementation." } } + { EAspectRatio::Auto, { "Auto", "Auto: the aspect ratio will dynamically adjust to the window size." } }, + { EAspectRatio::Wide, { "16:9", "16:9: locks the game to a widescreen aspect ratio." } } } }, { ELanguage::Japanese, { - { EAspectRatio::Auto, { "自動", "自動: アスペクト比は\u200Bウィンドウサイズに\u200B合わせて\u200B調整されます" } }, - { EAspectRatio::Wide, { "16:9", "16:9: ワイドスクリーンの\u200Bアスペクト比に\u200B固定されます" } }, - { EAspectRatio::Narrow, { "4:3", "4:3: ナローの\u200Bアスペクト比に\u200B固定されます" } }, - { EAspectRatio::OriginalNarrow, { "オリジナル 4:3", "オリジナル 4:3: オリジナルの\u200Bアスペクト比に\u200B固定されます" } } + { EAspectRatio::Auto, { "自動", "自動: アスペクト比はウィンドウサイズに合わせて調整されます" } }, + { EAspectRatio::Wide, { "16:9", "16:9: ワイドスクリーンのアスペクト比に固定されます" } } } }, { ELanguage::German, { - { EAspectRatio::Auto, { "AUTO", "Auto: Das Seitenverhältnis passt sich automatisch der Fenstergröße an." } }, - { EAspectRatio::Wide, { "16:9", "16:9: Stellt das Spiel in einem Breitbildschirm-Format dar." } }, - { EAspectRatio::Narrow, { "4:3", "4:3: Stellt das Spiel in einem Mittel-Format dar." } }, - { EAspectRatio::OriginalNarrow, { "ORIGINAL 4:3", "Original 4:3: Stellt das Spiel in einem Mittel-Format dar, was der ursprünglichen Implementation originalgetreut bleibt." } } + { EAspectRatio::Auto, { "Auto", "Auto: Das Seitenverhältnis passt sich automatisch der Fenstergröße an." } }, + { EAspectRatio::Wide, { "16:9", "16:9: Stellt das Spiel in einem Breitbildschirm-Format dar." } } } }, { ELanguage::French, { - { EAspectRatio::Auto, { "AUTO", "Auto : le format d'image s'adapte automatiquement à la taille de la fenêtre." } }, - { EAspectRatio::Wide, { "16:9", "16:9 : force le jeu sur un format d'image large." } }, - { EAspectRatio::Narrow, { "4:3", "4:3 : force le jeu sur un format d'image carré." } }, - { EAspectRatio::OriginalNarrow, { "4:3 ORIGINAL", "4:3 original : force le jeu à un format d'image carré et conserve la parité avec l'implémentation d'origine du jeu." } } + { EAspectRatio::Auto, { "Auto", "Auto : le format d'image s'adapte automatiquement à la taille de la fenêtre." } }, + { EAspectRatio::Wide, { "16:9", "16:9 : force le jeu sur un format d'image large." } } } }, { ELanguage::Spanish, { - { EAspectRatio::Auto, { "AUTO", "Auto: la relación de aspecto se ajusta de forma dinámica al tamaño de la ventana." } }, - { EAspectRatio::Wide, { "16:9", "16:9: muestra el juego en relación de aspecto de pantalla ancha." } }, - { EAspectRatio::Narrow, { "4:3", "4:3: muestra el juego en relación de aspecto de pantalla estrecha." } }, - { EAspectRatio::OriginalNarrow, { "ORIGINAL 4:3", "Original 4:3: muestra el juego en relación de aspecto de pantalla estrecha y mantiene paridad con la implementación original del juego." } } + { EAspectRatio::Auto, { "Auto", "Auto: la relación de aspecto se ajusta de forma dinámica al tamaño de la ventana." } }, + { EAspectRatio::Wide, { "16:9", "16:9: muestra el juego en relación de aspecto de pantalla ancha." } } } }, { ELanguage::Italian, { - { EAspectRatio::Auto, { "AUTO", "Auto: il rapporto d'aspetto verra cambiato automaticamente in base alle dimensioni della finestra." } }, - { EAspectRatio::Wide, { "16:9", "16:9: blocca il gioco a un rapporto d'aspetto widescreen." } }, - { EAspectRatio::Narrow, { "4:3", "4:3: blocca il gioco a un rapporto d'aspetto stretto." } }, - { EAspectRatio::OriginalNarrow, { "4:3 ORIGINALE", "4:3 Originale: blocca il gioco a un rapporto d'aspetto stretto e mantiene la parità con l'implementazione originale del gioco." } } + { EAspectRatio::Auto, { "Auto", "Auto: il rapporto d'aspetto verra cambiato automaticamente in base alle dimensioni della finestra." } }, + { EAspectRatio::Wide, { "16:9", "16:9: blocca il gioco a un rapporto d'aspetto widescreen." } } } } }; CONFIG_DEFINE_LOCALE(ResolutionScale) { - { ELanguage::English, { "Resolution Scale", "Adjust the internal resolution of the game.\n\n%dx%d" } }, - { ELanguage::Japanese, { "解像度スケール", "ゲームの\u200B内部解像度を\u200B調整できます\n\n%dx%d" } }, - { ELanguage::German, { "Rendering-Auflösung", "Passe die Auflösung der internen Darstellung an.\n\n%dx%d" } }, - { ELanguage::French, { "Échelle de rendu", "Modifie la résolution interne du jeu.\n\n%dx%d" } }, - { ELanguage::Spanish, { "Escala de resolución", "Ajusta la resolución interna del juego.\n\n%dx%d" } }, - { ELanguage::Italian, { "Scala risoluzione", "Regola la risoluzione interna del gioco.\n\n%dx%d" } } + { ELanguage::English, { "Resolution Scale", "Adjust the internal resolution of the game." } }, + { ELanguage::Japanese, { "解像度スケール", "ゲームの内部解像度を調整できます" } }, + { ELanguage::German, { "Rendering-Auflösung", "Passe die Auflösung der internen Darstellung an." } }, + { ELanguage::French, { "Échelle de rendu", "Modifie la résolution interne du jeu." } }, + { ELanguage::Spanish, { "Escala de resolución", "Ajusta la resolución interna del juego." } }, + { ELanguage::Italian, { "Scala risoluzione", "Regola la risoluzione interna del gioco." } } }; CONFIG_DEFINE_LOCALE(Fullscreen) { { ELanguage::English, { "Fullscreen", "Toggle between borderless fullscreen or windowed mode." } }, - { ELanguage::Japanese, { "フルスクリーン", "ボーダーレス\u200Bフルスクリーンか\u200Bウィンドウモードを\u200B選択できます" } }, + { ELanguage::Japanese, { "フルスクリーン", "ボーダーレスフルスクリーンかウィンドウモードを選択できます" } }, { ELanguage::German, { "Vollbild", "Wechsle zwischen dem randlosen Vollbildmodus und dem Fenstermodus." } }, { ELanguage::French, { "Plein écran", "Alterne entre le mode plein écran sans bordures et le mode fenêtré." } }, { ELanguage::Spanish, { "Pantalla completa", "Cambia entre modo de pantalla completa o ventana." } }, @@ -543,7 +511,7 @@ CONFIG_DEFINE_LOCALE(Fullscreen) CONFIG_DEFINE_LOCALE(VSync) { { ELanguage::English, { "V-Sync", "Synchronize the game to the refresh rate of the display to prevent screen tearing." } }, - { ELanguage::Japanese, { "垂直同期", "垂直同期の\u200B設定を\u200B変更できます" } }, + { ELanguage::Japanese, { "垂直同期", "垂直同期の設定を変更できます" } }, { ELanguage::German, { "V-Sync", "Synchronisiere das Spiel mit der Bildwiederholrate deines Bildschirms um Bildverzerrungen zu vermeiden." } }, { ELanguage::French, { "V-Sync", "Synchronise le jeu avec la fréquence de rafraîchissement de l'écran pour éviter le screen tearing." } }, { ELanguage::Spanish, { "V-Sync", "Sincroniza el juego a la tasa de refresco de la pantalla para prevenir el rasgado de la imagen." } }, @@ -552,28 +520,28 @@ CONFIG_DEFINE_LOCALE(VSync) CONFIG_DEFINE_LOCALE(FPS) { - { ELanguage::English, { "FPS", "Set the max frame rate the game can run at.\n\nWARNING: this may introduce glitches at frame rates higher than 60 FPS." } }, - { ELanguage::Japanese, { "フレームレート上限", "ゲームの\u200B最大フレームレートを\u200B設定できます\n\n警告: 60 FPSを\u200B超えるフレームレートで\u200B不具合が\u200B発生する\u200B可能性が\u200Bあります" } }, - { ELanguage::German, { "FPS", "Setze die maximale Anzahl der Bilder pro Sekunde, die das Spiel darstellen kann.\n\nWARNUNG: Das Spiel kann bei höheren FPS als 60 ungewolltes Verhalten aufweisen." } }, - { ELanguage::French, { "IPS", "Détermine la fréquence d'images maximale du jeu.\n\nATTENTION : cela peut entraîner des problèmes à des taux de rafraîchissement supérieurs à 60 IPS." } }, - { ELanguage::Spanish, { "FPS", "Establece la tasa de fotogramas máxima a la que puede correr el juego.\n\nADVERTENCIA: esto puede introducir fallos en tasas mayores a 60 FPS." } }, - { ELanguage::Italian, { "FPS", "Imposta il frame rate massimo del gioco.\n\nATTENZIONE: questa opzione può causare dei glitch a frame rate più alti di 60 FPS." } } + { ELanguage::English, { "FPS", "Set the max frame rate the game can run at. WARNING: this may introduce glitches at frame rates higher than 60 FPS." } }, + { ELanguage::Japanese, { "フレームレート上限", "ゲームの最大フレームレートを設定できます 警告: 60 FPSを超えるフレームレートで不具合が発生する可能性があります" } }, + { ELanguage::German, { "FPS", "Setze die maximale Anzahl der Bilder pro Sekunde, die das Spiel darstellen kann. WARNUNG: Das Spiel kann bei höheren FPS als 60 ungewolltes Verhalten aufweisen." } }, + { ELanguage::French, { "IPS", "Détermine la fréquence d'images maximale du jeu. ATTENTION : cela peut entraîner des problèmes à des taux de rafraîchissement supérieurs à 60 IPS." } }, + { ELanguage::Spanish, { "FPS", "Establece la tasa de fotogramas máxima a la que puede correr el juego. ADVERTENCIA: esto puede introducir fallos en tasas mayores a 60 FPS." } }, + { ELanguage::Italian, { "FPS", "Imposta il frame rate massimo del gioco. ATTENZIONE: questa opzione può causare dei glitch a frame rate più alti di 60 FPS." } } }; CONFIG_DEFINE_LOCALE(Brightness) { - { ELanguage::English, { "Brightness", "Adjust the brightness level until the symbol on the left is barely visible." } }, - { ELanguage::Japanese, { "明るさの設定", "画面の\u200B明るさを\u200B調整できます" } }, - { ELanguage::German, { "Helligkeit", "Passe die Helligkeit des Spiels an bis das linke Symbol noch gerade so sichtbar ist." } }, - { ELanguage::French, { "Luminosité", "Réglez le niveau de luminosité jusqu'à ce que le symbole à gauche soit à peine visible." } }, - { ELanguage::Spanish, { "Brillo", "Ajusta el nivel de brillo hasta que el símbolo a la izquierda sea apenas visible." } }, - { ELanguage::Italian, { "Luminosità", "Regola la luminosità dello schermo fino a quando il simbolo a sinistra diventa leggermente visibile." } } + { ELanguage::English, { "Brightness", "Adjust the brightness level." } }, + { ELanguage::Japanese, { "明るさの設定", "DUMMY" } }, + { ELanguage::German, { "Helligkeit", "DUMMY" } }, + { ELanguage::French, { "Luminosité", "DUMMY" } }, + { ELanguage::Spanish, { "Brillo", "DUMMY" } }, + { ELanguage::Italian, { "Luminosità", "DUMMY" } } }; CONFIG_DEFINE_LOCALE(AntiAliasing) { { ELanguage::English, { "Anti-Aliasing", "Adjust the amount of smoothing applied to jagged edges." } }, - { ELanguage::Japanese, { "アンチエイリアス", "アンチエイリアスの\u200B種類を\u200B選択できます" } }, + { ELanguage::Japanese, { "アンチエイリアス", "アンチエイリアスの種類を選択できます" } }, { ELanguage::German, { "Kantenglättung", "Passe die Menge an Kantenglättung an." } }, { ELanguage::French, { "Anticrénelage", "Ajuste le niveau d'anticrénelage appliqué aux bords des objets." } }, { ELanguage::Spanish, { "Anti-Aliasing", "Ajusta el nivel de suavizado aplicado a los dientes de sierra." } }, @@ -585,45 +553,45 @@ CONFIG_DEFINE_ENUM_LOCALE(EAntiAliasing) { ELanguage::English, { - { EAntiAliasing::None, { "NONE", "" } } + { EAntiAliasing::Off, { "Off", "" } } } }, { ELanguage::Japanese, { - { EAntiAliasing::None, { "オフ", "" } }, + { EAntiAliasing::Off, { "オフ", "" } }, } }, { ELanguage::German, { - { EAntiAliasing::None, { "KEINE", "" } } + { EAntiAliasing::Off, { "Aus", "" } } } }, { ELanguage::French, { - { EAntiAliasing::None, { "SANS", "" } } + { EAntiAliasing::Off, { "Non", "" } } } }, { ELanguage::Spanish, { - { EAntiAliasing::None, { "NO", "" } }, + { EAntiAliasing::Off, { "No", "" } }, } }, { ELanguage::Italian, { - { EAntiAliasing::None, { "NULLO", "" } } + { EAntiAliasing::Off, { "No", "" } } } - }, + } }; CONFIG_DEFINE_LOCALE(TransparencyAntiAliasing) { { ELanguage::English, { "Transparency Anti-Aliasing", "Apply anti-aliasing to alpha transparent textures." } }, - { ELanguage::Japanese, { "透明度のアンチエイリアス", "透過テクスチャに\u200Bアンチエイリアスを\u200B適用します" } }, + { ELanguage::Japanese, { "透明度のアンチエイリアス", "透過テクスチャにアンチエイリアスを適用します" } }, { ELanguage::German, { "Transparenz-Kantenglättung", "Wende Kantenglättung auf Alpha-Transparenz-Texturen an." } }, { ELanguage::French, { "Anticrénelage de transparence", "Applique l'anticrénelage sur les textures transparentes." } }, { ELanguage::Spanish, { "Anti-Aliasing de transparencias", "Aplica antialiasing a las texturas transparentes." } }, @@ -633,17 +601,104 @@ CONFIG_DEFINE_LOCALE(TransparencyAntiAliasing) CONFIG_DEFINE_LOCALE(ShadowResolution) { { ELanguage::English, { "Shadow Resolution", "Set the resolution of real-time shadows." } }, - { ELanguage::Japanese, { "影の解像度", "影の解像度を\u200B設定できます" } }, + { ELanguage::Japanese, { "影の解像度", "影の解像度を設定できます" } }, { ELanguage::German, { "Schattenauflösung", "Stelle die Auflösung der Echtzeit-Schatten ein." } }, { ELanguage::French, { "Résolution des ombres", "Définit la résolution des ombres en temps réel." } }, { ELanguage::Spanish, { "Resolución de sombras", "Establece la resolución de las sombras de tiempo real." } }, { ELanguage::Italian, { "Risoluzione ombre", "Imposta la risoluzioni delle ombre in tempo reale." } } }; +CONFIG_DEFINE_ENUM_LOCALE(EShadowResolution) {}; + +CONFIG_DEFINE_LOCALE(ReflectionResolution) +{ + { ELanguage::English, { "Reflection Resolution", "Set the resolution of real-time reflections." } }, + { ELanguage::Japanese, { "DUMMY", "DUMMY" } }, + { ELanguage::German, { "DUMMY", "DUMMY" } }, + { ELanguage::French, { "DUMMY", "DUMMY" } }, + { ELanguage::Spanish, { "DUMMY", "DUMMY" } }, + { ELanguage::Italian, { "DUMMY", "DUMMY" } } +}; + +CONFIG_DEFINE_ENUM_LOCALE(EReflectionResolution) +{ + { + ELanguage::English, + { + { EReflectionResolution::Eighth, { "12.5%", "" } }, + { EReflectionResolution::Quarter, { "25%", "" } }, + { EReflectionResolution::Half, { "50%", "" } }, + { EReflectionResolution::Full, { "100%", "" } } + } + } +}; + +CONFIG_DEFINE_LOCALE(RadialBlur) +{ + { ELanguage::English, { "Radial Blur", "Change the quality of the radial blur." } }, + { ELanguage::Japanese, { "DUMMY", "DUMMY" } }, + { ELanguage::German, { "DUMMY", "DUMMY" } }, + { ELanguage::French, { "DUMMY", "DUMMY" } }, + { ELanguage::Spanish, { "DUMMY", "DUMMY" } }, + { ELanguage::Italian, { "DUMMY", "DUMMY" } } +}; + +CONFIG_DEFINE_ENUM_LOCALE(ERadialBlur) +{ + { + ELanguage::English, + { + { ERadialBlur::Off, { "Off", "" } }, + { ERadialBlur::Original, { "Original", "" } }, + { ERadialBlur::Enhanced, { "Enhanced", "" } } + } + }, + { + ELanguage::Japanese, + { + { ERadialBlur::Off, { "オフ", "" } }, + { ERadialBlur::Original, { "オリジナル", "" } }, + { ERadialBlur::Enhanced, { "エンハンスド", "" } } + } + }, + { + ELanguage::German, + { + { ERadialBlur::Off, { "Aus", "" } }, + { ERadialBlur::Original, { "Original", "" } }, + { ERadialBlur::Enhanced, { "Verbessert", "" } } + } + }, + { + ELanguage::French, + { + { ERadialBlur::Off, { "Non", "" } }, + { ERadialBlur::Original, { "Original", "" } }, + { ERadialBlur::Enhanced, { "Amélioré", "" } } + } + }, + { + ELanguage::Spanish, + { + { ERadialBlur::Off, { "No", "" } }, + { ERadialBlur::Original, { "Original", "" } }, + { ERadialBlur::Enhanced, { "Mejorado", "" } } + } + }, + { + ELanguage::Italian, + { + { ERadialBlur::Off, { "No", "" } }, + { ERadialBlur::Original, { "Originale", "" } }, + { ERadialBlur::Enhanced, { "Aumentato", "" } } + } + } +}; + CONFIG_DEFINE_LOCALE(CutsceneAspectRatio) { { ELanguage::English, { "Cutscene Aspect Ratio", "Change the aspect ratio of the real-time cutscenes." } }, - { ELanguage::Japanese, { "アスペクト比のカットシーン", "リアルタイム\u200Bカットシーンの\u200Bアスペクト比を\u200B変更できます" } }, + { ELanguage::Japanese, { "アスペクト比のカットシーン", "リアルタイムカットシーンのアスペクト比を変更できます" } }, { ELanguage::German, { "Zwischensequenz-Seitenverhältnis", "Verändere das Seitenverhältnis der Echtzeit-Zwischensequenzen." } }, { ELanguage::French, { "Format des cinématiques", "Modifie le format d'image des cinématiques en temps réel." } }, { ELanguage::Spanish, { "Relación de aspecto de cinemáticas", "Cambia la relación de aspecto de las cinemáticas de tiempo real." } }, @@ -655,43 +710,43 @@ CONFIG_DEFINE_ENUM_LOCALE(ECutsceneAspectRatio) { ELanguage::English, { - { ECutsceneAspectRatio::Original, { "ORIGINAL", "Original: locks cutscenes to their original 16:9 aspect ratio." } }, - { ECutsceneAspectRatio::Unlocked, { "UNLOCKED", "Unlocked: allows cutscenes to adjust their aspect ratio to the window size.\n\nWARNING: this will introduce visual oddities past the original 16:9 aspect ratio." } } + { ECutsceneAspectRatio::Original, { "Original", "Original: locks cutscenes to their original 16:9 aspect ratio." } }, + { ECutsceneAspectRatio::Unlocked, { "Unlocked", "Unlocked: allows cutscenes to adjust their aspect ratio to the window size. WARNING: this will introduce visual oddities past the original 16:9 aspect ratio." } } } }, { ELanguage::Japanese, { - { ECutsceneAspectRatio::Original, { "オリジナル", "オリジナル: カットシーンを\u200B元の\u200B16:9の\u200Bアスペクト比に\u200B固定します" } }, - { ECutsceneAspectRatio::Unlocked, { "解除", "解除: カットシーンの\u200Bアスペクト比を\u200Bウィンドウサイズに\u200B合わせて\u200B調整します\n\n警告: 元の\u200B16:9の\u200Bアスペクト比を\u200B超えると\u200B視覚的な\u200B異常が\u200B発生します" } }, + { ECutsceneAspectRatio::Original, { "オリジナル", "オリジナル: カットシーンを元の16:9のアスペクト比に固定します" } }, + { ECutsceneAspectRatio::Unlocked, { "解除", "解除: カットシーンのアスペクト比をウィンドウサイズに合わせて調整します 警告: 元の16:9のアスペクト比を超えると視覚的な異常が発生します" } }, } }, { ELanguage::German, { - { ECutsceneAspectRatio::Original, { "ORIGINAL", "Original: Behält die Zwischensequenzen in ihrem originalen 16:9 Seitenverhältnis." } }, - { ECutsceneAspectRatio::Unlocked, { "ENTSPERRT", "Entsperrt: Erlaubt Zwischensequenzen ihr Seitenverhältnis der Fenstergröße anzupassen.\n\nWARNUNG: Diese Option kann zu visuellen Fehlern außerhalb der Grenzen des ursprünglichen Seitenverhältnisses führen." } } + { ECutsceneAspectRatio::Original, { "Original", "Original: Behält die Zwischensequenzen in ihrem originalen 16:9 Seitenverhältnis." } }, + { ECutsceneAspectRatio::Unlocked, { "Entsperrt", "Entsperrt: Erlaubt Zwischensequenzen ihr Seitenverhältnis der Fenstergröße anzupassen.\n\nWARNUNG: Diese Option kann zu visuellen Fehlern außerhalb der Grenzen des ursprünglichen Seitenverhältnisses führen." } } } }, { ELanguage::French, { - { ECutsceneAspectRatio::Original, { "ORIGINAL", "Original : force les cinématiques dans leur format 16:9 d'origine." } }, - { ECutsceneAspectRatio::Unlocked, { "LIBRE", "Libre : permet aux cinématiques d'adapter leur format d'image à la taille de la fenêtre.\n\nATTENTION : au delà du format 16:9 d'origine, des bugs visuels apparaitront." } }, + { ECutsceneAspectRatio::Original, { "Original", "Original : force les cinématiques dans leur format 16:9 d'origine." } }, + { ECutsceneAspectRatio::Unlocked, { "Libre", "Libre : permet aux cinématiques d'adapter leur format d'image à la taille de la fenêtre.\n\nATTENTION : au delà du format 16:9 d'origine, des bugs visuels apparaitront." } }, } }, { ELanguage::Spanish, { - { ECutsceneAspectRatio::Original, { "ORIGINAL", "Original: muestra las cinemáticas en su relación de aspecto original de 16:9." } }, - { ECutsceneAspectRatio::Unlocked, { "DESBLOQUEADO", "Desbloqueado: permite que las cinemáticas ajusten su relación de aspecto al tamaño de la ventana.\n\nADVERTENCIA: esto introducirá rarezas visuales más allá de la relación de aspecto original de 16:9." } }, + { ECutsceneAspectRatio::Original, { "Original", "Original: muestra las cinemáticas en su relación de aspecto original de 16:9." } }, + { ECutsceneAspectRatio::Unlocked, { "Desbloqueado", "Desbloqueado: permite que las cinemáticas ajusten su relación de aspecto al tamaño de la ventana.\n\nADVERTENCIA: esto introducirá rarezas visuales más allá de la relación de aspecto original de 16:9." } }, } }, { ELanguage::Italian, { - { ECutsceneAspectRatio::Original, { "ORIGINALE", "Originale: blocca il rapporto d'aspetto dei filmati a 16:9." } }, - { ECutsceneAspectRatio::Unlocked, { "SBLOCCATO", "Sbloccato: il rapporto d'aspetto verrà regolato in base alle dimensioni della finestra.\n\nATTENZIONE: questa opzione potrebbe causare dei problemi visivi se il rapporto d'aspetto è oltre 16:9." } } + { ECutsceneAspectRatio::Original, { "Originale", "Originale: blocca il rapporto d'aspetto dei filmati a 16:9." } }, + { ECutsceneAspectRatio::Unlocked, { "Sbloccato", "Sbloccato: il rapporto d'aspetto verrà regolato in base alle dimensioni della finestra.\n\nATTENZIONE: questa opzione potrebbe causare dei problemi visivi se il rapporto d'aspetto è oltre 16:9." } } } } }; @@ -699,7 +754,7 @@ CONFIG_DEFINE_ENUM_LOCALE(ECutsceneAspectRatio) CONFIG_DEFINE_LOCALE(UIAlignmentMode) { { ELanguage::English, { "UI Alignment Mode", "Change how the UI aligns with the display." } }, - { ELanguage::Japanese, { "UIアライメントモード", "UIとディスプレイの\u200B配置を\u200B変更できます" } }, + { ELanguage::Japanese, { "UIアライメントモード", "UIとディスプレイの配置を変更できます" } }, { ELanguage::German, { "Benutzeroberflächenausrichtung", "Verändere wie die Benutzeroberfläche sich mit dem Bildschirm ausrichtet." } }, { ELanguage::French, { "Alignement de l'IU", "Modifie l'alignement de l'interface utilisateur sur l'écran." } }, { ELanguage::Spanish, { "Modo de alineamiento de UI", "Cambia la alineación de la interfaz de usuario con la pantalla." } }, @@ -711,43 +766,43 @@ CONFIG_DEFINE_ENUM_LOCALE(EUIAlignmentMode) { ELanguage::English, { - { EUIAlignmentMode::Edge, { "EDGE", "Edge: the UI will align with the edges of the display." } }, - { EUIAlignmentMode::Centre, { "CENTER", "Center: the UI will align with the center of the display." } } + { EUIAlignmentMode::Edge, { "Edge", "Edge: the UI will align with the edges of the display." } }, + { EUIAlignmentMode::Centre, { "Center", "Center: the UI will align with the center of the display." } } } }, { ELanguage::Japanese, { - { EUIAlignmentMode::Edge, { "エッジ", "エッジ: UIがディスプレイの\u200B端に\u200B揃います" } }, - { EUIAlignmentMode::Centre, { "センター", "センター: UIがディスプレイの\u200B中央に\u200B揃います" } }, + { EUIAlignmentMode::Edge, { "エッジ", "エッジ: UIがディスプレイの端に揃います" } }, + { EUIAlignmentMode::Centre, { "センター", "センター: UIがディスプレイの中央に揃います" } }, } }, { ELanguage::German, { - { EUIAlignmentMode::Edge, { "RAND", "Rand: Die Benutzeroberfläche richtet sich zum Rand des Bildschirms aus." } }, - { EUIAlignmentMode::Centre, { "MITTE", "Mitte: Die Benutzeroberfläche richtet sich zur Mitte des Bildschirms aus." } } + { EUIAlignmentMode::Edge, { "Rand", "Rand: Die Benutzeroberfläche richtet sich zum Rand des Bildschirms aus." } }, + { EUIAlignmentMode::Centre, { "Mitte", "Mitte: Die Benutzeroberfläche richtet sich zur Mitte des Bildschirms aus." } } } }, { ELanguage::French, { - { EUIAlignmentMode::Edge, { "BORD", "Bord : l'interface utilisateur sera alignée sur les bords de l'écran." } }, - { EUIAlignmentMode::Centre, { "CENTRÉE", "Centrée : l'interface utilisateur sera alignée sur le centre de l'écran." } }, + { EUIAlignmentMode::Edge, { "Bord", "Bord : l'interface utilisateur sera alignée sur les bords de l'écran." } }, + { EUIAlignmentMode::Centre, { "Centrée", "Centrée : l'interface utilisateur sera alignée sur le centre de l'écran." } }, } }, { ELanguage::Spanish, { - { EUIAlignmentMode::Edge, { "BORDE", "Borde: la interfaz de usuario se alineará con los bordes de la pantalla." } }, - { EUIAlignmentMode::Centre, { "CENTRO", "Centro: la interfaz de usuario se alineará con el centro de la pantalla." } }, + { EUIAlignmentMode::Edge, { "Borde", "Borde: la interfaz de usuario se alineará con los bordes de la pantalla." } }, + { EUIAlignmentMode::Centre, { "Centro", "Centro: la interfaz de usuario se alineará con el centro de la pantalla." } }, } }, { ELanguage::Italian, { - { EUIAlignmentMode::Edge, { "BORDI", "Bordi: l'interfaccia si allineerà con i bordi dello schermo." } }, - { EUIAlignmentMode::Centre, { "CENTRO", "Centro: l'interfaccia si allineerà con il centro dello schermo." } } + { EUIAlignmentMode::Edge, { "Bordi", "Bordi: l'interfaccia si allineerà con i bordi dello schermo." } }, + { EUIAlignmentMode::Centre, { "Centro", "Centro: l'interfaccia si allineerà con il centro dello schermo." } } } } }; diff --git a/MarathonRecomp/locale/locale.cpp b/MarathonRecomp/locale/locale.cpp index 4e554e9b4..ded592c81 100644 --- a/MarathonRecomp/locale/locale.cpp +++ b/MarathonRecomp/locale/locale.cpp @@ -57,45 +57,111 @@ std::unordered_map> { "Options_Category_System", { - { ELanguage::English, "SYSTEM" }, - { ELanguage::Japanese, "SYSTEM" }, - { ELanguage::German, "SYSTEM" }, - { ELanguage::French, "SYSTÈME" }, - { ELanguage::Spanish, "SISTEMA" }, - { ELanguage::Italian, "SISTEMA" } + { ELanguage::English, "System settings" }, + { ELanguage::Japanese, "DUMMY" }, + { ELanguage::German, "DUMMY" }, + { ELanguage::French, "DUMMY" }, + { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Italian, "DUMMY" } + } + }, + { + "Options_Desc_Category_System", + { + { ELanguage::English, "Adjust system settings." }, + { ELanguage::Japanese, "DUMMY" }, + { ELanguage::German, "DUMMY" }, + { ELanguage::French, "DUMMY" }, + { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Italian, "DUMMY" } } }, { "Options_Category_Input", { - { ELanguage::English, "INPUT" }, - { ELanguage::Japanese, "INPUT" }, - { ELanguage::German, "EINGABE" }, - { ELanguage::French, "COMMANDES" }, - { ELanguage::Spanish, "CONTROLES" }, - { ELanguage::Italian, "COMANDI" } + { ELanguage::English, "Input settings" }, + { ELanguage::Japanese, "DUMMY" }, + { ELanguage::German, "DUMMY" }, + { ELanguage::French, "DUMMY" }, + { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Italian, "DUMMY" } + } + }, + { + "Options_Desc_Category_Input", + { + { ELanguage::English, "Adjust input settings." }, + { ELanguage::Japanese, "DUMMY" }, + { ELanguage::German, "DUMMY" }, + { ELanguage::French, "DUMMY" }, + { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Italian, "DUMMY" } } }, { "Options_Category_Audio", { - { ELanguage::English, "AUDIO" }, - { ELanguage::Japanese, "AUDIO" }, - { ELanguage::German, "AUDIO" }, - { ELanguage::French, "SON" }, - { ELanguage::Spanish, "AUDIO" }, - { ELanguage::Italian, "AUDIO" } + { ELanguage::English, "Audio settings" }, + { ELanguage::Japanese, "DUMMY" }, + { ELanguage::German, "DUMMY" }, + { ELanguage::French, "DUMMY" }, + { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Italian, "DUMMY" } + } + }, + { + "Options_Desc_Category_Audio", + { + { ELanguage::English, "Adjust audio settings." }, + { ELanguage::Japanese, "DUMMY" }, + { ELanguage::German, "DUMMY" }, + { ELanguage::French, "DUMMY" }, + { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Italian, "DUMMY" } } }, { "Options_Category_Video", { - { ELanguage::English, "VIDEO" }, - { ELanguage::Japanese, "VIDEO" }, - { ELanguage::German, "VIDEO" }, - { ELanguage::French, "AFFICHAGE" }, - { ELanguage::Spanish, "VÍDEO" }, - { ELanguage::Italian, "VIDEO" } + { ELanguage::English, "Video settings" }, + { ELanguage::Japanese, "DUMMY" }, + { ELanguage::German, "DUMMY" }, + { ELanguage::French, "DUMMY" }, + { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Italian, "DUMMY" } + } + }, + { + "Options_Desc_Category_Video", + { + { ELanguage::English, "Adjust video settings." }, + { ELanguage::Japanese, "DUMMY" }, + { ELanguage::German, "DUMMY" }, + { ELanguage::French, "DUMMY" }, + { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Italian, "DUMMY" } + } + }, + { + "Options_Category_Code", + { + { ELanguage::English, "Code settings" }, + { ELanguage::Japanese, "DUMMY" }, + { ELanguage::German, "DUMMY" }, + { ELanguage::French, "DUMMY" }, + { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Italian, "DUMMY" } + } + }, + { + "Options_Desc_Category_Code", + { + { ELanguage::English, "Adjust code settings." }, + { ELanguage::Japanese, "DUMMY" }, + { ELanguage::German, "DUMMY" }, + { ELanguage::French, "DUMMY" }, + { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Italian, "DUMMY" } } }, { @@ -125,7 +191,7 @@ std::unordered_map> "Options_Desc_WindowSize", { { ELanguage::English, "Adjust the size of the game window in windowed mode." }, - { ELanguage::Japanese, "ゲームの\u200Bウィンドウサイズを\u200B[設定:せってい]できます" }, + { ELanguage::Japanese, "ゲームのウィンドウサイズを設定できます" }, { ELanguage::German, "Passe die Fenstergröße des Spiels im Fenstermodus an." }, { ELanguage::French, "Définir la résolution de jeu en mode fenêtré." }, { ELanguage::Spanish, "Ajusta el tamaño de la ventana de juego en modo ventana." }, @@ -133,15 +199,15 @@ std::unordered_map> } }, { - // Notes: description for options that cannot be accessed anywhere but the title screen or world map (e.g. Language). + // Notes: description for options that cannot be accessed during gameplay (e.g. Language). "Options_Desc_NotAvailable", { - { ELanguage::English, "This option is not available at this location." }, - { ELanguage::Japanese, "この\u200Bオプションは\u200B[現在:げんざい]の\u200B[画面:がめん]では\u200B[変更:へんこう]\u200Bできません" }, - { ELanguage::German, "Diese Option ist an dieser Stelle nicht verfügbar." }, - { ELanguage::French, "Cette option n'est pas disponible pour l'instant." }, - { ELanguage::Spanish, "Esta opción no está disponible en este momento." }, - { ELanguage::Italian, "Questa opzione non può essere modificata in questo momento." } + { ELanguage::English, "This option is not available during gameplay." }, + { ELanguage::Japanese, "DUMMY" }, + { ELanguage::German, "DUMMY" }, + { ELanguage::French, "DUMMY" }, + { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Italian, "DUMMY" } } }, { @@ -149,7 +215,7 @@ std::unordered_map> "Options_Desc_NotAvailableFullscreen", { { ELanguage::English, "This option is not available in fullscreen mode." }, - { ELanguage::Japanese, "この\u200Bオプションは\u200Bフルスクリーンモードでは\u200B[変更:へんこう]\u200Bできません" }, + { ELanguage::Japanese, "このオプションはフルスクリーンモードでは変更できません" }, { ELanguage::German, "Diese Option ist im Vollbildmodus nicht verfügbar." }, { ELanguage::French, "Cette option n'est pas disponible en mode plein écran." }, { ELanguage::Spanish, "Esta opción no está disponible en modo pantalla completa." }, @@ -161,7 +227,7 @@ std::unordered_map> "Options_Desc_NotAvailableWindowed", { { ELanguage::English, "This option is not available in windowed mode." }, - { ELanguage::Japanese, "この\u200Bオプションは\u200Bウィンドウモードでは\u200B[変更:へんこう]\u200Bできません" }, + { ELanguage::Japanese, "このオプションはウィンドウモードでは変更できません" }, { ELanguage::German, "Diese Option ist im Fenstermodus nicht verfügbar." }, { ELanguage::French, "Cette option n'est pas disponible en mode fenêtré." }, { ELanguage::Spanish, "Esta opción no está disponible en modo ventana." }, @@ -173,7 +239,7 @@ std::unordered_map> "Options_Desc_NotAvailableHardware", { { ELanguage::English, "This option is not available with your current hardware configuration." }, - { ELanguage::Japanese, "この\u200Bオプションは\u200B[現在:げんざい]の\u200Bハードウェア[構成:こうせい]で\u200B[変更:へんこう]\u200Bできません" }, + { ELanguage::Japanese, "このオプションは現在のハードウェア構成で変更できません" }, { ELanguage::German, "Diese Option ist mit der momentanen Hardwarekonfiguration nicht verfügbar." }, { ELanguage::French, "Cette option n'est pas disponible avec votre configuration matérielle actuelle." }, { ELanguage::Spanish, "Esta opción no está disponible con tu configuración actual de hardware." }, @@ -185,7 +251,7 @@ std::unordered_map> "Options_Desc_NotAvailableMSAA", { { ELanguage::English, "This option is not available without MSAA." }, - { ELanguage::Japanese, "この\u200Bオプションは\u200BMSAAなしで\u200B[変更:へんこう]\u200Bできません" }, + { ELanguage::Japanese, "このオプションはMSAAなしで変更できません" }, { ELanguage::German, "Diese Option ist ohne MSAA nicht verfügbar." }, { ELanguage::French, "Cette option n'est pas disponible sans MSAA." }, { ELanguage::Spanish, "Esta opción no está disponible sin MSAA." }, @@ -197,7 +263,7 @@ std::unordered_map> "Options_Desc_OSNotSupported", { { ELanguage::English, "This option is not supported by your operating system." }, - { ELanguage::Japanese, "この\u200Bオプションは\u200B[現在:げんざい]の\u200BOSで\u200B[変更:へんこう]\u200Bできません" }, + { ELanguage::Japanese, "このオプションは現在のOSで変更できません" }, { ELanguage::German, "Diese Option wird von diesem Betriebssystem nicht unterstützt." }, { ELanguage::French, "Cette option n'est pas prise en charge par votre système d'exploitation." }, { ELanguage::Spanish, "Esta opción no está soportada por tu sistema operativo." }, @@ -209,7 +275,7 @@ std::unordered_map> "Options_Message_Restart", { { ELanguage::English, "The game will now restart." }, - { ELanguage::Japanese, "ゲームが[再起動:さいきどう]されます" }, + { ELanguage::Japanese, "ゲームが再起動されます" }, { ELanguage::German, "Das Spiel wird jetzt neu starten." }, { ELanguage::French, "Le jeu va maintenant redémarrer." }, { ELanguage::Spanish, "El juego se va a reiniciar." }, @@ -277,7 +343,7 @@ std::unordered_map> "Installer_Page_SelectLanguage", { { ELanguage::English, "Please select a language." }, - { ELanguage::Japanese, "[言語:げんご]を[選択:せんたく]してください" }, + { ELanguage::Japanese, "言語を選択してください" }, { ELanguage::German, "Bitte eine Sprache auswählen." }, { ELanguage::French, "Choisissez une langue." }, { ELanguage::Spanish, "Selecciona un idioma." }, @@ -288,7 +354,7 @@ std::unordered_map> "Installer_Page_Introduction", { { ELanguage::English, "Welcome to Marathon Recompiled!\n\nYou'll need an Xbox 360 copy\nof Sonic the Hedgehog (2006) in order to proceed with the installation." }, - { ELanguage::Japanese, "Marathon Recompiledへようこそ!\nインストールにはXbox 360[版:ばん]の\n「ソニック・ザ・ヘッジホッグ (2006)」\nが必要です" }, + { ELanguage::Japanese, "Marathon Recompiledへようこそ!\nインストールにはXbox 360版の\n「ソニック・ザ・ヘッジホッグ (2006)」\nが必要です" }, { ELanguage::German, "Willkommen zu Marathon Recompiled!\n\nEs wird eine Xbox 360 Kopie von\nSonic the Hedgehog (2006) benötigt um mit der Installation fortfahren zu können." }, { ELanguage::French, "Bienvenue sur Marathon Recompiled !\n\nVous aurez besoin d'une copie de\nSonic the Hedgehog (2006) pour\nXbox 360 pour procéder à l'installation." }, { ELanguage::Spanish, "¡Bienvenido a Marathon Recompiled!\n\nNecesitas una copia de\nSonic the Hedgehog (2006) de\nXbox 360 para continuar con la instalación." }, @@ -299,7 +365,7 @@ std::unordered_map> "Installer_Page_SelectGame", { { ELanguage::English, "Add the sources for the game." }, - { ELanguage::Japanese, "ゲームのソースを[追加:ついか]" }, + { ELanguage::Japanese, "ゲームのソースを追加" }, { ELanguage::German, "Füge die Quellen für das Spiel." }, { ELanguage::French, "Ajouter les fichiers du jeu." }, { ELanguage::Spanish, "Añade las fuentes para el juego." }, @@ -310,7 +376,7 @@ std::unordered_map> "Installer_Page_SelectDLC", { { ELanguage::English, "Add the sources for the DLC." }, - { ELanguage::Japanese, "DLCのソースを[追加:ついか]" }, + { ELanguage::Japanese, "DLCのソースを追加" }, { ELanguage::German, "Füge die Quellen für die Erweiterungen des Spiels hinzu." }, { ELanguage::French, "Ajouter les fichiers pour les DLCs." }, { ELanguage::Spanish, "Añade las fuentes para el DLC." }, @@ -332,7 +398,7 @@ std::unordered_map> "Installer_Page_Installing", { { ELanguage::English, "Please wait while the content is being installed..." }, - { ELanguage::Japanese, "コンテンツのインストール[中:ちゅう]はお[待:ま]ち\nください" }, + { ELanguage::Japanese, "コンテンツのインストール中はお待ち\nください" }, { ELanguage::German, "Bitte warten. Der Inhalt wird installiert..." }, { ELanguage::French, "Veuillez patienter pendant l'installation du contenu..." }, { ELanguage::Spanish, "Por favor, espera mientras el contenido se instala... " }, @@ -343,7 +409,7 @@ std::unordered_map> "Installer_Page_InstallSucceeded", { { ELanguage::English, "Installation complete!\nThis project is brought to you by:" }, - { ELanguage::Japanese, "インストール[完了:かんりょう]!\nプロジェクト[制作:せいさく]:" }, + { ELanguage::Japanese, "インストール完了!\nプロジェクト制作:" }, { ELanguage::German, "Installation abgeschlossen!\nDieses Projekt wird präsentiert von:" }, { ELanguage::French, "Installation terminée !\nCe projet vous est présenté\npar :" }, { ELanguage::Spanish, "¡Instalación completada!\nEste proyecto ha sido posible gracias a:" }, @@ -354,7 +420,7 @@ std::unordered_map> "Installer_Page_InstallFailed", { { ELanguage::English, "Installation failed.\n\nError: " }, - { ELanguage::Japanese, "インストールに[失敗:しっぱい]しました\n\nエラー: " }, + { ELanguage::Japanese, "インストールに失敗しました\n\nエラー: " }, { ELanguage::German, "Installation fehlgeschlagen.\n\nFehler: " }, { ELanguage::French, "L'installation a échoué.\n\nErreur : " }, { ELanguage::Spanish, "La instalación falló.\n\nError: " }, @@ -376,7 +442,7 @@ std::unordered_map> "Installer_Step_RequiredSpace", { { ELanguage::English, "Required space: %2.2f GiB" }, - { ELanguage::Japanese, "[必要:ひつよう]な[容量:ようりょう]: %2.2f GiB" }, + { ELanguage::Japanese, "必要な容量: %2.2f GiB" }, { ELanguage::German, "Benötigter Speicherplatz:\n%2.2f GiB" }, { ELanguage::French, "Espace nécessaire :\n%2.2f Gio" }, { ELanguage::Spanish, "Espacio necesario: %2.2f GiB" }, @@ -387,7 +453,7 @@ std::unordered_map> "Installer_Step_AvailableSpace", { { ELanguage::English, "Available space: %2.2f GiB" }, - { ELanguage::Japanese, "[使用:しよう][可能:かのう]な[容量:ようりょう]: %2.2f GiB" }, + { ELanguage::Japanese, "使用可能な容量: %2.2f GiB" }, { ELanguage::German, "Verfügbarer Speicherplatz:\n%2.2f GiB" }, { ELanguage::French, "Espace disponible :\n%2.2f Gio" }, { ELanguage::Spanish, "Espacio disponible: %2.2f GiB" }, @@ -454,7 +520,7 @@ std::unordered_map> "Installer_Message_InvalidFilesList", { { ELanguage::English, "The following selected files are invalid:" }, - { ELanguage::Japanese, "[選択:せんたく]した[次:つぎ]のファイルは[無効:むこう]です:" }, + { ELanguage::Japanese, "選択した次のファイルは無効です:" }, { ELanguage::German, "Die folgenden Dateien sind ungültig:" }, { ELanguage::French, "Les fichiers suivants ne sont pas valides :" }, { ELanguage::Spanish, "Los siguientes archivos no son válidos:" }, @@ -466,7 +532,7 @@ std::unordered_map> "Installer_Message_InvalidFiles", { { ELanguage::English, "Some of the files that have\nbeen provided are not valid.\n\nPlease make sure all the\nspecified files are correct\nand try again." }, - { ELanguage::Japanese, "[提供:ていきょう]されたファイルの[一部:いちぶ]が[有効:ゆうこう]ではありません\n[指定:してい]されたファイルがすべて[正:ただ]しいことを[確認:かくにん]して\nもう[一度:いちど]お[試:ため]しください" }, + { ELanguage::Japanese, "提供されたファイルの一部が有効ではありません\n指定されたファイルがすべて正しいことを確認して\nもう一度お試しください" }, { ELanguage::German, "Einige Dateien, die bereitgestellt\nwurden sind ungültig.\n\nBitte stelle sicher, dass\ndie angegebenen Dateien korrekt\nsind und versuche es erneut." }, { ELanguage::French, "Certains fichiers fournis ne\nsont pas valides.\n\nVeuillez vous assurer que tous\nles fichiers spécifiés sont\ncorrects et réessayez." }, { ELanguage::Spanish, "Algunos de los archivos\nseleccionados no son válidos.\n\nPor favor, asegúrate de que\ntodos los archivos son correctos\ne inténtalo de nuevo.\n" }, @@ -478,7 +544,7 @@ std::unordered_map> "Installer_Message_FilePickerTutorial", { { ELanguage::English, "Select a digital dump with\ncontent from the game.\n\nThese files can be obtained from\nyour Xbox 360 hard drive by\nfollowing the instructions on\nthe GitHub page.\n\nFor choosing a folder with extracted\nand unmodified game files, use\nthe \"Add Folder\" option instead." }, - { ELanguage::Japanese, "ゲームのコンテンツを[含:ふく]む デジタルダンプを[選択:せんたく]してください\n\nこれらのファイルは GitHubページの[指示:しじ]に[従:したが]って\nXbox 360ハードドライブから[取得:しゅとく]できます\n\n[抽出:ちゅうしゅつ]された[変更:へんこう]されていない\nゲームファイルを[含:ふく]むフォルダーを[選択:せんたく]するには\n[代:か]わりに「フォルダの[追加:ついか]」オプションを[使用:しよう]してください" }, + { ELanguage::Japanese, "ゲームのコンテンツを含む デジタルダンプを選択してください\n\nこれらのファイルは GitHubページの指示に従って\nXbox 360ハードドライブから取得できます\n\n抽出された変更されていない\nゲームファイルを含むフォルダーを選択するには\n代わりに「フォルダの追加」オプションを使用してください" }, { ELanguage::German, "Wähle einen digitalen Dump von dem Spiel.\n\nDie Dateien können über die Festplatte deiner\nXbox 360 erlangt werden.\nFolge hierfür den Anweisungen auf der GitHub Seite.\n\nUm einen Ordner mit unmodifizierten Spieldateien auszuwählen, benutze die \"Ordner Hinzufügen\" Option stattdessen." }, { ELanguage::French, "Sélectionnez une copie\ndématérialisée avec le contenu du\njeu de base.\n\nCes fichiers peuvent être obtenus\nà partir du disque dur de votre\nXbox 360 en suivant les\ninstructions de la page GitHub.\n\nPour choisir un dossier contenant\nles fichiers de jeu extraits et\nnon modifiés, utilisez plutôt\nl'option \"Ajouter un dossier\"." }, { ELanguage::Spanish, "Selecciona una copia digital\ncon contenido del juego.\n\nPuedes obtener los archivos\nde tu disco duro de Xbox 360\nsiguiendo las instrucciones de\nla página de GitHub.\n\nPara elegir una carpeta con\narchivos extraídos sin modificar,\nutiliza la opción \"Añadir Carpeta\"." }, @@ -490,7 +556,7 @@ std::unordered_map> "Installer_Message_FolderPickerTutorial", { { ELanguage::English, "Select a folder that contains the\nunmodified files that have been\nextracted from the game.\n\nThese files can be obtained from\nyour Xbox 360 hard drive by\nfollowing the instructions on\nthe GitHub page.\n\nFor choosing a digital dump,\nuse the \"Add Files\" option instead." }, - { ELanguage::Japanese, "ゲームから[抽出:ちゅうしゅつ]された[変更:へんこう]されていない\nファイルを含むフォルダを[選択:せんたく]してください\n\nこれらのファイルは GitHubページの[指示:しじ]に[従:したが]って\nXbox 360ハードドライブから[取得:しゅとく]できます\n\nデジタルダンプを[選択:せんたく]するには\n[代:か]わりに「ファイルの[追加:ついか]」オプションを[使用:しよう]してください" }, + { ELanguage::Japanese, "ゲームから抽出された変更されていない\nファイルを含むフォルダを選択してください\n\nこれらのファイルは GitHubページの指示に従って\nXbox 360ハードドライブから取得できます\n\nデジタルダンプを選択するには\n代わりに「ファイルの追加」オプションを使用してください" }, { ELanguage::German, "Wähle einen Ordner, der unmodifizierte Dateien, die vom Spiel extrahiert wurden enthält.\n\nDie Dateien können über die Festplatte deiner\nXbox 360 erlangt werden.\nFolge hierfür den Anweisungen auf der GitHub Seite.\n\nUm einen digitalen Dump auszuwählen, benutze die \"Datei Hinzufügen\" Option stattdessen." }, { ELanguage::French, "Sélectionnez un dossier contenant\nles fichiers extraits du jeu de\nbase.\n\nCes fichiers peuvent être obtenus\nà partir du disque dur de votre\nXbox 360 en suivant les\ninstructions de la page GitHub.\n\nPour choisir une copie\ndématérialisée, utilisez plutôt\nl'option \"Ajouter des fichiers\"." }, { ELanguage::Spanish, "Selecciona una carpeta que\ncontenga los archivos sin\nmodificar extraídos del juego.\n\nPuedes obtener los archivos\nde tu disco duro de Xbox 360\nsiguiendo las instrucciones de\nla página de GitHub.\n\nPara elegir una copia digital,\nutiliza la opción \"Añadir Archivos\"." }, @@ -501,12 +567,12 @@ std::unordered_map> // Notes: message appears when choosing the Install option at the title screen when the user is missing DLC content. "Installer_Message_TitleMissingDLC", { - { ELanguage::English, "This will restart the game to\nallow you to install any DLC\nthat you are missing.\n\nInstalling DLC will improve the\nlighting quality across the game.\n\nWould you like to install missing\ncontent?" }, - { ELanguage::Japanese, "これによりゲームが[再起動:さいきどう]し[不足:ふそく]しているDLCを\nインストールできるようになります\n\nDLCをインストールすると\nゲーム[全体:ぜんたい]の [照明品質:しょうめいひんしつ] が[向上:こうじょう]します\n\n[不足:ふそく]しているコンテンツを\nインストールしますか?" }, - { ELanguage::German, "Das Spiel wird neu gestartet\num die Installation einer fehlenden\nErweiterung zu ermöglichen.\n\nDie Installation einer\nErweiterung erhöht die Qualität\nder Beleuchtung im gesamten Spiel.\n\nMöchtest du den fehlenden\nInhalt installieren?" }, - { ELanguage::French, "Cela redémarrera le jeu pour vous\npermettre d'installer les DLC\nmanquants.\n\nL'installation du DLC améliorera\nla qualité de l'éclairage dans le\njeu.\n\nSouhaitez-vous installer le\ncontenu manquant ?" }, - { ELanguage::Spanish, "Esta opción reiniciará el juego\npara permitirte instalar los DLC\nque falten.\n\nInstalar el DLC mejorará la calidad\nde iluminación en todo el juego.\n\n¿Quieres instalar el contenido\nque falta?" }, - { ELanguage::Italian, "Questa opzione riavviera il gioco\nper farti installare qualsiasi DLC\nche non hai installato.\n\nL'installazione dei DLC migliorerà la qualità\ndell'illuminazione del gioco.\n\nVuoi installare i DLC mancanti?" } + { ELanguage::English, "This will restart the game to\nallow you to install any DLC\nthat you are missing.\n\nWould you like to install missing\ncontent?" }, + { ELanguage::Japanese, "これによりゲームが再起動し不足しているDLCを\nインストールできるようになります\n\n不足しているコンテンツを\nインストールしますか?" }, + { ELanguage::German, "Das Spiel wird neu gestartet\num die Installation einer fehlenden\nErweiterung zu ermöglichen.\n\nMöchtest du den fehlenden\nInhalt installieren?" }, + { ELanguage::French, "Cela redémarrera le jeu pour vous\npermettre d'installer les DLC\nmanquants.\n\nSouhaitez-vous installer le\ncontenu manquant ?" }, + { ELanguage::Spanish, "Esta opción reiniciará el juego\npara permitirte instalar los DLC\nque falten.\n\n¿Quieres instalar el contenido\nque falta?" }, + { ELanguage::Italian, "Questa opzione riavviera il gioco\nper farti installare qualsiasi DLC\nche non hai installato.\n\nVuoi installare i DLC mancanti?" } } }, { @@ -514,7 +580,7 @@ std::unordered_map> "Installer_Message_Title", { { ELanguage::English, "This restarts the game to\nallow you to install any DLC\nthat you may be missing.\n\nYou are not currently\nmissing any DLC.\n\nWould you like to proceed\nanyway?" }, - { ELanguage::Japanese, "これによりゲームが[再起動:さいきどう]され\n[不足:ふそく]しているDLCを\nインストールできるようになります\n\n[現在:げんざい] [不足:ふそく]しているDLCはありません\n\nそれでも[続行:ぞっこう]しますか?" }, + { ELanguage::Japanese, "これによりゲームが再起動され\n不足しているDLCを\nインストールできるようになります\n\n現在 不足しているDLCはありません\n\nそれでも続行しますか?" }, { ELanguage::German, "Das Spiel wird neu gestartet\num die Installation einer fehlenden\nErweiterung zu ermöglichen.\n\nEs kann keine weitere Erweiterung\ninstalliert werden.\n\nMöchtest du trotzdem fortfahren?" }, { ELanguage::French, "Cela redémarrera le jeu pour vous\npermettre d'installer les DLC\nmanquants.\n\nIl ne vous manque aucun DLC.\n\nVoulez-vous quand même continuer ?" }, { ELanguage::Spanish, "Esto reiniciará el juego\npara permitirte instalar\nlos DLC que falten.\n\nActualmente, no falta ningún\nDLC por instalarse.\n\n¿Quieres continuar de todos\nmodos?" }, @@ -526,11 +592,11 @@ std::unordered_map> "Installer_Message_Quit", { { ELanguage::English, "Exit the installer.\nOK?" }, - { ELanguage::Japanese, "" }, - { ELanguage::German, "" }, - { ELanguage::French, "" }, - { ELanguage::Spanish, "" }, - { ELanguage::Italian, "" } + { ELanguage::Japanese, "DUMMY" }, + { ELanguage::German, "DUMMY" }, + { ELanguage::French, "DUMMY" }, + { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Italian, "DUMMY" } } }, { @@ -538,11 +604,11 @@ std::unordered_map> "Installer_Message_Cancel", { { ELanguage::English, "Cancel the installation.\nOK?" }, - { ELanguage::Japanese, "" }, - { ELanguage::German, "" }, - { ELanguage::French, "" }, - { ELanguage::Spanish, "" }, - { ELanguage::Italian, "" } + { ELanguage::Japanese, "DUMMY" }, + { ELanguage::German, "DUMMY" }, + { ELanguage::French, "DUMMY" }, + { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Italian, "DUMMY" } } }, { @@ -550,25 +616,11 @@ std::unordered_map> "Title_Message_Quit", { { ELanguage::English, "Exit the game.\nOK?" }, - { ELanguage::Japanese, "" }, - { ELanguage::German, "" }, - { ELanguage::French, "" }, - { ELanguage::Spanish, "" }, - { ELanguage::Italian, "" } - } - }, - { - // Notes: message appears when SYS-DATA is corrupted (mismatching file size) upon pressing start at the title screen. - // To make this occur, open the file in any editor and just remove a large chunk of data. - // Do not localise this unless absolutely necessary, these strings are from the XEX. - "Title_Message_SaveDataCorrupt", - { - { ELanguage::English, "The save file appears to be\ncorrupted and cannot be loaded." }, - { ELanguage::Japanese, "ゲームデータの読み込みに失敗しました。\nこのまま続けるとゲームデータをセーブすることはできません" }, - { ELanguage::German, "Diese Speicherdatei ist beschädigt\nund kann nicht geladen werden." }, - { ELanguage::French, "Le fichier de sauvegarde semble être\nendommagé et ne peut être chargé." }, - { ELanguage::Spanish, "El archivo parece estar dañado\ny no se puede cargar." }, - { ELanguage::Italian, "I file di salvataggio sembrano danneggiati\ne non possono essere caricati." } + { ELanguage::Japanese, "DUMMY" }, + { ELanguage::German, "DUMMY" }, + { ELanguage::French, "DUMMY" }, + { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Italian, "DUMMY" } } }, { @@ -577,7 +629,7 @@ std::unordered_map> "Title_Message_AchievementDataCorrupt", { { ELanguage::English, "The achievement data appears to be\ncorrupted and cannot be loaded.\n\nProceeding from this point will\nclear your achievement data." }, - { ELanguage::Japanese, "[実績:じっせき]データが[破損:はそん]しているため\n[読:よ]み[込:こ]むことができません\n\nこの[先:さき]に[進:すす]むと[実績:じっせき]データが\n[消去:しょうきょ]されます" }, + { ELanguage::Japanese, "実績データが破損しているため\n読み込むことができません\n\nこの先に進むと実績データが\n消去されます" }, { ELanguage::German, "Die Erfolgsdaten sind möglicherweise\nfehlerhaft und können nicht\ngeladen werden.\n\nDurch das Fortfahren werden\ndeine bisherigen Erfolgsdaten gelöscht." }, { ELanguage::French, "Les données des succès semblent être\nendommagées et ne peuvent être\nchargées.\n\nSi vous continuez, vos données\nseront écrasées." }, { ELanguage::Spanish, "Los datos de logros parecen estar\ncorruptos y no pueden cargarse.\n\nContinuar a partir de este punto\neliminará los datos de logros." }, @@ -590,7 +642,7 @@ std::unordered_map> "Title_Message_AchievementDataIOError", { { ELanguage::English, "The achievement data could not be loaded.\nYour achievements will not be saved." }, - { ELanguage::Japanese, "[実績:じっせき]データを[読:よ]み[込:こ]めませんでした\n[実績:じっせき]は[保存:ほぞん]されません。" }, + { ELanguage::Japanese, "実績データを読み込めませんでした\n実績は保存されません" }, { ELanguage::German, "Die Erfolgsdaten konnten nicht geladen werden.\nDeine Erfolge werden nicht gespeichert." }, { ELanguage::French, "Les données des succès ne\npeuvent être chargées.\nVos succès ne seront pas\nsauvegardés." }, { ELanguage::Spanish, "Los datos de logros no pueden cargarse.\nTus logros no serán guardados." }, @@ -601,7 +653,7 @@ std::unordered_map> "Title_Message_UpdateAvailable", { { ELanguage::English, "An update is available!\n\nWould you like to visit the\nreleases page to download it?" }, - { ELanguage::Japanese, "アップデートが[利用:りよう][可能:かのう]です\n\nリリースページにアクセスして\nダウンロードしますか?" }, + { ELanguage::Japanese, "アップデートが利用可能です\n\nリリースページにアクセスして\nダウンロードしますか?" }, { ELanguage::German, "Ein Update ist verfügbar!\n\nMöchtest du die Release-Seite\nbesuchen um es herunterzuladen?" }, { ELanguage::French, "Une mise à jour est disponible !\n\nVoulez-vous visiter la page\ndes mises à jour pour la\ntélécharger ?" }, { ELanguage::Spanish, "¡Hay una actualización disponible!\n\n¿Quieres ir a la página\npara descargarla?" }, @@ -634,7 +686,7 @@ std::unordered_map> "System_MemoryAllocationFailed", { { ELanguage::English, "Failed to allocate game memory.\n\nPlease make sure that:\n\n- You meet the minimum system requirements (8 GB).\n- Your page file is configured with at least 4-8 GB of virtual memory." }, - { ELanguage::Japanese, "ゲームメモリの割り当てに失敗しました\n\n次の点を確認してください:\n\n※最小システム要件(8 GB)を満たしていること。\n※ページファイルに少なくとも4~8 GBの仮想メモリが設定されていること" }, + { ELanguage::Japanese, "ゲームメモリの割り当てに失敗しました\n\n次の点を確認してください:\n\n※最小システム要件(8 GB)を満たしていること\n※ページファイルに少なくとも4~8 GBの仮想メモリが設定されていること" }, { ELanguage::German, "Fehler beim Zuweisen des Spielspeichers.\n\nBitte stelle sicher, dass:\n\n- Die Mindestanforderungen für das System erfüllt sind (8 GB).\n- Die Auslagerungsdatei mit mindestens 4-8 GB virtuellem Speicher konfiguriert ist." }, { ELanguage::French, "Échec d'allocation de la mémoire du jeu.\n\nVeuillez vous assurer que :\n\n- Vous disposez de la configuration minimale requise (8 Go).\n- Votre fichier d’échange est configuré avec au moins 4 à 8 Go de mémoire virtuelle." }, { ELanguage::Spanish, "Fallo al asignar memoria del juego.\n\nPor favor, asegúrate de que:\n\n- Cumples los requisitos mínimos del sistema (8 GB).\n- Tu archivo de páginación está configurado con al menos 4 u 8 GB de memoria virtual." }, @@ -677,23 +729,23 @@ std::unordered_map> { "Common_On", { - { ELanguage::English, "ON" }, + { ELanguage::English, "On" }, { ELanguage::Japanese, "オン" }, - { ELanguage::German, "EIN" }, - { ELanguage::French, "OUI" }, - { ELanguage::Spanish, "SÍ" }, - { ELanguage::Italian, "SÌ" } + { ELanguage::German, "Ein" }, + { ELanguage::French, "Oui" }, + { ELanguage::Spanish, "Sí" }, + { ELanguage::Italian, "Sì" } } }, { "Common_Off", { - { ELanguage::English, "OFF" }, + { ELanguage::English, "Off" }, { ELanguage::Japanese, "オフ" }, - { ELanguage::German, "AUS" }, - { ELanguage::French, "NON" }, - { ELanguage::Spanish, "NO" }, - { ELanguage::Italian, "NO" } + { ELanguage::German, "Aus" }, + { ELanguage::French, "Non" }, + { ELanguage::Spanish, "No" }, + { ELanguage::Italian, "No" } } }, { @@ -703,7 +755,7 @@ std::unordered_map> { ELanguage::Japanese, "はい" }, { ELanguage::German, "Ja" }, { ELanguage::French, "Oui" }, - { ELanguage::Spanish, "Sí" }, + { ELanguage::Spanish, "Sí" }, { ELanguage::Italian, "Sì" } } }, @@ -725,7 +777,7 @@ std::unordered_map> { ELanguage::Japanese, "次へ" }, { ELanguage::German, "Weiter" }, { ELanguage::French, "Suivant" }, - { ELanguage::Spanish, "Siguiente" }, + { ELanguage::Spanish, "Siguiente" }, { ELanguage::Italian, "Avanti" } } }, diff --git a/MarathonRecomp/patches/MainMenuTask_patches.cpp b/MarathonRecomp/patches/MainMenuTask_patches.cpp index 2f50f9d8b..f301c0c88 100644 --- a/MarathonRecomp/patches/MainMenuTask_patches.cpp +++ b/MarathonRecomp/patches/MainMenuTask_patches.cpp @@ -13,24 +13,19 @@ PPC_FUNC(sub_824FFCF8) if (!OptionsMenu::s_isVisible && (pMainMenuTask->m_PressedButtons.get() & 0x10) != 0) { OptionsMenu::Open(); - + Game_PlaySound("main_deside"); - - pMainMenuTask->m_State = Sonicteam::MainMenuTask::MainMenuState_MainMenu; - pMainMenuTask->m_PressedButtons = 0; - } - else if (OptionsMenu::s_isVisible && (pMainMenuTask->m_PressedButtons.get() & 0x20) != 0) - { - OptionsMenu::Close(); - - Game_PlaySound("window_close"); - + pMainMenuTask->m_State = Sonicteam::MainMenuTask::MainMenuState_MainMenu; pMainMenuTask->m_PressedButtons = 0; } } - if (!OptionsMenu::s_isVisible) + if (OptionsMenu::s_isVisible) + { + pMainMenuTask->m_PressedButtons = 0; + } + else { MainMenuTaskPatches::State = (Sonicteam::MainMenuTask::MainMenuState)pMainMenuTask->m_State.get(); @@ -39,8 +34,4 @@ PPC_FUNC(sub_824FFCF8) __imp__sub_824FFCF8(ctx, base); } - else - { - pMainMenuTask->m_PressedButtons = 0; - } } diff --git a/MarathonRecomp/patches/TitleTask_patches.cpp b/MarathonRecomp/patches/TitleTask_patches.cpp index 0fe1c5df2..b7a829e1c 100644 --- a/MarathonRecomp/patches/TitleTask_patches.cpp +++ b/MarathonRecomp/patches/TitleTask_patches.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -16,6 +17,19 @@ static double g_titleExitOutroTime{}; static bool g_quitMessageOpen{}; static bool g_saveDataExists{}; +static bool g_isSecretDone{}; +static uint32_t g_secretFlags{}; + +enum +{ + SECRET_NONE, + SECRET_UP = 1 << 0, + SECRET_DOWN = 1 << 1, + SECRET_LEFT = 1 << 2, + SECRET_RIGHT = 1 << 3, + SECRET_ALL = SECRET_UP | SECRET_DOWN | SECRET_LEFT | SECRET_RIGHT +}; + bool ProcessQuitMessage(Sonicteam::TitleTask* pTitleTask) { static int s_quitMessageResult = -1; @@ -91,13 +105,50 @@ PPC_FUNC(sub_825126A0) break; } - case Sonicteam::TitleTask::TitleState_Proceed: + case Sonicteam::TitleTask::TitleState_Wait: { - g_saveDataExists = std::filesystem::exists(GetSaveFilePath(false)); + if (g_isSecretDone) + break; - // Redirect PRESS START proceed to options open. - if (g_saveDataExists) - GuestToHostFunction(sub_82511CA0, pTitleTask, (int)Sonicteam::TitleTask::TitleState_OptionsOpen); + if (auto& spInputManager = App::s_pApp->m_pDoc->m_vspInputManager[0]) + { + auto& rPadState = spInputManager->m_PadState; + + if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_DpadUp)) + g_secretFlags = SECRET_UP; + + if ((g_secretFlags & SECRET_UP) != 0 && rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_DpadDown)) + g_secretFlags |= SECRET_DOWN; + + if ((g_secretFlags & SECRET_DOWN) != 0 && rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_DpadLeft)) + g_secretFlags |= SECRET_LEFT; + + if ((g_secretFlags & SECRET_LEFT) != 0 && rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_DpadRight)) + g_secretFlags |= SECRET_RIGHT; + } + + if (g_secretFlags == SECRET_ALL) + { + Game_PlaySound("totalring_count"); + g_isSecretDone = true; + } + + break; + } + + case Sonicteam::TitleTask::TitleState_OptionsWait: + { + if (auto& spInputManager = App::s_pApp->m_pDoc->m_vspInputManager[0]) + { + auto& rPadState = spInputManager->m_PadState; + + if (g_secretFlags == SECRET_ALL && + rPadState.IsDown(Sonicteam::SoX::Input::KeyState_A) && + rPadState.IsDown(Sonicteam::SoX::Input::KeyState_Start)) + { + OptionsMenu::s_isCodesUnlocked = true; + } + } break; } @@ -115,6 +166,17 @@ PPC_FUNC(sub_825126A0) 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; + } } if (pTitleTask->m_State != Sonicteam::TitleTask::TitleState_OptionsProceed) diff --git a/MarathonRecomp/ui/common_menu.cpp b/MarathonRecomp/ui/common_menu.cpp index e6feed529..c05605e62 100644 --- a/MarathonRecomp/ui/common_menu.cpp +++ b/MarathonRecomp/ui/common_menu.cpp @@ -4,37 +4,20 @@ #include #include -void CommonMenu::Open() -{ - m_appearTime = ImGui::GetTime(); - m_titleAppearTime = m_appearTime; - m_descAppearTime = m_appearTime; -} +static constexpr double ANIMATION_DURATION = 10.0; void CommonMenu::Draw() { auto* drawList = ImGui::GetBackgroundDrawList(); auto& res = ImGui::GetIO().DisplaySize; - auto width = 0.0f; - auto height = 0.0f; - - if (g_aspectRatio > WIDE_ASPECT_RATIO) - { - width = (res.x - (res.y * 16.0f / 9.0f)) / 2.0f; - } - else if (WIDE_ASPECT_RATIO > g_aspectRatio) - { - height = (res.y - (res.x * 9.0f / 16.0f)) / 2.0f; - } - if (App::s_isInit) SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); - ImVec2 min = { width, height }; - ImVec2 max = { res.x - width, res.y - height }; + ImVec2 min = { g_pillarboxWidth, g_letterboxHeight }; + ImVec2 max = { res.x - g_pillarboxWidth, res.y - g_letterboxHeight }; - auto borderMotionTime = PlayIntroAnim ? ComputeMotion(m_appearTime, 0, 10) : 1.0; + auto borderMotionTime = PlayTransitions ? ComputeMotion(m_time, 0, 10, m_isClosing) : 1.0; auto gradientTop = IM_COL32(0, 103, 255, 255); auto gradientBottom = IM_COL32(0, 92, 229, 255); @@ -57,13 +40,13 @@ void CommonMenu::Draw() ImVec2 redStripCornerMax = { redStripCornerMin.x + Scale(300, true), redStripMotion + redStripHeight }; // Draw corner left red strip. - drawList->AddImage(g_texMainMenu1.get(), redStripCornerMin, redStripCornerMax, GET_UV_COORDS(redStripCornerUVs), redStripColour); + drawList->AddImage(g_upTexMainMenu1.get(), redStripCornerMin, redStripCornerMax, GET_UV_COORDS(redStripCornerUVs), redStripColour); // Draw left stretched red strip. - drawList->AddImage(g_texMainMenu1.get(), { 0.0f, redStripCornerMin.y }, { redStripCornerMin.x, redStripCornerMax.y }, GET_UV_COORDS(redStripLeftStretchUVs), redStripColour); + drawList->AddImage(g_upTexMainMenu1.get(), { 0.0f, redStripCornerMin.y }, { redStripCornerMin.x, redStripCornerMax.y }, GET_UV_COORDS(redStripLeftStretchUVs), redStripColour); // Draw right stretched red strip. - drawList->AddImage(g_texMainMenu1.get(), { redStripCornerMax.x, redStripCornerMin.y }, { res.x, redStripCornerMax.y }, GET_UV_COORDS(redStripRightStretchUVs), redStripColour); + drawList->AddImage(g_upTexMainMenu1.get(), { redStripCornerMax.x, redStripCornerMin.y }, { res.x, redStripCornerMax.y }, GET_UV_COORDS(redStripRightStretchUVs), redStripColour); auto redStripHighlightWidth = Scale(270, true); auto redStripHighlightHeight = Scale(48, true); @@ -78,7 +61,7 @@ void CommonMenu::Draw() SetAdditive(true); SetHorizontalGradient(redStripHighlightMin, redStripHighlightMax, tlColour, brColour); - drawList->AddImage(g_texMainMenu1.get(), redStripHighlightMin, redStripHighlightMax, GET_UV_COORDS(redStripHighlightUVs)); + drawList->AddImage(g_upTexMainMenu1.get(), redStripHighlightMin, redStripHighlightMax, GET_UV_COORDS(redStripHighlightUVs)); ResetGradient(); ResetAdditive(); @@ -89,12 +72,12 @@ void CommonMenu::Draw() drawRedStripHighlight({ redStripCornerMax.x + Scale(-43, true), redStripHighlightY }, IM_COL32(255, 89, 0, 0), IM_COL32(255, 89, 0, 20)); auto redStripHighlightMotionOffsetX = Scale(63, true); - auto redStripHighlightMotionX1Time = ComputeMotion(m_titleAppearTime, PlayIntroAnim ? 10 : 0, 10); - auto redStripHighlightMotionX2Time = ComputeMotion(m_titleAppearTime, PlayIntroAnim ? 12 : 2, 10); - auto redStripHighlightMotionAlphaIn1Time = ComputeMotion(m_titleAppearTime, PlayIntroAnim ? 10 : 0, 8); - auto redStripHighlightMotionAlphaIn2Time = ComputeMotion(m_titleAppearTime, PlayIntroAnim ? 12 : 2, 8); - auto redStripHighlightMotionAlphaOut1Time = ComputeMotion(m_titleAppearTime, PlayIntroAnim ? 18 : 8, 2); - auto redStripHighlightMotionAlphaOut2Time = ComputeMotion(m_titleAppearTime, PlayIntroAnim ? 20 : 2, 2); + auto redStripHighlightMotionX1Time = ComputeMotion(m_titleTime, PlayTransitions ? 10 : 0, 10, m_isClosing); + auto redStripHighlightMotionX2Time = ComputeMotion(m_titleTime, PlayTransitions ? 12 : 2, 10, m_isClosing); + auto redStripHighlightMotionAlphaIn1Time = ComputeMotion(m_titleTime, PlayTransitions ? 10 : 0, 8, m_isClosing); + auto redStripHighlightMotionAlphaIn2Time = ComputeMotion(m_titleTime, PlayTransitions ? 12 : 2, 8, m_isClosing); + auto redStripHighlightMotionAlphaOut1Time = ComputeMotion(m_titleTime, PlayTransitions ? 18 : 8, 2, m_isClosing); + auto redStripHighlightMotionAlphaOut2Time = ComputeMotion(m_titleTime, PlayTransitions ? 20 : 2, 2, m_isClosing); auto redStripHighlightMotionX1 = Lerp(res.x + redStripHighlightWidth, min.x + redStripHighlightMotionOffsetX, redStripHighlightMotionX1Time); auto redStripHighlightMotionX2 = Lerp(res.x + redStripHighlightWidth, min.x + redStripHighlightMotionOffsetX, redStripHighlightMotionX2Time); auto redStripHighlightMotionAlpha1 = Lerp(0.0, 100.0, redStripHighlightMotionAlphaIn1Time); @@ -110,23 +93,24 @@ void CommonMenu::Draw() drawRedStripHighlight({ redStripHighlightMotionX1, redStripHighlightY }, IM_COL32(255, 172, 0, 0), IM_COL32(255, 172, 0, redStripHighlightMotionAlpha1)); drawRedStripHighlight({ redStripHighlightMotionX2, redStripHighlightY }, IM_COL32(255, 172, 0, 0), IM_COL32(255, 172, 0, redStripHighlightMotionAlpha2)); + auto titleText = Title.empty() ? "DUMMY" : Title.data(); auto titleFontSize = Scale(33, true); - auto titleSize = g_fntNewRodin->CalcTextSizeA(titleFontSize, FLT_MAX, 0.0f, Title); + auto titleSize = g_pFntNewRodin->CalcTextSizeA(titleFontSize, FLT_MAX, 0.0f, titleText); auto titleOffsetX = redStripCornerMax.x - Scale(105, true); - auto titleOffsetY = min.y + Scale(85, true); - auto titleOffsetXMotionTime = ComputeMotion(m_titleAppearTime, PlayIntroAnim ? 10 : 0, 10); + auto titleOffsetY = redStripMotion + Scale(3.25, true); + auto titleOffsetXMotionTime = ComputeMotion(m_titleTime, PlayTransitions && !m_isClosing ? 10 : 0, 10, m_isClosing); auto titleOffsetXMotion = Lerp(max.x + titleSize.x, titleOffsetX, titleOffsetXMotionTime); - if (m_previousTitle) + if (!m_previousTitle.empty()) { - auto prevTitleAlphaMotionTime = ComputeMotion(m_titleAppearTime, PlayIntroAnim ? 10 : 0, 3); + auto prevTitleAlphaMotionTime = ComputeMotion(m_titleTime, PlayTransitions ? 10 : 0, 3, m_isClosing); // Draw previous title fading out. - drawList->AddText(g_fntNewRodin, titleFontSize, { titleOffsetX, titleOffsetY }, IM_COL32(255, 255, 255, Lerp(255, 0, prevTitleAlphaMotionTime)), m_previousTitle); + drawList->AddText(g_pFntNewRodin, titleFontSize, { titleOffsetX, titleOffsetY }, IM_COL32(255, 255, 255, Lerp(255, 0, prevTitleAlphaMotionTime)), m_previousTitle.data()); } // Draw title. - drawList->AddText(g_fntNewRodin, titleFontSize, { titleOffsetXMotion, min.y + Scale(85, true) }, IM_COL32(255, 255, 255, 255 * titleOffsetXMotionTime), Title ? Title : "DUMMY"); + drawList->AddText(g_pFntNewRodin, titleFontSize, { titleOffsetXMotion, titleOffsetY }, IM_COL32(255, 255, 255, 255 * titleOffsetXMotionTime), titleText); auto topPlateCornerUVs = PIXELS_TO_UV_COORDS(1024, 1024, 0, 150, 250, 145); auto topPlateLeftStretchUVs = PIXELS_TO_UV_COORDS(1024, 1024, 2, 150, 150, 145); @@ -141,14 +125,14 @@ void CommonMenu::Draw() ImVec2 topPlateStretchMax = { res.x, topPlateCornerMax.y }; // Draw top left corner metal plate. - drawList->AddImage(g_texMainMenu1.get(), topPlateCornerMin, topPlateCornerMax, GET_UV_COORDS(topPlateCornerUVs)); + drawList->AddImage(g_upTexMainMenu1.get(), topPlateCornerMin, topPlateCornerMax, GET_UV_COORDS(topPlateCornerUVs)); // Draw top left stretched metal plate. - AddImageFlipped(g_texMainMenu1.get(), { 0.0f, topPlateCornerMin.y }, { topPlateCornerMin.x + Scale(2, true), topPlateCornerMax.y }, GET_UV_COORDS(topPlateLeftStretchUVs), IM_COL32_WHITE, true); + AddImageFlipped(g_upTexMainMenu1.get(), { 0.0f, topPlateCornerMin.y }, { topPlateCornerMin.x + Scale(2, true), topPlateCornerMax.y }, GET_UV_COORDS(topPlateLeftStretchUVs), IM_COL32_WHITE, true); // Draw top right stretched metal plate. SetHorizontalGradient(topPlateStretchMin, topPlateStretchMax, IM_COL32_WHITE, IM_COL32(200, 200, 200, 255)); - drawList->AddImage(g_texMainMenu1.get(), topPlateStretchMin, topPlateStretchMax, GET_UV_COORDS(topPlateRightStretchUVs)); + drawList->AddImage(g_upTexMainMenu1.get(), topPlateStretchMin, topPlateStretchMax, GET_UV_COORDS(topPlateRightStretchUVs)); ResetGradient(); auto textCoverCornerUVs = PIXELS_TO_UV_COORDS(1024, 1024, 801, 400, 150, 150); @@ -171,11 +155,11 @@ void CommonMenu::Draw() // Draw text cover backdrop. drawList->AddRectFilled({ 0.0f, textCoverMotion }, { res.x, textCoverMotion + textCoverHeight }, IM_COL32(0, 0, 0, 65)); - if (Description) + if (!Description.empty()) { auto descFadeScale = Scale(20, true); auto descFontSize = Scale(27, true); - auto descSize = g_fntRodin->CalcTextSizeA(descFontSize, FLT_MAX, 0.0f, Description); + auto descSize = g_pFntRodin->CalcTextSizeA(descFontSize, FLT_MAX, 0.0f, Description.data()); ImVec2 descBoundsMin = { textCoverCentreMin.x - Scale(18, true), textCoverCentreMin.y + Scale(20, true) }; ImVec2 descBoundsMax = { textCoverCentreMax.x + Scale(18, true), textCoverCentreMax.y - Scale(90, true) }; @@ -254,25 +238,25 @@ void CommonMenu::Draw() m_descPos.x = (descBoundsMin.x + descFadeScale) - m_descScrollOffset; } - auto descAlphaMotionTime = ComputeMotion(m_descAppearTime, PlayIntroAnim ? 10 : 0, 15); + auto descAlphaMotionTime = ComputeMotion(m_descTime, PlayTransitions ? 10 : 0, 15, m_isClosing); // Draw previous description fading out. - if (m_previousDesc) - drawList->AddText(g_fntRodin, descFontSize, m_previousDescPos, IM_COL32(255, 255, 255, Lerp(255, 0, descAlphaMotionTime)), m_previousDesc); + if (!m_previousDesc.empty()) + drawList->AddText(g_pFntRodin, descFontSize, m_previousDescPos, IM_COL32(255, 255, 255, Lerp(255, 0, descAlphaMotionTime)), m_previousDesc.data()); // Draw description. - drawList->AddText(g_fntRodin, descFontSize, m_descPos, IM_COL32(255, 255, 255, Lerp(0, 255, descAlphaMotionTime)), Description); + drawList->AddText(g_pFntRodin, descFontSize, m_descPos, IM_COL32(255, 255, 255, Lerp(0, 255, descAlphaMotionTime)), Description.data()); // Draw left text cover. - drawList->AddImage(g_texMainMenu1.get(), { 0.0f, textCoverCornerLeftMin.y }, { textCoverCornerLeftMin.x + textCoverCornerUVCompensation, textCoverCornerLeftMax.y }, GET_UV_COORDS(textCoverCornerExtendUVs), textCoverColour); - drawList->AddImage(g_texMainMenu1.get(), textCoverCornerLeftMin, textCoverCornerLeftMax, GET_UV_COORDS(textCoverCornerUVs), textCoverColour); + drawList->AddImage(g_upTexMainMenu1.get(), { 0.0f, textCoverCornerLeftMin.y }, { textCoverCornerLeftMin.x + textCoverCornerUVCompensation, textCoverCornerLeftMax.y }, GET_UV_COORDS(textCoverCornerExtendUVs), textCoverColour); + drawList->AddImage(g_upTexMainMenu1.get(), textCoverCornerLeftMin, textCoverCornerLeftMax, GET_UV_COORDS(textCoverCornerUVs), textCoverColour); // Draw centre text cover. - drawList->AddImage(g_texMainMenu1.get(), textCoverCentreMin, textCoverCentreMax, GET_UV_COORDS(textCoverCentreUVs), textCoverColour); + drawList->AddImage(g_upTexMainMenu1.get(), textCoverCentreMin, textCoverCentreMax, GET_UV_COORDS(textCoverCentreUVs), textCoverColour); // Draw right text cover. - AddImageFlipped(g_texMainMenu1.get(), { textCoverCornerRightMax.x - textCoverCornerUVCompensation, textCoverCornerRightMin.y }, { res.x, textCoverCornerRightMax.y }, GET_UV_COORDS(textCoverCornerExtendUVs), textCoverColour); - AddImageFlipped(g_texMainMenu1.get(), textCoverCornerRightMin, textCoverCornerRightMax, GET_UV_COORDS(textCoverCornerUVs), textCoverColour, true); + AddImageFlipped(g_upTexMainMenu1.get(), { textCoverCornerRightMax.x - textCoverCornerUVCompensation, textCoverCornerRightMin.y }, { res.x, textCoverCornerRightMax.y }, GET_UV_COORDS(textCoverCornerExtendUVs), textCoverColour); + AddImageFlipped(g_upTexMainMenu1.get(), textCoverCornerRightMin, textCoverCornerRightMax, GET_UV_COORDS(textCoverCornerUVs), textCoverColour, true); } else { @@ -294,29 +278,50 @@ void CommonMenu::Draw() ImVec2 bottomPlateRightMax = { bottomPlateRightMin.x + bottomPlateWidth, bottomPlateLeftMax.y }; // Draw bottom left metal plate. - drawList->AddImage(g_texMainMenu1.get(), bottomPlateLeftMin, bottomPlateLeftMax, GET_UV_COORDS(bottomPlateUVs)); + drawList->AddImage(g_upTexMainMenu1.get(), bottomPlateLeftMin, bottomPlateLeftMax, GET_UV_COORDS(bottomPlateUVs)); // Draw bottom left stretched metal plate. - AddImageFlipped(g_texMainMenu1.get(), { 0.0f, bottomPlateLeftMin.y }, { bottomPlateLeftMin.x, bottomPlateLeftMax.y }, GET_UV_COORDS(bottomPlateStretchUVs), IM_COL32_WHITE, true); + AddImageFlipped(g_upTexMainMenu1.get(), { 0.0f, bottomPlateLeftMin.y }, { bottomPlateLeftMin.x, bottomPlateLeftMax.y }, GET_UV_COORDS(bottomPlateStretchUVs), IM_COL32_WHITE, true); // Draw bottom right metal plate. - AddImageFlipped(g_texMainMenu1.get(), bottomPlateRightMin, bottomPlateRightMax, GET_UV_COORDS(bottomPlateUVs), IM_COL32_WHITE, true); + AddImageFlipped(g_upTexMainMenu1.get(), bottomPlateRightMin, bottomPlateRightMax, GET_UV_COORDS(bottomPlateUVs), IM_COL32_WHITE, true); // Draw bottom right stretched metal plate. - AddImageFlipped(g_texMainMenu1.get(), { bottomPlateRightMax.x, bottomPlateRightMin.y }, { res.x, bottomPlateRightMax.y }, GET_UV_COORDS(bottomPlateStretchUVs), IM_COL32_WHITE, true); + AddImageFlipped(g_upTexMainMenu1.get(), { bottomPlateRightMax.x, bottomPlateRightMin.y }, { res.x, bottomPlateRightMax.y }, GET_UV_COORDS(bottomPlateStretchUVs), IM_COL32_WHITE, true); if (App::s_isInit) SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); if (ShowVersionString) { - auto verAlphaMotionTime = PlayIntroAnim ? ComputeMotion(m_appearTime, 10, 10) : 1.0; + auto verAlphaMotionTime = PlayTransitions ? ComputeMotion(m_time, 0, m_isClosing ? 3 : 10, m_isClosing) : 1.0; - DrawVersionString(g_fntNewRodin, IM_COL32(0, 0, 0, 70 * verAlphaMotionTime)); + DrawVersionString(g_pFntNewRodin, IM_COL32(0, 0, 0, 70 * verAlphaMotionTime)); } } -void CommonMenu::SetTitle(const char* title, bool isAnimated) +void CommonMenu::Open() +{ + m_isClosing = false; + m_time = ImGui::GetTime(); + m_titleTime = m_time; + m_descTime = m_time; +} + +bool CommonMenu::Close(bool isAnimated) +{ + if (!m_isClosing) + { + m_isClosing = true; + m_time = ImGui::GetTime(); + m_titleTime = m_time; + m_descTime = m_time; + } + + return isAnimated ? ComputeMotion(m_time, 0, ANIMATION_DURATION) >= 1.0 : true; +} + +void CommonMenu::SetTitle(std::string title, bool isAnimated) { m_previousTitle = Title; Title = title; @@ -324,10 +329,10 @@ void CommonMenu::SetTitle(const char* title, bool isAnimated) if (!isAnimated) return; - m_titleAppearTime = ImGui::GetTime(); + m_titleTime = ImGui::GetTime(); } -void CommonMenu::SetDescription(const char* desc, bool isAnimated) +void CommonMenu::SetDescription(std::string desc, bool isAnimated) { m_previousDesc = Description; m_previousDescPos = m_descPos; @@ -338,5 +343,5 @@ void CommonMenu::SetDescription(const char* desc, bool isAnimated) if (!isAnimated) return; - m_descAppearTime = ImGui::GetTime(); + m_descTime = ImGui::GetTime(); } diff --git a/MarathonRecomp/ui/common_menu.h b/MarathonRecomp/ui/common_menu.h index 74c7a3a59..7ec9f9e56 100644 --- a/MarathonRecomp/ui/common_menu.h +++ b/MarathonRecomp/ui/common_menu.h @@ -4,15 +4,16 @@ class CommonMenu { - const char* m_previousTitle{}; - const char* m_previousDesc{}; + std::string m_previousTitle{}; + std::string m_previousDesc{}; ImVec2 m_descPos{}; ImVec2 m_previousDescPos{}; - double m_appearTime{}; - double m_titleAppearTime{}; - double m_descAppearTime{}; + bool m_isClosing{}; + double m_time{}; + double m_titleTime{}; + double m_descTime{}; bool m_isDescScrolling{}; bool m_isDescManualScrolling{}; @@ -44,18 +45,19 @@ class CommonMenu } public: - const char* Title{}; - const char* Description{}; - bool PlayIntroAnim{}; + std::string Title{}; + std::string Description{}; + bool PlayTransitions{}; bool ShowVersionString{ true }; CommonMenu() {} - CommonMenu(const char* title, const char* desc = nullptr, bool playIntroAnim = false, bool showVersionString = true) - : Title(title), Description(desc), PlayIntroAnim(playIntroAnim), ShowVersionString(showVersionString) {} + CommonMenu(std::string title, std::string desc, bool playTransitions = false) + : Title(title), Description(desc), PlayTransitions(playTransitions) {} - void Open(); void Draw(); - void SetTitle(const char* title, bool isAnimated = true); - void SetDescription(const char* desc, bool isAnimated = true); + void Open(); + bool Close(bool isAnimated = true); + void SetTitle(std::string title, bool isAnimated = true); + void SetDescription(std::string desc, bool isAnimated = true); }; diff --git a/MarathonRecomp/ui/imgui_utils.cpp b/MarathonRecomp/ui/imgui_utils.cpp index 2ac67e0f9..114e283b4 100644 --- a/MarathonRecomp/ui/imgui_utils.cpp +++ b/MarathonRecomp/ui/imgui_utils.cpp @@ -12,27 +12,27 @@ #include #include -ImFont* g_fntRodin; -ImFont* g_fntNewRodin; +ImFont* g_pFntRodin; +ImFont* g_pFntNewRodin; -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; +std::unique_ptr g_upTexWindow; +std::unique_ptr g_upTexLight; +std::unique_ptr g_upTexSelect; +std::unique_ptr g_upTexSelectArrow; +std::unique_ptr g_upTexMainMenu1; +std::unique_ptr g_upTexArrow; void InitImGuiUtils() { - g_fntRodin = ImFontAtlasSnapshot::GetFont("FOT-RodinPro-DB.otf"); - g_fntNewRodin = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-UB.otf"); + g_pFntRodin = ImFontAtlasSnapshot::GetFont("FOT-RodinPro-DB.otf"); + g_pFntNewRodin = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-UB.otf"); - 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(g_arrow); + g_upTexWindow = LOAD_ZSTD_TEXTURE(g_window); + g_upTexLight = LOAD_ZSTD_TEXTURE(g_light); + g_upTexSelect = LOAD_ZSTD_TEXTURE(g_select); + g_upTexSelectArrow = LOAD_ZSTD_TEXTURE(g_select_arrow); + g_upTexMainMenu1 = LOAD_ZSTD_TEXTURE(g_main_menu1); + g_upTexArrow = LOAD_ZSTD_TEXTURE(g_arrow); } void SetGradient(const ImVec2& min, const ImVec2& max, ImU32 top, ImU32 bottom) @@ -220,14 +220,16 @@ double ComputeLoopMotion(double time, double offset, double total) 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 time, double offset, double total) +double ComputeLinearMotion(double time, double offset, double total, bool reverse) { - return std::clamp((ImGui::GetTime() - time - offset / 60.0) / total * 60.0, 0.0, 1.0); + auto result = std::clamp((ImGui::GetTime() - time - offset / 60.0) / total * 60.0, 0.0, 1.0); + + return reverse ? 1.0f - result : result; } -double ComputeMotion(double time, double offset, double total) +double ComputeMotion(double time, double offset, double total, bool reverse) { - return sqrt(ComputeLinearMotion(time, offset, total)); + return sqrt(ComputeLinearMotion(time, offset, total, reverse)); } void DrawArrows(ImVec2 min, ImVec2 max) @@ -293,7 +295,7 @@ void DrawArrows(ImVec2 min, ImVec2 max) auto endX = baseX + leftArrowSize; 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)); + drawList->AddImage(g_upTexArrow.get(), { endX, leftBaseY }, { baseX, leftEndY }, GET_UV_COORDS(arrowUV), IM_COL32(255, 255, 255, opacity)); } auto rightBaseY = min.y - (rightArrowSize / 2); @@ -305,11 +307,11 @@ void DrawArrows(ImVec2 min, ImVec2 max) auto endX = baseX + rightArrowSize; 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)); + drawList->AddImage(g_upTexArrow.get(), { baseX, rightBaseY }, { endX, rightEndY }, GET_UV_COORDS(arrowUV), IM_COL32(255, 255, 255, opacity)); } } -static double ImValueDebug(double& value, double increment = 1.0) +double ImValueDebug(double& value, double increment) { #ifdef _WIN32 if (GetAsyncKeyState(VK_OEM_PLUS) & 1) @@ -342,12 +344,12 @@ void DrawContainerBox(ImVec2 min, ImVec2 max, float alpha) SetHorizontalGradient({ max.x - commonWidth, min.y }, max, IM_COL32_WHITE, IM_COL32(255, 255, 255, 0)); - drawList->AddImage(g_texMainMenu1.get(), min, { min.x + commonWidth, min.y + commonHeight }, GET_UV_COORDS(tl), color); - drawList->AddImage(g_texMainMenu1.get(), { min.x + commonWidth, min.y }, { max.x, min.y + commonHeight }, GET_UV_COORDS(tc), color); - drawList->AddImage(g_texMainMenu1.get(), { min.x, min.y + commonHeight }, { min.x + commonWidth, max.y - commonHeight }, GET_UV_COORDS(cl), color); - drawList->AddImage(g_texMainMenu1.get(), { min.x + commonWidth, min.y + commonHeight }, { max.x, max.y - commonHeight }, GET_UV_COORDS(cc), color); - drawList->AddImage(g_texMainMenu1.get(), { min.x, max.y - commonHeight }, { min.x + commonWidth, max.y + bottomHeight }, GET_UV_COORDS(bl), color); - drawList->AddImage(g_texMainMenu1.get(), { min.x + commonWidth, max.y - commonHeight }, { max.x, max.y + bottomHeight }, GET_UV_COORDS(bc), color); + drawList->AddImage(g_upTexMainMenu1.get(), min, { min.x + commonWidth, min.y + commonHeight }, GET_UV_COORDS(tl), color); + drawList->AddImage(g_upTexMainMenu1.get(), { min.x + commonWidth, min.y }, { max.x, min.y + commonHeight }, GET_UV_COORDS(tc), color); + drawList->AddImage(g_upTexMainMenu1.get(), { min.x, min.y + commonHeight }, { min.x + commonWidth, max.y - commonHeight }, GET_UV_COORDS(cl), color); + drawList->AddImage(g_upTexMainMenu1.get(), { min.x + commonWidth, min.y + commonHeight }, { max.x, max.y - commonHeight }, GET_UV_COORDS(cc), color); + drawList->AddImage(g_upTexMainMenu1.get(), { min.x, max.y - commonHeight }, { min.x + commonWidth, max.y + bottomHeight }, GET_UV_COORDS(bl), color); + drawList->AddImage(g_upTexMainMenu1.get(), { min.x + commonWidth, max.y - commonHeight }, { max.x, max.y + bottomHeight }, GET_UV_COORDS(bc), color); ResetGradient(); } @@ -418,12 +420,6 @@ void DrawTextWithShadow(const ImFont* font, float fontSize, const ImVec2& pos, I offset = Scale(offset); - // Original 4:3 has thicker text shadows. - if (Config::AspectRatio == EAspectRatio::OriginalNarrow) - { - radius *= 1.5f; - } - SetOutline(radius); drawList->AddText(font, fontSize, { pos.x + offset, pos.y + offset }, shadowColour, text); ResetOutline(); @@ -672,6 +668,7 @@ void DrawVersionString(const ImFont* font, const ImU32 col) auto textMargin = Scale(2); auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, g_versionString); + // TODO: remove this line after v1 release. drawList->AddText(font, fontSize, { textMargin, res.y - textSize.y - textMargin }, col, "WORK IN PROGRESS"); drawList->AddText(font, fontSize, { res.x - textSize.x - textMargin, res.y - textSize.y - textMargin }, col, g_versionString); } @@ -694,18 +691,18 @@ void DrawToggleLight(ImVec2 pos, bool isEnabled, float alpha) ImVec2 lightGlowMax = { min.x + lightGlowSize, min.y + lightGlowSize }; SetAdditive(true); - drawList->AddImage(g_texLight.get(), lightGlowMin, lightGlowMax, GET_UV_COORDS(lightGlowUVs), IM_COL32(255, 255, 0, 127 * alpha)); + drawList->AddImage(g_upTexLight.get(), lightGlowMin, lightGlowMax, GET_UV_COORDS(lightGlowUVs), IM_COL32(255, 255, 0, 127 * alpha)); SetAdditive(false); auto lightOnUVs = PIXELS_TO_UV_COORDS(64, 64, 14, 0, 14, 14); - drawList->AddImage(g_texLight.get(), min, max, GET_UV_COORDS(lightOnUVs), lightCol); + drawList->AddImage(g_upTexLight.get(), min, max, GET_UV_COORDS(lightOnUVs), lightCol); } else { auto lightOffUVs = PIXELS_TO_UV_COORDS(64, 64, 0, 0, 14, 14); - drawList->AddImage(g_texLight.get(), min, max, GET_UV_COORDS(lightOffUVs), lightCol); + drawList->AddImage(g_upTexLight.get(), min, max, GET_UV_COORDS(lightOffUVs), lightCol); } } diff --git a/MarathonRecomp/ui/imgui_utils.h b/MarathonRecomp/ui/imgui_utils.h index 12fc9efbf..bb23e6608 100644 --- a/MarathonRecomp/ui/imgui_utils.h +++ b/MarathonRecomp/ui/imgui_utils.h @@ -14,14 +14,16 @@ #define BREATHE_MOTION(start, end, time, rate) Lerp(start, end, (sin((ImGui::GetTime() - time) * (2.0f * M_PI / rate)) + 1.0f) / 2.0f) -extern ImFont* g_fntRodin; -extern ImFont* g_fntNewRodin; +#define IM_COL32_WHITE_TRANS IM_COL32(255, 255, 255, 0) -extern std::unique_ptr g_texWindow; -extern std::unique_ptr g_texLight; -extern std::unique_ptr g_texSelect; -extern std::unique_ptr g_texSelectArrow; -extern std::unique_ptr g_texMainMenu1; +extern ImFont* g_pFntRodin; +extern ImFont* g_pFntNewRodin; + +extern std::unique_ptr g_upTexWindow; +extern std::unique_ptr g_upTexLight; +extern std::unique_ptr g_upTexSelect; +extern std::unique_ptr g_upTexSelectArrow; +extern std::unique_ptr g_upTexMainMenu1; void InitImGuiUtils(); @@ -48,9 +50,10 @@ void ResetAdditive(); void AddImageFlipped(ImTextureID texture, const ImVec2& min, const ImVec2& max, const ImVec2& uvMin = { 0, 0 }, const ImVec2& uvMax = { 0, 0 }, ImU32 col = IM_COL32_WHITE, bool flipHorz = false, bool flipVert = false); float Scale(float size, bool useGameplayScale = false); double ComputeLoopMotion(double time, double offset, double total); -double ComputeLinearMotion(double time, double offset, double total); -double ComputeMotion(double time, double offset, double total); +double ComputeLinearMotion(double time, double offset, double total, bool reverse = false); +double ComputeMotion(double time, double offset, double total, bool reverse = false); void DrawArrows(ImVec2 min, ImVec2 max); +double ImValueDebug(double& value, double increment = 1.0); void DrawContainerBox(ImVec2 min, ImVec2 max, float alpha = 1); void DrawTextBasic(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 colour, const char* text); 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); diff --git a/MarathonRecomp/ui/installer_wizard.cpp b/MarathonRecomp/ui/installer_wizard.cpp index 17b04880e..0e110fb8d 100644 --- a/MarathonRecomp/ui/installer_wizard.cpp +++ b/MarathonRecomp/ui/installer_wizard.cpp @@ -18,7 +18,7 @@ #include #include -#include +#include #include #include #include @@ -82,7 +82,7 @@ static std::filesystem::path g_gameSourcePath; static std::array g_dlcSourcePaths; static std::array g_dlcInstalled = {}; static std::array, 8> g_installTextures; -static std::unique_ptr g_upHedgeDev; +static std::unique_ptr g_upSonicNextDev; static Journal g_installerJournal; static Installer::Sources g_installerSources; static uint64_t g_installerAvailableSize = 0; @@ -549,7 +549,7 @@ static void DrawDescriptionContainer() DrawTextParagraph ( - g_fntRodin, + g_pFntRodin, fontSize, lineWidth, { textX, textY }, @@ -558,7 +558,7 @@ static void DrawDescriptionContainer() [=](const char* str, ImVec2 pos) { - DrawTextBasic(g_fntRodin, fontSize, pos, IM_COL32(255, 255, 255, 255 * textAlpha), str); + DrawTextBasic(g_pFntRodin, fontSize, pos, IM_COL32(255, 255, 255, 255 * textAlpha), str); } ); @@ -567,11 +567,7 @@ static void DrawDescriptionContainer() if (g_currentPage == WizardPage::InstallSucceeded) { - auto descTextSize = MeasureCentredParagraph(g_fntRodin, fontSize, lineWidth, lineMargin, descriptionText); - - auto hedgeDevStr = "hedge-dev"; - auto hedgeDevTextSize = g_fntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, hedgeDevStr); - auto hedgeDevTextMarginX = Scale(15); + auto descTextSize = MeasureCentredParagraph(g_pFntRodin, fontSize, lineWidth, lineMargin, descriptionText); auto colWhite = IM_COL32(255, 255, 255, 255 * textAlpha); @@ -580,7 +576,7 @@ static void DrawDescriptionContainer() auto containerRight = containerLeft + Scale(CONTAINER_WIDTH); auto containerBottom = containerTop + Scale(CONTAINER_HEIGHT); - auto marqueeTextSize = g_fntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, g_creditsStr.c_str()); + auto marqueeTextSize = g_pFntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, g_creditsStr.c_str()); auto marqueeTextMarginX = Scale(5); auto marqueeTextMarginY = Scale(15); @@ -588,33 +584,24 @@ static void DrawDescriptionContainer() ImVec2 marqueeTextMin = { containerLeft, marqueeTextPos.y }; ImVec2 marqueeTextMax = { containerRight, containerBottom }; - auto imageScale = hedgeDevTextSize.x / 3; - auto imageMarginY = Scale(2); + auto imageWidth = Scale(524); + auto imageHeight = Scale(45); ImVec2 imageRegionMin = { containerLeft, textY + descTextSize.y }; ImVec2 imageRegionMax = { containerRight, containerBottom - (marqueeTextMax.y - marqueeTextMin.y) }; ImVec2 imageMin = { - /* X */ imageRegionMin.x + ((imageRegionMax.x - imageRegionMin.x) / 2) - (imageScale / 2) - (hedgeDevTextSize.x / 2) - hedgeDevTextMarginX, - /* Y */ imageRegionMin.y + ((imageRegionMax.y - imageRegionMin.y) / 2) - (imageScale / 2) - imageMarginY + /* X */ imageRegionMin.x + ((imageRegionMax.x - imageRegionMin.x) / 2) - (imageWidth / 2), + /* Y */ imageRegionMin.y + ((imageRegionMax.y - imageRegionMin.y) / 2) - (imageHeight / 2) }; - ImVec2 imageMax = { imageMin.x + imageScale, imageMin.y + imageScale }; - - drawList->AddImage(g_upHedgeDev.get(), imageMin, imageMax, { 0, 0 }, { 1, 1 }, colWhite); + ImVec2 imageMax = { imageMin.x + imageWidth, imageMin.y + imageHeight }; - drawList->AddText - ( - g_fntRodin, - fontSize, - { /* X */ imageMax.x + hedgeDevTextMarginX, /* Y */ imageMin.y + (imageScale / 2) - (hedgeDevTextSize.y / 2) }, - colWhite, - hedgeDevStr - ); + drawList->AddImage(g_upSonicNextDev.get(), imageMin, imageMax, { 0, 0 }, { 1, 1 }, colWhite); SetHorizontalMarqueeFade(marqueeTextMin, marqueeTextMax, Scale(32)); - DrawTextWithMarquee(g_fntRodin, fontSize, marqueeTextPos, marqueeTextMin, marqueeTextMax, colWhite, g_creditsStr.c_str(), g_installerEndTime, 0.9, Scale(200)); + DrawTextWithMarquee(g_pFntRodin, fontSize, marqueeTextPos, marqueeTextMin, marqueeTextMax, colWhite, g_creditsStr.c_str(), g_installerEndTime, 0.9, Scale(200)); ResetMarqueeFade(); } @@ -724,7 +711,7 @@ static void DrawButton(ImVec2 min, ImVec2 max, const char *buttonText, bool sour DrawButtonContainer(min, max, baser, baseg, alpha); - ImFont *font = g_fntRodin; + ImFont *font = g_pFntRodin; float size = Scale(18.0f); float squashRatio; ImVec2 textSize = ComputeTextSize(font, buttonText, size, squashRatio, Scale(maxTextWidth)); @@ -1000,7 +987,7 @@ static void DrawLanguagePicker() if (buttonPressed) { Config::Language = LANGUAGE_ENUM[i]; - g_commonMenu.SetTitle(Localise("Installer_Header_Installer").c_str()); + g_commonMenu.SetTitle(Localise("Installer_Header_Installer")); } } } @@ -1015,7 +1002,7 @@ static void DrawSourcePickers() constexpr float ADD_BUTTON_MAX_TEXT_WIDTH = 168.0f; const std::string &addFilesText = Localise("Installer_Button_AddFiles"); float squashRatio; - ImVec2 textSize = ComputeTextSize(g_fntRodin, addFilesText.c_str(), 20.0f, squashRatio, ADD_BUTTON_MAX_TEXT_WIDTH); + ImVec2 textSize = ComputeTextSize(g_pFntRodin, addFilesText.c_str(), 20.0f, squashRatio, ADD_BUTTON_MAX_TEXT_WIDTH); ImVec2 min = { g_aspectRatioOffsetX + Scale(CONTAINER_X + BOTTOM_X_GAP), g_aspectRatioOffsetY + Scale(CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP) }; ImVec2 max = { g_aspectRatioOffsetX + Scale(CONTAINER_X + BOTTOM_X_GAP + textSize.x * squashRatio + BUTTON_TEXT_GAP), g_aspectRatioOffsetY + Scale(CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP + BUTTON_HEIGHT) }; DrawButton(min, max, addFilesText.c_str(), false, true, buttonPressed, ADD_BUTTON_MAX_TEXT_WIDTH); @@ -1027,7 +1014,7 @@ static void DrawSourcePickers() min.x += Scale(BOTTOM_X_GAP + textSize.x * squashRatio + BUTTON_TEXT_GAP); const std::string &addFolderText = Localise("Installer_Button_AddFolder"); - textSize = ComputeTextSize(g_fntRodin, addFolderText.c_str(), 20.0f, squashRatio, ADD_BUTTON_MAX_TEXT_WIDTH); + textSize = ComputeTextSize(g_pFntRodin, addFolderText.c_str(), 20.0f, squashRatio, ADD_BUTTON_MAX_TEXT_WIDTH); max.x = min.x + Scale(textSize.x * squashRatio + BUTTON_TEXT_GAP); DrawButton(min, max, addFolderText.c_str(), false, true, buttonPressed, ADD_BUTTON_MAX_TEXT_WIDTH); if (buttonPressed) @@ -1068,7 +1055,7 @@ static void DrawInstallingProgress() g_installerThread.reset(); g_installerEndTime = ImGui::GetTime(); g_currentPage = g_installerFailed ? WizardPage::InstallFailed : WizardPage::InstallSucceeded; - g_commonMenu.SetTitle(Localise("Installer_Header_Installer").c_str()); + g_commonMenu.SetTitle(Localise("Installer_Header_Installer")); } } } @@ -1106,7 +1093,7 @@ static void InstallerStart() g_installerFailed = false; g_installerFinished = false; g_installerThread = std::make_unique(InstallerThread); - g_commonMenu.SetTitle(Localise("Installer_Header_Installing").c_str()); + g_commonMenu.SetTitle(Localise("Installer_Header_Installing")); } static bool InstallerParseSources(std::string &errorMessage) @@ -1167,7 +1154,7 @@ static void DrawNavigationButton() } const std::string &nextButtonText = Localise(nextButtonKey); - ImVec2 nextTextSize = ComputeTextSize(g_fntRodin, nextButtonText.c_str(), 20.0f, squashRatio, NAV_BUTTON_MAX_TEXT_WIDTH); + ImVec2 nextTextSize = ComputeTextSize(g_pFntRodin, nextButtonText.c_str(), 20.0f, squashRatio, NAV_BUTTON_MAX_TEXT_WIDTH); ImVec2 min = { g_aspectRatioOffsetX + Scale(CONTAINER_X + CONTAINER_WIDTH - nextTextSize.x * squashRatio - BOTTOM_X_GAP - BUTTON_TEXT_GAP), g_aspectRatioOffsetY + Scale(CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP) }; ImVec2 max = { g_aspectRatioOffsetX + Scale(CONTAINER_X + CONTAINER_WIDTH - BOTTOM_X_GAP), g_aspectRatioOffsetY + Scale(CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP + BUTTON_HEIGHT) }; @@ -1392,7 +1379,7 @@ void InstallerWizard::Init() { auto &io = ImGui::GetIO(); - g_commonMenu = CommonMenu(Localise("Installer_Header_Installer").c_str(), nullptr, true); + g_commonMenu = CommonMenu(Localise("Installer_Header_Installer"), "", true); g_installTextures[0] = LOAD_ZSTD_TEXTURE(g_install_001); g_installTextures[1] = LOAD_ZSTD_TEXTURE(g_install_002); g_installTextures[2] = LOAD_ZSTD_TEXTURE(g_install_003); @@ -1401,7 +1388,7 @@ void InstallerWizard::Init() g_installTextures[5] = LOAD_ZSTD_TEXTURE(g_install_006); g_installTextures[6] = LOAD_ZSTD_TEXTURE(g_install_007); g_installTextures[7] = LOAD_ZSTD_TEXTURE(g_install_008); - g_upHedgeDev = LOAD_ZSTD_TEXTURE(g_hedgedev); + g_upSonicNextDev = LOAD_ZSTD_TEXTURE(g_sonicnextdev); for (int i = 0; i < g_credits.size(); i++) { diff --git a/MarathonRecomp/ui/message_window.cpp b/MarathonRecomp/ui/message_window.cpp index a4a13aa3f..68bc276c4 100644 --- a/MarathonRecomp/ui/message_window.cpp +++ b/MarathonRecomp/ui/message_window.cpp @@ -179,7 +179,7 @@ void DrawContainerArrow(const ImVec2 pos, float scale, float rotation, uint32_t 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); + drawList->AddImageQuad(g_upTexWindow.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) @@ -198,16 +198,16 @@ void DrawContainer(const ImVec2 min, const ImVec2 max) auto lineOffsetRight = Scale(3); // Top - drawList->AddImage(g_texWindow.get(), min, { max.x, min.y + lineScale }, GET_UV_COORDS(lineHorzUVs)); + drawList->AddImage(g_upTexWindow.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)); + drawList->AddImage(g_upTexWindow.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)); + drawList->AddImage(g_upTexWindow.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)); + drawList->AddImage(g_upTexWindow.get(), { max.x - lineOffsetRight, min.y }, { (max.x - lineOffsetRight) + lineScale, max.y }, GET_UV_COORDS(lineVertUVs)); SetAdditive(true); @@ -273,7 +273,7 @@ void DrawButtonArrow(const ImVec2 pos) drawList->AddImage ( - g_texSelectArrow.get(), + g_upTexSelectArrow.get(), { pos.x + arrowRight, pos.y }, { pos.x + arrowRight + arrowScaleX, pos.y + arrowScaleY }, GET_UV_COORDS(arrowUVs), diff --git a/MarathonRecomp/ui/options_menu.cpp b/MarathonRecomp/ui/options_menu.cpp index 715b4e3e6..0aed0a38f 100644 --- a/MarathonRecomp/ui/options_menu.cpp +++ b/MarathonRecomp/ui/options_menu.cpp @@ -1,69 +1,956 @@ #include "options_menu.h" #include #include -#include +#include +#include +#include +#include +#include #include #include +#include +#include +#include -static CommonMenu g_commonMenu; -static double g_appearTime = 0.0; +// #define USE_REFERENCE_PARAMETERS + +#ifdef USE_REFERENCE_PARAMETERS +static constexpr double CTRL_OFFSET_X = 5.0; +#else +static constexpr double CTRL_OFFSET_X = 30.0; +#endif + +static double g_stateTime{}; +static double g_flowStateTime{}; +static double g_scrollArrowsTime{}; + +static bool g_up{}; +static bool g_upWasHeld{}; +static bool g_down{}; +static bool g_downWasHeld{}; +static bool g_left{}; +static bool g_leftWasHeld{}; +static bool g_right{}; +static bool g_rightWasHeld{}; +static bool g_isAccepted{}; +static bool g_isDeclined{}; +static bool g_isReset{}; + +static int g_categoryIndex{ -1 }; +static int g_optionIndex{}; +static int g_optionCount{}; +static IConfigDef* g_optionCurrent{}; +static bool g_optionCanReset{}; + +static std::unique_ptr g_upTexMainMenu7{}; +static std::unique_ptr g_upTexMainMenu8{}; +static std::unique_ptr g_upTexMainMenu9{}; + +static std::string& GetCategoryName(OptionsMenuCategory category) +{ + switch (category) + { + case OptionsMenuCategory::System: return Localise("Options_Category_System"); + case OptionsMenuCategory::Input: return Localise("Options_Category_Input"); + case OptionsMenuCategory::Audio: return Localise("Options_Category_Audio"); + case OptionsMenuCategory::Video: return Localise("Options_Category_Video"); + case OptionsMenuCategory::Code: return Localise("Options_Category_Code"); + } + + return g_localeMissing; +} + +static std::string& GetCategoryDescription(OptionsMenuCategory category) +{ + switch (category) + { + case OptionsMenuCategory::System: return Localise("Options_Desc_Category_System"); + case OptionsMenuCategory::Input: return Localise("Options_Desc_Category_Input"); + case OptionsMenuCategory::Audio: return Localise("Options_Desc_Category_Audio"); + case OptionsMenuCategory::Video: return Localise("Options_Desc_Category_Video"); + case OptionsMenuCategory::Code: return Localise("Options_Desc_Category_Code"); + } + + return g_localeMissing; +} + +static void ResetCategorySelection() +{ + g_categoryIndex = -1; +} + +static void ResetOptionSelection() +{ + g_optionIndex = 0; + g_optionCurrent = nullptr; +} + +static void ResetSelection() +{ + g_flowStateTime = 0.0; + + ResetCategorySelection(); + ResetOptionSelection(); +} + +static bool CheckAndDiscard(bool& value) +{ + if (value) + { + value = false; + return true; + } + + return false; +} + +static void MoveCursor(int& cursorIndex, double& cursorTime, int min = 0, int max = INT_MAX, std::function onCursorMoved = nullptr) +{ + auto time = ImGui::GetTime(); + + // TODO: handle holding inputs and allow selecting up/down or left/right. + + if (g_up) + { + --cursorIndex; + + if (cursorIndex < min) + cursorIndex = max - 1; + } + else if (g_down) + { + ++cursorIndex; + + if (cursorIndex >= max) + cursorIndex = min; + } + + if (g_up || g_down) + { + cursorTime = ImGui::GetTime(); + + Game_PlaySound("move"); + + if (onCursorMoved) + onCursorMoved(); + } +} + +static void DrawCursor(ImVec2 pos) +{ + auto drawList = ImGui::GetBackgroundDrawList(); + + auto cursorWidth = Scale(29, true); + auto cursorHeight = Scale(24, true); + + drawList->AddRectFilled(pos, { pos.x + cursorWidth, pos.y + cursorHeight }, IM_COL32_WHITE); +} + +static void DrawCategories(ImVec2 min, ImVec2 max) +{ + auto drawList = ImGui::GetBackgroundDrawList(); + + auto selectedUVs = PIXELS_TO_UV_COORDS(1024, 1024, 443, 524, 560, 47); + auto unselectedUVs = PIXELS_TO_UV_COORDS(1024, 1024, 443, 579, 560, 47); + auto categoryWidth = Scale(560, true); + auto categoryHeight = Scale(47, true); + + if (g_categoryIndex == -1) + { + g_categoryIndex = 0; + OptionsMenu::s_commonMenu.SetDescription(GetCategoryDescription((OptionsMenuCategory)g_categoryIndex)); + } + + for (size_t i = 0; i < (size_t)OptionsMenuCategory::Count; i++) + { + // Don't show codes category if locked. + if (!OptionsMenu::s_isCodesUnlocked && (OptionsMenuCategory)i == OptionsMenuCategory::Code) + continue; + + auto isCurrent = i == g_categoryIndex; + auto categoryOffsetY = Scale(48, true) * i; + + ImVec2 categoryMin = { min.x + Scale(42, true), min.y + Scale(147, true) + categoryOffsetY }; + ImVec2 categoryMax = { categoryMin.x + categoryWidth, categoryMin.y + categoryHeight }; + + auto categoryAlphaMotionTime = ComputeMotion(g_stateTime, 0, 5); + auto categoryAlphaMotion = IM_COL32(255, 255, 255, 255 * categoryAlphaMotionTime); + + SetHorizontalGradient(categoryMin, categoryMax, categoryAlphaMotion, IM_COL32_WHITE_TRANS); + drawList->AddImage(g_upTexMainMenu7.get(), categoryMin, categoryMax, GET_UV_COORDS(isCurrent ? selectedUVs : unselectedUVs)); + ResetGradient(); + + if (isCurrent && OptionsMenu::s_flowState == OptionsMenuFlowState::CategoryCursor) + { + auto cursorOffsetX = Scale(89, true); + auto cursorOffsetY = Scale(8, true); + + DrawCursor({ categoryMin.x + cursorOffsetX, categoryMin.y + cursorOffsetY }); + } + + if (App::s_isInit) + SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); + + auto fontSize = Scale(27, true); + auto text = GetCategoryName((OptionsMenuCategory)i); + auto textSize = g_pFntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, text.c_str()); + + drawList->AddText(g_pFntRodin, fontSize, { categoryMin.x + Scale(129, true), categoryMin.y + Scale(6, true) }, categoryAlphaMotion, text.c_str()); + + if (App::s_isInit) + SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); + } +} + +template +static void DrawOption(int rowIndex, ConfigDef* config, bool isAccessible, std::string* inaccessibleReason = nullptr, T valueMin = T(0), T valueCentre = T(0.5), T valueMax = T(1), bool isSlider = true) +{ + auto drawList = ImGui::GetBackgroundDrawList(); + auto clipRectMin = drawList->GetClipRectMin(); + auto clipRectMax = drawList->GetClipRectMax(); + + auto fontSize = Scale(27, true); + auto accessibleColour = isAccessible ? IM_COL32_WHITE : IM_COL32(137, 137, 137, 255); + auto optionHeight = Scale(106, true); + auto offsetScroll = 0.0f; + + // Only scroll if page has more than three items. + if (g_optionCount > 3) + { + if (g_optionIndex >= g_optionCount - 2) + { + // Stop scrolling near bottom to use cursor instead. + offsetScroll = -(g_optionCount - 3) * optionHeight; + } + else if (g_optionIndex >= 1) + { + // Start scrolling from the middle item. + offsetScroll = -(g_optionIndex - 1) * optionHeight; + } + } + + auto offsetY = optionHeight * rowIndex + offsetScroll; + auto isCurrent = g_optionIndex == rowIndex; + auto isSelected = isCurrent && OptionsMenu::s_flowState == OptionsMenuFlowState::OptionSelected; + + auto bgEdgeUVs = PIXELS_TO_UV_COORDS(256, 256, 1, 0, 51, 45); + auto bgStretchUVs = PIXELS_TO_UV_COORDS(256, 256, 51, 0, 51, 45); + auto bgWidth = Scale(50, true); + auto bgHeight = Scale(45, true); + auto bgColourMotion = isSelected ? 255 : 0; + auto bgColour = IM_COL32(bgColourMotion, bgColourMotion, bgColourMotion, 245); + auto bgFadeColour = IM_COL32(bgColourMotion, bgColourMotion, bgColourMotion, 45); + + ImVec2 titleBgEdgeMin = { clipRectMin.x, clipRectMin.y + offsetY }; + ImVec2 titleBgEdgeMax = { titleBgEdgeMin.x + bgWidth, titleBgEdgeMin.y + bgHeight }; + ImVec2 titleBgStretchMin = { titleBgEdgeMax.x, titleBgEdgeMin.y }; + ImVec2 titleBgStretchMax = { clipRectMax.x, titleBgEdgeMax.y }; + ImVec2 titleBgFadeMin = { titleBgStretchMax.x - Scale(300, true), titleBgStretchMax.y }; + ImVec2 titleBgFadeMax = titleBgStretchMax; + + drawList->AddImage(g_upTexMainMenu8.get(), titleBgEdgeMin, titleBgEdgeMax, GET_UV_COORDS(bgEdgeUVs), bgColour); + + SetHorizontalGradient(titleBgFadeMin, titleBgFadeMax, bgColour, bgFadeColour); + drawList->AddImage(g_upTexMainMenu8.get(), titleBgStretchMin, titleBgStretchMax, GET_UV_COORDS(bgStretchUVs), bgColour); + ResetGradient(); + + if (App::s_isInit) + SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); + + ImVec2 titlePos = { titleBgEdgeMin.x + Scale(51, true), titleBgEdgeMin.y + Scale(8, true) }; + + drawList->AddText(g_pFntRodin, fontSize, titlePos, accessibleColour, config->GetNameLocalised(Config::Language).c_str()); + + if (App::s_isInit) + SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); + + if (isCurrent) + { + if (OptionsMenu::s_flowState == OptionsMenuFlowState::OptionCursor) + { + if (config != g_optionCurrent) + OptionsMenu::s_commonMenu.SetDescription(isAccessible ? config->GetDescription(Config::Language) : *inaccessibleReason); + + DrawCursor({ titleBgEdgeMin.x + Scale(18, true), titleBgEdgeMin.y + Scale(10, true) }); + + g_optionCurrent = config; + } + else + { + g_optionCurrent = nullptr; + } + } + + ImVec2 ctrlBgLeftEdgeMin = { titleBgEdgeMin.x + Scale(CTRL_OFFSET_X, true), titleBgEdgeMin.y + Scale(54, true)}; + ImVec2 ctrlBgLeftEdgeMax = { ctrlBgLeftEdgeMin.x + bgWidth, ctrlBgLeftEdgeMin.y + bgHeight }; + ImVec2 ctrlBgStretchMin = { ctrlBgLeftEdgeMax.x, ctrlBgLeftEdgeMin.y }; + ImVec2 ctrlBgStretchMax = { ctrlBgStretchMin.x + Scale(300, true), ctrlBgLeftEdgeMax.y }; + ImVec2 ctrlBgRightEdgeMin = { ctrlBgStretchMax.x, ctrlBgStretchMin.y }; + ImVec2 ctrlBgRightEdgeMax = { ctrlBgRightEdgeMin.x + bgWidth, ctrlBgRightEdgeMin.y + bgHeight }; + + ImVec2 ctrlBgCentre = + { + ctrlBgLeftEdgeMin.x + ((ctrlBgRightEdgeMax.x - ctrlBgLeftEdgeMin.x) / 2), + ctrlBgLeftEdgeMin.y + ((ctrlBgLeftEdgeMax.y - ctrlBgLeftEdgeMin.y) / 2) + }; + + drawList->AddImage(g_upTexMainMenu8.get(), ctrlBgLeftEdgeMin, ctrlBgLeftEdgeMax, GET_UV_COORDS(bgEdgeUVs), accessibleColour); + drawList->AddImage(g_upTexMainMenu8.get(), ctrlBgStretchMin, ctrlBgStretchMax, GET_UV_COORDS(bgStretchUVs), accessibleColour); + AddImageFlipped(g_upTexMainMenu8.get(), ctrlBgRightEdgeMin, ctrlBgRightEdgeMax, GET_UV_COORDS(bgEdgeUVs), accessibleColour, true); + + constexpr auto isSliderType = std::is_same_v || std::is_same_v; + + auto drawSelectionArrows = [=]() + { + if (!isSelected) + return; + + auto arrowUVs = PIXELS_TO_UV_COORDS(256, 256, 15, 46, 28, 26); + auto arrowWidth = Scale(28, true); + auto arrowHeight = Scale(26, true); + auto arrowOffsetX = Scale(30, true); + auto arrowOffsetY = Scale(10, true); + + ImVec2 arrowLeftMin = { ctrlBgLeftEdgeMin.x + arrowOffsetX, ctrlBgLeftEdgeMin.y + arrowOffsetY }; + ImVec2 arrowLeftMax = { arrowLeftMin.x + arrowWidth, arrowLeftMin.y + arrowHeight }; + ImVec2 arrowRightMin = { ctrlBgRightEdgeMax.x - arrowWidth - arrowOffsetX, arrowLeftMin.y }; + ImVec2 arrowRightMax = { arrowRightMin.x + arrowWidth, arrowLeftMax.y }; + + // Draw left arrow. + AddImageFlipped(g_upTexMainMenu8.get(), arrowLeftMin, arrowLeftMax, GET_UV_COORDS(arrowUVs), IM_COL32_WHITE, true); + + // Draw right arrow. + drawList->AddImage(g_upTexMainMenu8.get(), arrowRightMin, arrowRightMax, GET_UV_COORDS(arrowUVs)); + }; + + if constexpr (isSliderType) + { + if (isSlider) + { + auto gaugeOffsetX = Scale(41, true); + auto gaugeHeight = Scale(11, true); + + ImVec2 gaugeMin = { ctrlBgLeftEdgeMin.x + gaugeOffsetX, (ctrlBgRightEdgeMin.y + bgHeight / 2) - (gaugeHeight / 2) + Scale(1, true) }; + ImVec2 gaugeMax = { ctrlBgRightEdgeMax.x - gaugeOffsetX, gaugeMin.y + gaugeHeight / 2 }; + + drawList->AddRectFilled(gaugeMin, gaugeMax, IM_COL32_BLACK, Scale(10, true)); + + auto handleUVs = PIXELS_TO_UV_COORDS(256, 256, 72, 49, 49, 19); + auto handleWidth = Scale(49, true); + auto handleHeight = Scale(19, true); + auto handleOffsetX = Scale(12, true); + auto handleOffsetFactor = 0.0f; + + if (config->Value <= valueCentre) + { + handleOffsetFactor = float(config->Value - valueMin) / (valueCentre - valueMin) * 0.5f; + } + else + { + handleOffsetFactor = 0.5f + float(config->Value - valueCentre) / (valueMax - valueCentre) * 0.5f; + } + + handleOffsetX = ((gaugeMin.x + handleOffsetX) + ((gaugeMax.x - handleOffsetX) - (gaugeMin.x + handleOffsetX)) * handleOffsetFactor) - (handleWidth / 2); + + ImVec2 handleMin = { handleOffsetX, gaugeMin.y - (handleHeight / 2) + Scale(4.5, true) }; + ImVec2 handleMax = { handleMin.x + handleWidth, handleMin.y + handleHeight }; + + drawList->AddImage(g_upTexMainMenu8.get(), handleMin, handleMax, GET_UV_COORDS(handleUVs), accessibleColour); + } + else + { + drawSelectionArrows(); + } + } + else + { + drawSelectionArrows(); + } + + if (isCurrent) + { + static T s_oldValue; + + if (isAccessible) + { + if (CheckAndDiscard(g_isAccepted)) + { + Game_PlaySound("main_deside"); + + if (OptionsMenu::s_flowState == OptionsMenuFlowState::OptionCursor) + { + s_oldValue = config->Value; + + if (config->LockCallback) + config->LockCallback(config); + + OptionsMenu::s_flowState = OptionsMenuFlowState::OptionSelected; + } + else + { + if (config->Value != s_oldValue) + { + VideoConfigValueChangedCallback(config); + + if (config->ApplyCallback) + config->ApplyCallback(config); + } + + OptionsMenu::s_flowState = OptionsMenuFlowState::OptionCursor; + } + + g_flowStateTime = ImGui::GetTime(); + } + + if (CheckAndDiscard(g_isDeclined)) + { + Game_PlaySound("window_close"); + + if (config->Value != s_oldValue) + { + config->Value = s_oldValue; + + VideoConfigValueChangedCallback(config); + + if (config->Callback) + config->Callback(config); + + if (config->ApplyCallback) + config->ApplyCallback(config); + } + + OptionsMenu::s_flowState = OptionsMenuFlowState::OptionCursor; + g_flowStateTime = ImGui::GetTime(); + } + + if (g_optionCanReset && CheckAndDiscard(g_isReset)) + { + Game_PlaySound("window_close"); + + if (!config->IsDefaultValue()) + { + config->MakeDefault(); + + VideoConfigValueChangedCallback(config); + + if (config->Callback) + config->Callback(config); + + if (config->ApplyCallback) + config->ApplyCallback(config); + } + } + + if (isSelected) + { + auto increment = g_right; + auto decrement = g_left; + + if constexpr (std::is_enum_v) + { + auto it = config->EnumTemplateReverse.find(config->Value); + + if (decrement) + { + if (it == config->EnumTemplateReverse.begin()) + it = config->EnumTemplateReverse.end(); + + --it; + } + else if (increment) + { + ++it; + + if (it == config->EnumTemplateReverse.end()) + it = config->EnumTemplateReverse.begin(); + } + + config->Value = it->first; + config->SnapToNearestAccessibleValue(increment); + + if (increment || decrement) + { + Game_PlaySound("move"); + + if (config->Callback) + config->Callback(config); + } + } + else if constexpr (isSliderType) + { + if constexpr (std::is_integral_v) + { + if (decrement) + config->Value -= 1; + else if (increment) + config->Value += 1; + } + else + { + if (decrement) + config->Value -= 0.01f; + else if (increment) + config->Value += 0.01f; + } + + auto isValueInBounds = config->Value >= valueMin && config->Value <= valueMax; + + if ((increment || decrement) && isValueInBounds) + Game_PlaySound("move"); + + config->Value = std::clamp(config->Value, valueMin, valueMax); + } + else if constexpr (std::is_same_v) + { + if (increment || decrement) + { + Game_PlaySound("move"); + + config->Value = !config->Value; + } + } + + // Run standard callback if there's no callback on apply. + if (!config->ApplyCallback) + { + if ((increment || decrement) && config->Callback) + config->Callback(config); + } + } + } + else + { + if (CheckAndDiscard(g_isAccepted)) + Game_PlaySound("cannot_deside"); + } + } + + std::string valueText; + auto isValueCentred = false; + + if constexpr (std::is_same_v) + { + valueText = fmt::format("{}%", int(round(config->Value * 100.0f))); + } + else if constexpr (std::is_same_v) + { + if (config == &Config::WindowSize) + { + if (Config::Fullscreen) + { + int displayW, displayH; + + GameWindow::GetSizeInPixels(&displayW, &displayH); + + valueText = fmt::format("{}x{}", displayW, displayH); + } + else + { + auto displayModes = GameWindow::GetDisplayModes(); + + if (config->Value >= 0 && config->Value < displayModes.size()) + { + auto& displayMode = displayModes[config->Value]; + + valueText = fmt::format("{}x{}", displayMode.w, displayMode.h); + } + else + { + valueText = fmt::format("{}x{}", GameWindow::s_width, GameWindow::s_height); + } + } + + isValueCentred = true; + } + else if (config == &Config::Monitor) + { + valueText = fmt::format("{}", config->Value + 1); + isValueCentred = true; + } + else + { + valueText = fmt::format("{}", config->Value); + + if (isSlider && config->Value >= valueMax) + valueText = Localise("Options_Value_Max"); + } + } + else + { + valueText = config->GetValueLocalised(Config::Language); + isValueCentred = true; + } + + auto valueTextSize = g_pFntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, valueText.data()); + + ImVec2 valuePos = { ctrlBgCentre.x - (valueTextSize.x / 2), ctrlBgCentre.y - (valueTextSize.y / 2) - Scale(2, true) }; + + // Align text to right side of the background. + if (!isValueCentred) + valuePos.x = ctrlBgRightEdgeMax.x + Scale(10, true); + + if (App::s_isInit) + SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); + + drawList->AddText(g_pFntRodin, fontSize, valuePos, accessibleColour, valueText.data()); + + if (App::s_isInit) + SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); +} + +static void DrawOptions(ImVec2 min, ImVec2 max) +{ + auto drawList = ImGui::GetBackgroundDrawList(); + + drawList->PushClipRect(min, max); + + auto rowCount = 0; + auto cmnReason = &Localise("Options_Desc_NotAvailable"); + + switch ((OptionsMenuCategory)g_categoryIndex) + { + case OptionsMenuCategory::System: + DrawOption(rowCount++, &Config::Language, !OptionsMenu::s_isPause, cmnReason); + DrawOption(rowCount++, &Config::VoiceLanguage, !OptionsMenu::s_isPause, cmnReason); + DrawOption(rowCount++, &Config::Subtitles, true); + DrawOption(rowCount++, &Config::Hints, true); + DrawOption(rowCount++, &Config::ControlTutorial, true); + DrawOption(rowCount++, &Config::Autosave, true); + DrawOption(rowCount++, &Config::AchievementNotifications, true); + break; + + case OptionsMenuCategory::Input: + DrawOption(rowCount++, &Config::HorizontalCamera, true); + DrawOption(rowCount++, &Config::VerticalCamera, true); + DrawOption(rowCount++, &Config::AllowBackgroundInput, true); + DrawOption(rowCount++, &Config::ControllerIcons, true); + break; + + case OptionsMenuCategory::Audio: + DrawOption(rowCount++, &Config::MasterVolume, true); + DrawOption(rowCount++, &Config::MusicVolume, true); + DrawOption(rowCount++, &Config::EffectsVolume, true); + DrawOption(rowCount++, &Config::ChannelConfiguration, !OptionsMenu::s_isPause, cmnReason); + DrawOption(rowCount++, &Config::MusicAttenuation, AudioPatches::CanAttenuate(), &Localise("Options_Desc_OSNotSupported")); + break; + + case OptionsMenuCategory::Video: + { + DrawOption(rowCount++, &Config::WindowSize, !Config::Fullscreen, + &Localise("Options_Desc_NotAvailableFullscreen"), + 0, 0, (int)GameWindow::GetDisplayModes().size() - 1, false); + + auto displayCount = GameWindow::GetDisplayCount(); + auto canChangeMonitor = Config::Fullscreen && displayCount > 1; + auto monitorReason = &Localise("Options_Desc_NotAvailableWindowed"); + + if (Config::Fullscreen && displayCount <= 1) + monitorReason = &Localise("Options_Desc_NotAvailableHardware"); + + DrawOption(rowCount++, &Config::Monitor, canChangeMonitor, monitorReason, 0, 0, displayCount - 1, false); + DrawOption(rowCount++, &Config::AspectRatio, true); + DrawOption(rowCount++, &Config::ResolutionScale, true, nullptr, 0.25f, 1.0f, 2.0f); + DrawOption(rowCount++, &Config::Fullscreen, true); + DrawOption(rowCount++, &Config::VSync, true); + DrawOption(rowCount++, &Config::FPS, true, nullptr, FPS_MIN, 120, FPS_MAX); + DrawOption(rowCount++, &Config::Brightness, true); + DrawOption(rowCount++, &Config::AntiAliasing, Config::AntiAliasing.InaccessibleValues.size() != 3, &Localise("Options_Desc_NotAvailableHardware")); + DrawOption(rowCount++, &Config::TransparencyAntiAliasing, Config::AntiAliasing != EAntiAliasing::Off, &Localise("Options_Desc_NotAvailableMSAA")); + DrawOption(rowCount++, &Config::ShadowResolution, true); + DrawOption(rowCount++, &Config::ReflectionResolution, true); + DrawOption(rowCount++, &Config::RadialBlur, true); + DrawOption(rowCount++, &Config::CutsceneAspectRatio, true); + DrawOption(rowCount++, &Config::UIAlignmentMode, true); + + break; + } + + case OptionsMenuCategory::Code: + DrawOption(rowCount++, &Config::ControllableBoundAttack, true); + DrawOption(rowCount++, &Config::ControllableSpinkick, true); + DrawOption(rowCount++, &Config::ControllableTeleportDash, true); + DrawOption(rowCount++, &Config::DisableDWMRoundedCorners, true); + DrawOption(rowCount++, &Config::DisableEdgeGrabLeftover, true); + DrawOption(rowCount++, &Config::DisableKingdomValleyMist, true); + DrawOption(rowCount++, &Config::DisableLowResolutionFontOnCustomUI, true); + DrawOption(rowCount++, &Config::DisablePushState, true); + DrawOption(rowCount++, &Config::DisableTitleInputDelay, true); + DrawOption(rowCount++, &Config::EnableDebugMode, true); + DrawOption(rowCount++, &Config::FixPowerUpJingleDuration, true); + DrawOption(rowCount++, &Config::HUDToggleKey, true); + DrawOption(rowCount++, &Config::MidairControlForMachSpeed, true); + DrawOption(rowCount++, &Config::MidairControlForSnowboards, true); + DrawOption(rowCount++, &Config::RestoreChainJumpFlips, true); + DrawOption(rowCount++, &Config::RestoreChaosBoostJump, true); + DrawOption(rowCount++, &Config::RestoreChaosSpearFlips, true); + DrawOption(rowCount++, &Config::RestoreContextualHUDColours, true); + DrawOption(rowCount++, &Config::RestoreDemoCameraMode, true); + DrawOption(rowCount++, &Config::RestoreSonicActionGauge, true); + DrawOption(rowCount++, &Config::SkipIntroLogos, true); + DrawOption(rowCount++, &Config::TailsGauge, true); + DrawOption(rowCount++, &Config::UseOfficialTitleOnTitleBar, true); + break; + } + + g_optionCount = rowCount; + + drawList->PopClipRect(); + + if (OptionsMenu::s_flowState == OptionsMenuFlowState::OptionCursor) + { + auto scrollArrowUVs = PIXELS_TO_UV_COORDS(1024, 1024, 500, 450, 50, 50); + auto scrollArrowScale = Scale(20, true); + auto scrollArrowAlphaMotionInTime = ComputeMotion(g_scrollArrowsTime, 0, 3); + auto scrollArrowAlphaMotionPauseTime = ComputeMotion(g_scrollArrowsTime, 3, 11); + auto scrollArrowAlphaMotionOutTime = ComputeMotion(g_scrollArrowsTime, 11, 4, true); + auto scrollArrowAlphaMotionLoopTime = ComputeMotion(g_scrollArrowsTime, 15, 50); + auto scrollArrowAlphaMotion = 255; + + if (scrollArrowAlphaMotionPauseTime >= 1.0) + { + // Fade out arrows. + scrollArrowAlphaMotion = 255 * scrollArrowAlphaMotionOutTime; + + // Reset loop. + if (scrollArrowAlphaMotionLoopTime >= 1.0) + g_scrollArrowsTime = ImGui::GetTime(); + } + else + { + // Fade in arrows. + scrollArrowAlphaMotion = 255 * scrollArrowAlphaMotionInTime; + } + + auto scrollArrowColourMotion = IM_COL32(255, 255, 255, scrollArrowAlphaMotion); + + ImVec2 scrollArrowTopMin = { max.x - scrollArrowScale - Scale(64, true) - g_pillarboxWidth, min.y + Scale(12, true) }; + ImVec2 scrollArrowTopMax = { scrollArrowTopMin.x + scrollArrowScale, scrollArrowTopMin.y + scrollArrowScale }; + ImVec2 scrollArrowBottomMin = { scrollArrowTopMin.x, max.y - scrollArrowScale - Scale(16, true) }; + ImVec2 scrollArrowBottomMax = { scrollArrowTopMax.x, scrollArrowBottomMin.y + scrollArrowScale }; + + // Draw top arrow. + AddImageFlipped(g_upTexMainMenu1.get(), scrollArrowTopMin, scrollArrowTopMax, GET_UV_COORDS(scrollArrowUVs), scrollArrowColourMotion, false, true); + + // Draw bottom arrow. + drawList->AddImage(g_upTexMainMenu1.get(), scrollArrowBottomMin, scrollArrowBottomMax, GET_UV_COORDS(scrollArrowUVs), scrollArrowColourMotion); + } +} + +static void DrawContainer(ImVec2 min, ImVec2 max) +{ + auto* drawList = ImGui::GetBackgroundDrawList(); + auto& res = ImGui::GetIO().DisplaySize; + + auto containerUVs = PIXELS_TO_UV_COORDS(1024, 1024, 1, 6, 736, 432); + auto containerWidth = Scale(716, true); + auto containerHeight = Scale(412, true); + auto containerOffsetX = Scale(73, true); + auto containerOffsetY = Scale(136, true); + + ImVec2 containerMin = { max.x - containerWidth - containerOffsetX, min.y + containerOffsetY }; + ImVec2 containerMax = { containerMin.x + containerWidth, containerMin.y + containerHeight }; + + auto containerAlphaMotionTime = ComputeMotion(g_stateTime, 0, 5); + auto containerAlphaMotion = IM_COL32(255, 255, 255, 255 * containerAlphaMotionTime); + + SetGradient(containerMin, containerMax, containerAlphaMotion, containerAlphaMotion, IM_COL32_WHITE_TRANS, IM_COL32(255, 255, 255, 20 * containerAlphaMotionTime)); + drawList->AddImage(g_upTexMainMenu9.get(), containerMin, containerMax, GET_UV_COORDS(containerUVs)); + ResetGradient(); + + auto optionsOffsetX = Scale(170, true); + auto optionsOffsetY = Scale(23, true); + + DrawOptions({ containerMin.x + optionsOffsetX, containerMin.y + optionsOffsetY }, { res.x, containerMax.y - optionsOffsetY }); +} void OptionsMenu::Draw() { + // s_isVisible = GetAsyncKeyState(VK_F5); + if (!s_isVisible) return; + auto* drawList = ImGui::GetBackgroundDrawList(); + auto& res = ImGui::GetIO().DisplaySize; + + ImVec2 min = { g_pillarboxWidth, g_letterboxHeight }; + ImVec2 max = { res.x - g_pillarboxWidth, res.y - g_letterboxHeight }; + + auto upIsHeld = false; + auto downIsHeld = false; + auto leftIsHeld = false; + auto rightIsHeld = false; + + for (auto& spInputManager : App::s_pApp->m_pDoc->m_vspInputManager) + { + auto& rPadState = spInputManager->m_PadState; + + if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DpadUp) || -rPadState.LeftStickVertical > 0.5f) + upIsHeld = true; + + if (!g_upWasHeld && upIsHeld) + g_up = true; + + if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DpadDown) || -rPadState.LeftStickVertical < -0.5f) + downIsHeld = true; + + if (!g_downWasHeld && downIsHeld) + g_down = true; + + if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DpadLeft) || -rPadState.LeftStickHorizontal > 0.5f) + leftIsHeld = true; + + if (!g_leftWasHeld && leftIsHeld) + g_left = true; + + if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DpadRight) || -rPadState.LeftStickHorizontal < -0.5f) + rightIsHeld = true; + + if (!g_rightWasHeld && rightIsHeld) + g_right = true; + + if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_A)) + g_isAccepted = true; + + if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_B)) + g_isDeclined = true; + + if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_X)) + g_isReset = true; + } + + auto alphaMotionTime = s_isPause ? ComputeMotion(g_stateTime, 0, 10, s_state == OptionsMenuState::Closing) : 0.0; + auto alpha = s_isPause ? Lerp(0, 175, alphaMotionTime) : 255; + auto gradientTop = IM_COL32(0, 103, 255, alpha); + auto gradientBottom = IM_COL32(0, 41, 100, alpha); + + drawList->AddRectFilledMultiColor({ 0.0f, g_letterboxHeight }, { res.x, res.y - g_letterboxHeight }, gradientTop, gradientTop, gradientBottom, gradientBottom); + switch (s_state) { - case OptionsMenuState::Opening: - g_commonMenu.Open(); - s_state = OptionsMenuState::Idle; + case OptionsMenuState::Closing: + { + s_commonMenu.Draw(); + + if (s_commonMenu.Close(s_isPause)) + s_isVisible = false; + break; + } default: { - auto* drawList = ImGui::GetBackgroundDrawList(); - auto& res = ImGui::GetIO().DisplaySize; + if (s_state == OptionsMenuState::Opening) + { + s_commonMenu.Open(); + s_state = OptionsMenuState::Idle; + } + + switch (s_flowState) + { + case OptionsMenuFlowState::CategoryCursor: + { + auto categoryCount = (int)OptionsMenuCategory::Count; + + // Remove codes category from cursor select. + if (!s_isCodesUnlocked) + categoryCount -= 1; + + MoveCursor(g_categoryIndex, g_flowStateTime, 0, categoryCount, []() + { + ResetOptionSelection(); - auto alphaMotionTime = s_isPause ? ComputeMotion(g_appearTime, 0, 10) : 0.0; - auto alpha = s_isPause ? Lerp(0, 175, alphaMotionTime) : 255; - auto gradientTop = IM_COL32(0, 103, 255, alpha); - auto gradientBottom = IM_COL32(0, 41, 100, alpha); + s_commonMenu.SetDescription(GetCategoryDescription((OptionsMenuCategory)g_categoryIndex)); + }); - drawList->AddRectFilledMultiColor({ 0.0f, g_letterboxHeight }, { res.x, res.y - g_letterboxHeight }, gradientTop, gradientTop, gradientBottom, gradientBottom); + if (CheckAndDiscard(g_isAccepted)) + { + Game_PlaySound("main_deside"); + + s_flowState = OptionsMenuFlowState::OptionCursor; + g_flowStateTime = ImGui::GetTime(); + g_scrollArrowsTime = g_flowStateTime; + } + + if (CheckAndDiscard(g_isDeclined)) + Close(); + + break; + } + + case OptionsMenuFlowState::OptionCursor: + { + MoveCursor(g_optionIndex, g_flowStateTime, 0, g_optionCount); + + if (CheckAndDiscard(g_isDeclined)) + { + Game_PlaySound("window_close"); + + s_flowState = OptionsMenuFlowState::CategoryCursor; + g_flowStateTime = ImGui::GetTime(); + + s_commonMenu.SetDescription(GetCategoryDescription((OptionsMenuCategory)g_categoryIndex)); + } + + break; + } + } DrawArrows({ 0, res.y / 2 - Scale(10) }, res); - g_commonMenu.Draw(); + DrawCategories(min, max); + DrawContainer(min, max); + + s_commonMenu.Draw(); break; } } + + g_up = false; + g_upWasHeld = upIsHeld; + g_down = false; + g_downWasHeld = downIsHeld; + g_left = false; + g_leftWasHeld = leftIsHeld; + g_right = false; + g_rightWasHeld = rightIsHeld; + g_isAccepted = false; + g_isDeclined = false; + g_isReset = false; +} + +void OptionsMenu::Init() +{ + g_upTexMainMenu7 = LOAD_ZSTD_TEXTURE(g_main_menu7); + g_upTexMainMenu8 = LOAD_ZSTD_TEXTURE(g_main_menu8); + g_upTexMainMenu9 = LOAD_ZSTD_TEXTURE(g_main_menu9); } void OptionsMenu::Open(bool isPause) { - g_commonMenu = CommonMenu(Localise("Options_Header_Name").c_str(), nullptr, isPause); - g_appearTime = ImGui::GetTime(); + s_commonMenu = CommonMenu(Localise("Options_Header_Name"), "", isPause); + g_stateTime = ImGui::GetTime(); s_state = OptionsMenuState::Opening; s_isVisible = true; s_isPause = isPause; + + ResetSelection(); } void OptionsMenu::Close() { - if (s_state != OptionsMenuState::Closing) - { - g_appearTime = ImGui::GetTime(); - s_state = OptionsMenuState::Closing; + if (s_state == OptionsMenuState::Closing) + return; - Config::Save(); - } + g_stateTime = ImGui::GetTime(); + s_state = OptionsMenuState::Closing; - s_isVisible = false; + Game_PlaySound("window_close"); + Config::Save(); } bool OptionsMenu::CanClose() { - return true; + return OptionsMenu::s_flowState == OptionsMenuFlowState::CategoryCursor; } diff --git a/MarathonRecomp/ui/options_menu.h b/MarathonRecomp/ui/options_menu.h index 53bfefd5d..0e57df4de 100644 --- a/MarathonRecomp/ui/options_menu.h +++ b/MarathonRecomp/ui/options_menu.h @@ -1,5 +1,7 @@ #pragma once +#include + enum class OptionsMenuState { Opening, @@ -7,14 +9,35 @@ enum class OptionsMenuState Closing }; +enum class OptionsMenuFlowState +{ + CategoryCursor, + OptionCursor, + OptionSelected +}; + +enum class OptionsMenuCategory +{ + System, + Input, + Audio, + Video, + Code, + Count +}; + class OptionsMenu { public: - static inline OptionsMenuState s_state = OptionsMenuState::Opening; - static inline bool s_isVisible = false; - static inline bool s_isPause = false; - static inline bool s_isRestartRequired = false; + static inline CommonMenu s_commonMenu{}; + static inline OptionsMenuState s_state{}; + static inline OptionsMenuFlowState s_flowState{}; + static inline bool s_isVisible{}; + static inline bool s_isPause{}; + static inline bool s_isRestartRequired{}; + static inline bool s_isCodesUnlocked{}; + static void Init(); static void Draw(); static void Open(bool isPause = false); static void Close(); diff --git a/MarathonRecomp/user/config.cpp b/MarathonRecomp/user/config.cpp index 12ad2b138..ca1a4aaa3 100644 --- a/MarathonRecomp/user/config.cpp +++ b/MarathonRecomp/user/config.cpp @@ -1,7 +1,9 @@ #include "config.h" #include #include +#include #include +#include std::vector g_configDefinitions; @@ -313,19 +315,10 @@ CONFIG_DEFINE_ENUM_TEMPLATE(EWindowState) { "Maximized", EWindowState::Maximised } }; -CONFIG_DEFINE_ENUM_TEMPLATE(ERadialBlur) -{ - { "Off", ERadialBlur::Off }, - { "Original", ERadialBlur::Original }, - { "Enhanced", ERadialBlur::Enhanced } -}; - CONFIG_DEFINE_ENUM_TEMPLATE(EAspectRatio) { { "Auto", EAspectRatio::Auto }, - { "16:9", EAspectRatio::Wide }, - { "4:3", EAspectRatio::Narrow }, - { "Original 4:3", EAspectRatio::OriginalNarrow }, + { "16:9", EAspectRatio::Wide } }; CONFIG_DEFINE_ENUM_TEMPLATE(ETripleBuffering) @@ -337,7 +330,7 @@ CONFIG_DEFINE_ENUM_TEMPLATE(ETripleBuffering) CONFIG_DEFINE_ENUM_TEMPLATE(EAntiAliasing) { - { "None", EAntiAliasing::None }, + { "Off", EAntiAliasing::Off }, { "2x MSAA", EAntiAliasing::MSAA2x }, { "4x MSAA", EAntiAliasing::MSAA4x }, { "8x MSAA", EAntiAliasing::MSAA8x } @@ -360,6 +353,13 @@ CONFIG_DEFINE_ENUM_TEMPLATE(EReflectionResolution) { "Eighth", EReflectionResolution::Eighth }, }; +CONFIG_DEFINE_ENUM_TEMPLATE(ERadialBlur) +{ + { "Off", ERadialBlur::Off }, + { "Original", ERadialBlur::Original }, + { "Enhanced", ERadialBlur::Enhanced } +}; + CONFIG_DEFINE_ENUM_TEMPLATE(ECutsceneAspectRatio) { { "Original", ECutsceneAspectRatio::Original }, @@ -740,6 +740,14 @@ std::filesystem::path Config::GetConfigPath() void Config::CreateCallbacks() { + Config::Language.Callback = [](ConfigDef* def) + { + if (!App::s_isInit) + return; + + OptionsMenu::s_commonMenu.SetDescription(def->GetDescription(def->Value)); + }; + Config::WindowSize.LockCallback = [](ConfigDef* def) { // Try matching the current window size with a known configuration. diff --git a/MarathonRecomp/user/config.h b/MarathonRecomp/user/config.h index 4ce766119..016d71ac3 100644 --- a/MarathonRecomp/user/config.h +++ b/MarathonRecomp/user/config.h @@ -80,9 +80,7 @@ enum class EWindowState : uint32_t enum class EAspectRatio : uint32_t { Auto, - Wide, - Narrow, - OriginalNarrow + Wide }; enum class ETripleBuffering : uint32_t @@ -97,7 +95,7 @@ static constexpr int32_t FPS_MAX = 241; enum class EAntiAliasing : uint32_t { - None = 0, + Off = 0, MSAA2x = 2, MSAA4x = 4, MSAA8x = 8 diff --git a/MarathonRecomp/user/config_def.h b/MarathonRecomp/user/config_def.h index 419d9c93b..7491c3673 100644 --- a/MarathonRecomp/user/config_def.h +++ b/MarathonRecomp/user/config_def.h @@ -12,7 +12,6 @@ CONFIG_DEFINE("System", bool, ShowConsole, false); CONFIG_DEFINE_ENUM_LOCALISED("Input", ECameraRotationMode, HorizontalCamera, ECameraRotationMode::Reverse); CONFIG_DEFINE_ENUM_LOCALISED("Input", ECameraRotationMode, VerticalCamera, ECameraRotationMode::Normal); -CONFIG_DEFINE_LOCALISED("Input", bool, Vibration, true); CONFIG_DEFINE_LOCALISED("Input", bool, AllowBackgroundInput, false); CONFIG_DEFINE_ENUM_LOCALISED("Input", EControllerIcons, ControllerIcons, EControllerIcons::Auto); @@ -54,8 +53,6 @@ CONFIG_DEFINE("Video", int32_t, WindowWidth, 1280); CONFIG_DEFINE("Video", int32_t, WindowHeight, 720); CONFIG_DEFINE_ENUM("Video", EWindowState, WindowState, EWindowState::Normal); CONFIG_DEFINE_LOCALISED("Video", int32_t, Monitor, 0); -// TODO: Localise this enum -CONFIG_DEFINE_ENUM("Video", ERadialBlur, RadialBlur, ERadialBlur::Original); CONFIG_DEFINE_ENUM_LOCALISED("Video", EAspectRatio, AspectRatio, EAspectRatio::Auto); CONFIG_DEFINE_LOCALISED("Video", float, ResolutionScale, 1.0f); CONFIG_DEFINE_LOCALISED("Video", bool, Fullscreen, true); @@ -68,34 +65,34 @@ CONFIG_DEFINE_LOCALISED("Video", float, Brightness, 0.5f); CONFIG_DEFINE_ENUM_LOCALISED("Video", EAntiAliasing, AntiAliasing, EAntiAliasing::MSAA4x); CONFIG_DEFINE_LOCALISED("Video", bool, TransparencyAntiAliasing, true); CONFIG_DEFINE("Video", uint32_t, AnisotropicFiltering, 16); -CONFIG_DEFINE_ENUM("Video", EShadowResolution, ShadowResolution, EShadowResolution::x4096); -// TODO: Localise this enum -CONFIG_DEFINE_ENUM("Video", EReflectionResolution, ReflectionResolution, EReflectionResolution::Half); +CONFIG_DEFINE_ENUM_LOCALISED("Video", EShadowResolution, ShadowResolution, EShadowResolution::x4096); +CONFIG_DEFINE_ENUM_LOCALISED("Video", EReflectionResolution, ReflectionResolution, EReflectionResolution::Half); +CONFIG_DEFINE_ENUM_LOCALISED("Video", ERadialBlur, RadialBlur, ERadialBlur::Original); CONFIG_DEFINE_ENUM_LOCALISED("Video", ECutsceneAspectRatio, CutsceneAspectRatio, ECutsceneAspectRatio::Original); CONFIG_DEFINE_ENUM_LOCALISED("Video", EUIAlignmentMode, UIAlignmentMode, EUIAlignmentMode::Edge); +CONFIG_DEFINE_HIDDEN("Codes", bool, ControllableBoundAttack, false); +CONFIG_DEFINE_HIDDEN("Codes", bool, ControllableSpinkick, false); +CONFIG_DEFINE_HIDDEN("Codes", bool, ControllableTeleportDash, false); CONFIG_DEFINE_HIDDEN("Codes", bool, DisableDWMRoundedCorners, false); -CONFIG_DEFINE_HIDDEN("Codes", bool, DisableTitleInputDelay, false); -CONFIG_DEFINE_HIDDEN("Codes", bool, DisableKingdomValleyMist, false); -CONFIG_DEFINE_HIDDEN("Codes", bool, DisablePushState, false); CONFIG_DEFINE_HIDDEN("Codes", bool, DisableEdgeGrabLeftover, false); +CONFIG_DEFINE_HIDDEN("Codes", bool, DisableKingdomValleyMist, false); CONFIG_DEFINE_HIDDEN("Codes", bool, DisableLowResolutionFontOnCustomUI, false); -CONFIG_DEFINE_HIDDEN("Codes", bool, HUDToggleKey, false); -CONFIG_DEFINE_HIDDEN("Codes", bool, SkipIntroLogos, false); +CONFIG_DEFINE_HIDDEN("Codes", bool, DisablePushState, false); +CONFIG_DEFINE_HIDDEN("Codes", bool, DisableTitleInputDelay, false); +CONFIG_DEFINE_HIDDEN("Codes", bool, EnableDebugMode, false); CONFIG_DEFINE_HIDDEN("Codes", bool, FixPowerUpJingleDuration, false); -CONFIG_DEFINE_HIDDEN("Codes", bool, UseOfficialTitleOnTitleBar, false); -CONFIG_DEFINE_HIDDEN("Codes", bool, RestoreContextualHUDColours, false); -CONFIG_DEFINE_HIDDEN("Codes", bool, RestoreChaosSpearFlips, false); -CONFIG_DEFINE_HIDDEN("Codes", bool, RestoreChaosBoostJump, false); +CONFIG_DEFINE_HIDDEN("Codes", bool, HUDToggleKey, false); +CONFIG_DEFINE_HIDDEN("Codes", bool, MidairControlForMachSpeed, false); +CONFIG_DEFINE_HIDDEN("Codes", bool, MidairControlForSnowboards, false); CONFIG_DEFINE_HIDDEN("Codes", bool, RestoreChainJumpFlips, false); +CONFIG_DEFINE_HIDDEN("Codes", bool, RestoreChaosBoostJump, false); +CONFIG_DEFINE_HIDDEN("Codes", bool, RestoreChaosSpearFlips, false); +CONFIG_DEFINE_HIDDEN("Codes", bool, RestoreContextualHUDColours, false); CONFIG_DEFINE_HIDDEN("Codes", bool, RestoreDemoCameraMode, false); CONFIG_DEFINE_HIDDEN("Codes", bool, RestoreSonicActionGauge, false); +CONFIG_DEFINE_HIDDEN("Codes", bool, SkipIntroLogos, false); CONFIG_DEFINE_HIDDEN("Codes", bool, TailsGauge, false); -CONFIG_DEFINE_HIDDEN("Codes", bool, EnableDebugMode, false); -CONFIG_DEFINE_HIDDEN("Codes", bool, MidairControlForMachSpeed, false); -CONFIG_DEFINE_HIDDEN("Codes", bool, MidairControlForSnowboards, false); -CONFIG_DEFINE_HIDDEN("Codes", bool, ControllableTeleportDash, false); -CONFIG_DEFINE_HIDDEN("Codes", bool, ControllableBoundAttack, false); -CONFIG_DEFINE_HIDDEN("Codes", bool, ControllableSpinkick, false); +CONFIG_DEFINE_HIDDEN("Codes", bool, UseOfficialTitleOnTitleBar, false); CONFIG_DEFINE("Update", time_t, LastChecked, 0); From fdfa54a054e8c789b3c12beb148a48b01d45a53e Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Tue, 7 Oct 2025 08:17:38 +0100 Subject: [PATCH 03/86] Update resources submodule --- MarathonRecompResources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MarathonRecompResources b/MarathonRecompResources index e0c15da29..13cd63f13 160000 --- a/MarathonRecompResources +++ b/MarathonRecompResources @@ -1 +1 @@ -Subproject commit e0c15da298954b8046af761af731ed1b9ff14412 +Subproject commit 13cd63f13cbf508487c7d14a43d0012647a2c254 From e37c0a9930094b43b1c092f695aec5e72ecca8d7 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Tue, 7 Oct 2025 08:25:10 +0100 Subject: [PATCH 04/86] Remove app init checks from options menu text rendering --- MarathonRecomp/ui/options_menu.cpp | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/MarathonRecomp/ui/options_menu.cpp b/MarathonRecomp/ui/options_menu.cpp index 0aed0a38f..e21053b59 100644 --- a/MarathonRecomp/ui/options_menu.cpp +++ b/MarathonRecomp/ui/options_menu.cpp @@ -188,17 +188,14 @@ static void DrawCategories(ImVec2 min, ImVec2 max) DrawCursor({ categoryMin.x + cursorOffsetX, categoryMin.y + cursorOffsetY }); } - if (App::s_isInit) - SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); auto fontSize = Scale(27, true); auto text = GetCategoryName((OptionsMenuCategory)i); auto textSize = g_pFntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, text.c_str()); + SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); drawList->AddText(g_pFntRodin, fontSize, { categoryMin.x + Scale(129, true), categoryMin.y + Scale(6, true) }, categoryAlphaMotion, text.c_str()); - - if (App::s_isInit) - SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); + SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); } } @@ -254,15 +251,11 @@ static void DrawOption(int rowIndex, ConfigDef* config, bool isAcce drawList->AddImage(g_upTexMainMenu8.get(), titleBgStretchMin, titleBgStretchMax, GET_UV_COORDS(bgStretchUVs), bgColour); ResetGradient(); - if (App::s_isInit) - SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); - ImVec2 titlePos = { titleBgEdgeMin.x + Scale(51, true), titleBgEdgeMin.y + Scale(8, true) }; + SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); drawList->AddText(g_pFntRodin, fontSize, titlePos, accessibleColour, config->GetNameLocalised(Config::Language).c_str()); - - if (App::s_isInit) - SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); + SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); if (isCurrent) { @@ -589,13 +582,9 @@ static void DrawOption(int rowIndex, ConfigDef* config, bool isAcce if (!isValueCentred) valuePos.x = ctrlBgRightEdgeMax.x + Scale(10, true); - if (App::s_isInit) - SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); - + SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); drawList->AddText(g_pFntRodin, fontSize, valuePos, accessibleColour, valueText.data()); - - if (App::s_isInit) - SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); + SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); } static void DrawOptions(ImVec2 min, ImVec2 max) From af4cf0f3791c2249f8d05acb4c227a9738b8d8bf Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Tue, 7 Oct 2025 08:34:05 +0100 Subject: [PATCH 05/86] Load hidden category dynamically --- MarathonRecomp/ui/options_menu.cpp | 33 +++++++++--------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/MarathonRecomp/ui/options_menu.cpp b/MarathonRecomp/ui/options_menu.cpp index e21053b59..e85dbbb54 100644 --- a/MarathonRecomp/ui/options_menu.cpp +++ b/MarathonRecomp/ui/options_menu.cpp @@ -655,30 +655,17 @@ static void DrawOptions(ImVec2 min, ImVec2 max) } case OptionsMenuCategory::Code: - DrawOption(rowCount++, &Config::ControllableBoundAttack, true); - DrawOption(rowCount++, &Config::ControllableSpinkick, true); - DrawOption(rowCount++, &Config::ControllableTeleportDash, true); - DrawOption(rowCount++, &Config::DisableDWMRoundedCorners, true); - DrawOption(rowCount++, &Config::DisableEdgeGrabLeftover, true); - DrawOption(rowCount++, &Config::DisableKingdomValleyMist, true); - DrawOption(rowCount++, &Config::DisableLowResolutionFontOnCustomUI, true); - DrawOption(rowCount++, &Config::DisablePushState, true); - DrawOption(rowCount++, &Config::DisableTitleInputDelay, true); - DrawOption(rowCount++, &Config::EnableDebugMode, true); - DrawOption(rowCount++, &Config::FixPowerUpJingleDuration, true); - DrawOption(rowCount++, &Config::HUDToggleKey, true); - DrawOption(rowCount++, &Config::MidairControlForMachSpeed, true); - DrawOption(rowCount++, &Config::MidairControlForSnowboards, true); - DrawOption(rowCount++, &Config::RestoreChainJumpFlips, true); - DrawOption(rowCount++, &Config::RestoreChaosBoostJump, true); - DrawOption(rowCount++, &Config::RestoreChaosSpearFlips, true); - DrawOption(rowCount++, &Config::RestoreContextualHUDColours, true); - DrawOption(rowCount++, &Config::RestoreDemoCameraMode, true); - DrawOption(rowCount++, &Config::RestoreSonicActionGauge, true); - DrawOption(rowCount++, &Config::SkipIntroLogos, true); - DrawOption(rowCount++, &Config::TailsGauge, true); - DrawOption(rowCount++, &Config::UseOfficialTitleOnTitleBar, true); + { + for (auto def : g_configDefinitions) + { + if (def->GetSection() != "Codes") + continue; + + DrawOption(rowCount++, (ConfigDef*)def, true); + } + break; + } } g_optionCount = rowCount; From de29c456835a3bb514f91d2057368bed3e99a488 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Tue, 7 Oct 2025 21:05:21 +0100 Subject: [PATCH 06/86] Implemented cursor animation --- MarathonRecomp/ui/imgui_utils.cpp | 39 ++++++++++++++++++++++++++++ MarathonRecomp/ui/imgui_utils.h | 1 + MarathonRecomp/ui/message_window.cpp | 37 +------------------------- MarathonRecomp/ui/options_menu.cpp | 30 ++++++++++----------- 4 files changed, 55 insertions(+), 52 deletions(-) diff --git a/MarathonRecomp/ui/imgui_utils.cpp b/MarathonRecomp/ui/imgui_utils.cpp index 114e283b4..dd70fb9e9 100644 --- a/MarathonRecomp/ui/imgui_utils.cpp +++ b/MarathonRecomp/ui/imgui_utils.cpp @@ -311,6 +311,45 @@ void DrawArrows(ImVec2 min, ImVec2 max) } } +void DrawArrowCursor(ImVec2 pos, double time, bool isIntroAnim, bool isBlinkingAnim, bool isReversed) +{ + auto drawList = ImGui::GetBackgroundDrawList(); + + auto cursorUVs = PIXELS_TO_UV_COORDS(50, 50, 0, 0, 27, 50); + auto cursorScaleX = Scale(14, true); + + for (int i = 0; i < 3; i++) + { + auto cursorMotionTime = isIntroAnim ? ComputeMotion(time, i, 5, isReversed) : 1.0; + auto cursorScaleYMotion = Lerp(Scale(37.5, true), Scale(24, true), cursorMotionTime); + auto cursorOffsetXMotion = Lerp(0, Scale(8.5, true), cursorMotionTime); + auto cursorOffsetYMotion = Lerp(pos.y - cursorScaleYMotion / 6, pos.y, cursorMotionTime); + auto cursorRightMotion = (cursorOffsetXMotion * 3) - (cursorOffsetXMotion * i); + auto cursorAlphaMotionTime = isIntroAnim ? ComputeMotion(time, i, 2, isReversed) : 1.0; + auto cursorAlphaMotion = (int)Lerp(0, 255, cursorAlphaMotionTime); + + if (isBlinkingAnim) + { + auto cursorAlphaMotionInTime = ComputeLoopMotion(time, 3.0 * i, 12.0); + auto cursorAlphaMotionOutTime = ComputeLoopMotion(time, 3.0 * (i + 1), 12.0); + + // horrible + cursorAlphaMotionTime = cursorAlphaMotionInTime >= 1.0 + ? cursorAlphaMotionOutTime >= 1.0 + ? cursorAlphaMotionInTime + : cursorAlphaMotionOutTime + : cursorAlphaMotionInTime; + + cursorAlphaMotion = (int)Lerp(50 * (i + 1), 255, cursorAlphaMotionTime); + } + + ImVec2 cursorMin = { pos.x + cursorRightMotion, cursorOffsetYMotion }; + ImVec2 cursorMax = { pos.x + cursorRightMotion + cursorScaleX, cursorMin.y + cursorScaleYMotion }; + + drawList->AddImage(g_upTexSelectArrow.get(), cursorMin, cursorMax, GET_UV_COORDS(cursorUVs), IM_COL32(255, 255, 255, cursorAlphaMotion)); + } +} + double ImValueDebug(double& value, double increment) { #ifdef _WIN32 diff --git a/MarathonRecomp/ui/imgui_utils.h b/MarathonRecomp/ui/imgui_utils.h index bb23e6608..455d2e821 100644 --- a/MarathonRecomp/ui/imgui_utils.h +++ b/MarathonRecomp/ui/imgui_utils.h @@ -53,6 +53,7 @@ double ComputeLoopMotion(double time, double offset, double total); double ComputeLinearMotion(double time, double offset, double total, bool reverse = false); double ComputeMotion(double time, double offset, double total, bool reverse = false); void DrawArrows(ImVec2 min, ImVec2 max); +void DrawArrowCursor(ImVec2 pos, double time, bool isIntroAnim = true, bool isBlinkingAnim = true, bool isReversed = false); double ImValueDebug(double& value, double increment = 1.0); void DrawContainerBox(ImVec2 min, ImVec2 max, float alpha = 1); void DrawTextBasic(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 colour, const char* text); diff --git a/MarathonRecomp/ui/message_window.cpp b/MarathonRecomp/ui/message_window.cpp index 68bc276c4..b1f653bc1 100644 --- a/MarathonRecomp/ui/message_window.cpp +++ b/MarathonRecomp/ui/message_window.cpp @@ -247,41 +247,6 @@ void DrawContainer(const ImVec2 min, const ImVec2 max) 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++) - { - 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); - - drawList->AddImage - ( - g_upTexSelectArrow.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 yPadding, float width, float height, std::string& text) { auto drawList = ImGui::GetBackgroundDrawList(); @@ -301,7 +266,7 @@ void DrawButton(int rowIndex, float yOffset, float yPadding, float width, float textColour = IM_COL32(255, gb, gb, 255); if (!g_isClosing) - DrawButtonArrow(min); + DrawArrowCursor(min, g_time, false, true); } auto fontSize = Scale(27); diff --git a/MarathonRecomp/ui/options_menu.cpp b/MarathonRecomp/ui/options_menu.cpp index e85dbbb54..c3918a0a0 100644 --- a/MarathonRecomp/ui/options_menu.cpp +++ b/MarathonRecomp/ui/options_menu.cpp @@ -22,6 +22,7 @@ static constexpr double CTRL_OFFSET_X = 30.0; static double g_stateTime{}; static double g_flowStateTime{}; +static double g_cursorArrowsTime{}; static double g_scrollArrowsTime{}; static bool g_up{}; @@ -136,16 +137,6 @@ static void MoveCursor(int& cursorIndex, double& cursorTime, int min = 0, int ma } } -static void DrawCursor(ImVec2 pos) -{ - auto drawList = ImGui::GetBackgroundDrawList(); - - auto cursorWidth = Scale(29, true); - auto cursorHeight = Scale(24, true); - - drawList->AddRectFilled(pos, { pos.x + cursorWidth, pos.y + cursorHeight }, IM_COL32_WHITE); -} - static void DrawCategories(ImVec2 min, ImVec2 max) { auto drawList = ImGui::GetBackgroundDrawList(); @@ -182,13 +173,12 @@ static void DrawCategories(ImVec2 min, ImVec2 max) if (isCurrent && OptionsMenu::s_flowState == OptionsMenuFlowState::CategoryCursor) { - auto cursorOffsetX = Scale(89, true); + auto cursorOffsetX = Scale(80, true); auto cursorOffsetY = Scale(8, true); - DrawCursor({ categoryMin.x + cursorOffsetX, categoryMin.y + cursorOffsetY }); + DrawArrowCursor({ categoryMin.x + cursorOffsetX, categoryMin.y + cursorOffsetY }, g_cursorArrowsTime, true, false, OptionsMenu::s_state == OptionsMenuState::Closing); } - auto fontSize = Scale(27, true); auto text = GetCategoryName((OptionsMenuCategory)i); auto textSize = g_pFntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, text.c_str()); @@ -264,7 +254,7 @@ static void DrawOption(int rowIndex, ConfigDef* config, bool isAcce if (config != g_optionCurrent) OptionsMenu::s_commonMenu.SetDescription(isAccessible ? config->GetDescription(Config::Language) : *inaccessibleReason); - DrawCursor({ titleBgEdgeMin.x + Scale(18, true), titleBgEdgeMin.y + Scale(10, true) }); + DrawArrowCursor({ titleBgEdgeMin.x + Scale(8, true), titleBgEdgeMin.y + Scale(9, true)}, g_cursorArrowsTime, true, false, OptionsMenu::s_state == OptionsMenuState::Closing); g_optionCurrent = config; } @@ -836,6 +826,7 @@ void OptionsMenu::Draw() { ResetOptionSelection(); + g_cursorArrowsTime = ImGui::GetTime(); s_commonMenu.SetDescription(GetCategoryDescription((OptionsMenuCategory)g_categoryIndex)); }); @@ -845,6 +836,7 @@ void OptionsMenu::Draw() s_flowState = OptionsMenuFlowState::OptionCursor; g_flowStateTime = ImGui::GetTime(); + g_cursorArrowsTime = g_flowStateTime; g_scrollArrowsTime = g_flowStateTime; } @@ -856,7 +848,10 @@ void OptionsMenu::Draw() case OptionsMenuFlowState::OptionCursor: { - MoveCursor(g_optionIndex, g_flowStateTime, 0, g_optionCount); + MoveCursor(g_optionIndex, g_flowStateTime, 0, g_optionCount, []() + { + g_cursorArrowsTime = ImGui::GetTime(); + }); if (CheckAndDiscard(g_isDeclined)) { @@ -864,6 +859,7 @@ void OptionsMenu::Draw() s_flowState = OptionsMenuFlowState::CategoryCursor; g_flowStateTime = ImGui::GetTime(); + g_cursorArrowsTime = g_flowStateTime; s_commonMenu.SetDescription(GetCategoryDescription((OptionsMenuCategory)g_categoryIndex)); } @@ -906,6 +902,7 @@ void OptionsMenu::Open(bool isPause) { s_commonMenu = CommonMenu(Localise("Options_Header_Name"), "", isPause); g_stateTime = ImGui::GetTime(); + g_cursorArrowsTime = g_stateTime; s_state = OptionsMenuState::Opening; s_isVisible = true; @@ -919,8 +916,9 @@ void OptionsMenu::Close() if (s_state == OptionsMenuState::Closing) return; - g_stateTime = ImGui::GetTime(); s_state = OptionsMenuState::Closing; + g_stateTime = ImGui::GetTime(); + g_cursorArrowsTime = g_stateTime; Game_PlaySound("window_close"); Config::Save(); From 43134c4bef20c4459b0d15937c0f54ed511fd7fd Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Wed, 8 Oct 2025 00:15:54 +0100 Subject: [PATCH 07/86] Implemented restart prompt for necessary options --- MarathonRecomp/ui/message_window.cpp | 16 +- MarathonRecomp/ui/options_menu.cpp | 243 +++++++++++++++------------ MarathonRecomp/ui/options_menu.h | 3 +- 3 files changed, 148 insertions(+), 114 deletions(-) diff --git a/MarathonRecomp/ui/message_window.cpp b/MarathonRecomp/ui/message_window.cpp index b1f653bc1..9d9775123 100644 --- a/MarathonRecomp/ui/message_window.cpp +++ b/MarathonRecomp/ui/message_window.cpp @@ -420,16 +420,24 @@ void MessageWindow::Draw() if (g_isDeclined) { - if (g_selectedRowIndex == g_cancelButtonIndex) + if (g_buttons.size() == 1) { Game_PlaySound("window_close"); } else { - Game_PlaySound("move"); + if (g_selectedRowIndex == g_cancelButtonIndex) + { + Game_PlaySound("window_close"); + } + else + { + Game_PlaySound("move"); + } + + g_selectedRowIndex = g_cancelButtonIndex; } - - g_selectedRowIndex = g_cancelButtonIndex; + g_isDeclined = false; } } diff --git a/MarathonRecomp/ui/options_menu.cpp b/MarathonRecomp/ui/options_menu.cpp index c3918a0a0..9c8ef71ac 100644 --- a/MarathonRecomp/ui/options_menu.cpp +++ b/MarathonRecomp/ui/options_menu.cpp @@ -5,8 +5,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -42,6 +44,7 @@ static int g_optionIndex{}; static int g_optionCount{}; static IConfigDef* g_optionCurrent{}; static bool g_optionCanReset{}; +static EChannelConfiguration g_optionCurrentChannelConfiguration{}; static std::unique_ptr g_upTexMainMenu7{}; static std::unique_ptr g_upTexMainMenu8{}; @@ -109,7 +112,7 @@ static void MoveCursor(int& cursorIndex, double& cursorTime, int min = 0, int ma { auto time = ImGui::GetTime(); - // TODO: handle holding inputs and allow selecting up/down or left/right. + // TODO: handle holding inputs. if (g_up) { @@ -176,7 +179,7 @@ static void DrawCategories(ImVec2 min, ImVec2 max) auto cursorOffsetX = Scale(80, true); auto cursorOffsetY = Scale(8, true); - DrawArrowCursor({ categoryMin.x + cursorOffsetX, categoryMin.y + cursorOffsetY }, g_cursorArrowsTime, true, false, OptionsMenu::s_state == OptionsMenuState::Closing); + DrawArrowCursor({ categoryMin.x + cursorOffsetX, categoryMin.y + cursorOffsetY }, g_cursorArrowsTime, true, false, OptionsMenu::s_state != OptionsMenuState::Idle); } auto fontSize = Scale(27, true); @@ -254,7 +257,7 @@ static void DrawOption(int rowIndex, ConfigDef* config, bool isAcce if (config != g_optionCurrent) OptionsMenu::s_commonMenu.SetDescription(isAccessible ? config->GetDescription(Config::Language) : *inaccessibleReason); - DrawArrowCursor({ titleBgEdgeMin.x + Scale(8, true), titleBgEdgeMin.y + Scale(9, true)}, g_cursorArrowsTime, true, false, OptionsMenu::s_state == OptionsMenuState::Closing); + DrawArrowCursor({ titleBgEdgeMin.x + Scale(8, true), titleBgEdgeMin.y + Scale(9, true)}, g_cursorArrowsTime, true, false, OptionsMenu::s_state != OptionsMenuState::Idle); g_optionCurrent = config; } @@ -742,153 +745,175 @@ void OptionsMenu::Draw() ImVec2 min = { g_pillarboxWidth, g_letterboxHeight }; ImVec2 max = { res.x - g_pillarboxWidth, res.y - g_letterboxHeight }; - auto upIsHeld = false; - auto downIsHeld = false; - auto leftIsHeld = false; - auto rightIsHeld = false; + auto alphaMotionTime = s_isPause ? ComputeMotion(g_stateTime, 0, 10, s_state == OptionsMenuState::Closing) : 0.0; + auto alpha = s_isPause ? Lerp(0, 175, alphaMotionTime) : 255; + auto gradientTop = IM_COL32(0, 103, 255, alpha); + auto gradientBottom = IM_COL32(0, 41, 100, alpha); - for (auto& spInputManager : App::s_pApp->m_pDoc->m_vspInputManager) + drawList->AddRectFilledMultiColor({ 0.0f, g_letterboxHeight }, { res.x, res.y - g_letterboxHeight }, gradientTop, gradientTop, gradientBottom, gradientBottom); + + if (s_state == OptionsMenuState::Closing) { - auto& rPadState = spInputManager->m_PadState; + s_commonMenu.Draw(); + + if (s_commonMenu.Close(s_isPause)) + s_isVisible = false; + } + else + { + auto upIsHeld = false; + auto downIsHeld = false; + auto leftIsHeld = false; + auto rightIsHeld = false; + + switch (s_state) + { + case OptionsMenuState::Opening: + s_commonMenu.Open(); + s_state = OptionsMenuState::Idle; + break; - if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DpadUp) || -rPadState.LeftStickVertical > 0.5f) - upIsHeld = true; + case OptionsMenuState::Idle: + { + for (auto& spInputManager : App::s_pApp->m_pDoc->m_vspInputManager) + { + auto& rPadState = spInputManager->m_PadState; - if (!g_upWasHeld && upIsHeld) - g_up = true; + if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DpadUp) || -rPadState.LeftStickVertical > 0.5f) + upIsHeld = true; - if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DpadDown) || -rPadState.LeftStickVertical < -0.5f) - downIsHeld = true; + if (!g_upWasHeld && upIsHeld) + g_up = true; - if (!g_downWasHeld && downIsHeld) - g_down = true; + if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DpadDown) || -rPadState.LeftStickVertical < -0.5f) + downIsHeld = true; - if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DpadLeft) || -rPadState.LeftStickHorizontal > 0.5f) - leftIsHeld = true; + if (!g_downWasHeld && downIsHeld) + g_down = true; - if (!g_leftWasHeld && leftIsHeld) - g_left = true; + if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DpadLeft) || -rPadState.LeftStickHorizontal > 0.5f) + leftIsHeld = true; - if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DpadRight) || -rPadState.LeftStickHorizontal < -0.5f) - rightIsHeld = true; + if (!g_leftWasHeld && leftIsHeld) + g_left = true; - if (!g_rightWasHeld && rightIsHeld) - g_right = true; + if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DpadRight) || -rPadState.LeftStickHorizontal < -0.5f) + rightIsHeld = true; - if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_A)) - g_isAccepted = true; + if (!g_rightWasHeld && rightIsHeld) + g_right = true; - if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_B)) - g_isDeclined = true; + if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_A)) + g_isAccepted = true; - if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_X)) - g_isReset = true; - } + if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_B)) + g_isDeclined = true; - auto alphaMotionTime = s_isPause ? ComputeMotion(g_stateTime, 0, 10, s_state == OptionsMenuState::Closing) : 0.0; - auto alpha = s_isPause ? Lerp(0, 175, alphaMotionTime) : 255; - auto gradientTop = IM_COL32(0, 103, 255, alpha); - auto gradientBottom = IM_COL32(0, 41, 100, alpha); + if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_X)) + g_isReset = true; + } - drawList->AddRectFilledMultiColor({ 0.0f, g_letterboxHeight }, { res.x, res.y - g_letterboxHeight }, gradientTop, gradientTop, gradientBottom, gradientBottom); + break; + } - switch (s_state) - { - case OptionsMenuState::Closing: - { - s_commonMenu.Draw(); + case OptionsMenuState::Restarting: + { + static int s_restartMessageResult = -1; - if (s_commonMenu.Close(s_isPause)) - s_isVisible = false; + if (MessageWindow::Open(Localise("Options_Message_Restart"), &s_restartMessageResult) == MSG_CLOSED) + Fader::FadeOut(1, []() { App::Restart(); }); - break; + break; + } } - default: + switch (s_flowState) { - if (s_state == OptionsMenuState::Opening) + case OptionsMenuFlowState::CategoryCursor: { - s_commonMenu.Open(); - s_state = OptionsMenuState::Idle; - } + auto categoryCount = (int)OptionsMenuCategory::Count; - switch (s_flowState) - { - case OptionsMenuFlowState::CategoryCursor: + // Remove codes category from cursor select. + if (!s_isCodesUnlocked) + categoryCount -= 1; + + MoveCursor(g_categoryIndex, g_flowStateTime, 0, categoryCount, []() { - auto categoryCount = (int)OptionsMenuCategory::Count; + ResetOptionSelection(); - // Remove codes category from cursor select. - if (!s_isCodesUnlocked) - categoryCount -= 1; + g_cursorArrowsTime = ImGui::GetTime(); + s_commonMenu.SetDescription(GetCategoryDescription((OptionsMenuCategory)g_categoryIndex)); + }); - MoveCursor(g_categoryIndex, g_flowStateTime, 0, categoryCount, []() - { - ResetOptionSelection(); + if (CheckAndDiscard(g_isAccepted)) + { + Game_PlaySound("main_deside"); - g_cursorArrowsTime = ImGui::GetTime(); - s_commonMenu.SetDescription(GetCategoryDescription((OptionsMenuCategory)g_categoryIndex)); - }); + s_flowState = OptionsMenuFlowState::OptionCursor; + g_flowStateTime = ImGui::GetTime(); + g_cursorArrowsTime = g_flowStateTime; + g_scrollArrowsTime = g_flowStateTime; + } - if (CheckAndDiscard(g_isAccepted)) - { - Game_PlaySound("main_deside"); + if (CheckAndDiscard(g_isDeclined)) + { + Game_PlaySound("window_close"); - s_flowState = OptionsMenuFlowState::OptionCursor; - g_flowStateTime = ImGui::GetTime(); - g_cursorArrowsTime = g_flowStateTime; - g_scrollArrowsTime = g_flowStateTime; + if (s_isRestartRequired) + { + s_state = OptionsMenuState::Restarting; } - - if (CheckAndDiscard(g_isDeclined)) + else + { Close(); - - break; + } } - case OptionsMenuFlowState::OptionCursor: + break; + } + + case OptionsMenuFlowState::OptionCursor: + { + MoveCursor(g_optionIndex, g_flowStateTime, 0, g_optionCount, []() { - MoveCursor(g_optionIndex, g_flowStateTime, 0, g_optionCount, []() - { - g_cursorArrowsTime = ImGui::GetTime(); - }); + g_cursorArrowsTime = ImGui::GetTime(); + }); - if (CheckAndDiscard(g_isDeclined)) - { - Game_PlaySound("window_close"); + if (CheckAndDiscard(g_isDeclined)) + { + Game_PlaySound("window_close"); - s_flowState = OptionsMenuFlowState::CategoryCursor; - g_flowStateTime = ImGui::GetTime(); - g_cursorArrowsTime = g_flowStateTime; + s_flowState = OptionsMenuFlowState::CategoryCursor; + g_flowStateTime = ImGui::GetTime(); + g_cursorArrowsTime = g_flowStateTime; - s_commonMenu.SetDescription(GetCategoryDescription((OptionsMenuCategory)g_categoryIndex)); - } - - break; + s_commonMenu.SetDescription(GetCategoryDescription((OptionsMenuCategory)g_categoryIndex)); } - } - - DrawArrows({ 0, res.y / 2 - Scale(10) }, res); - DrawCategories(min, max); - DrawContainer(min, max); - - s_commonMenu.Draw(); - break; + break; + } } + + DrawArrows({ 0, res.y / 2 - Scale(10) }, res); + DrawCategories(min, max); + DrawContainer(min, max); + + s_commonMenu.Draw(); + + g_up = false; + g_upWasHeld = upIsHeld; + g_down = false; + g_downWasHeld = downIsHeld; + g_left = false; + g_leftWasHeld = leftIsHeld; + g_right = false; + g_rightWasHeld = rightIsHeld; + g_isAccepted = false; + g_isDeclined = false; + g_isReset = false; } - g_up = false; - g_upWasHeld = upIsHeld; - g_down = false; - g_downWasHeld = downIsHeld; - g_left = false; - g_leftWasHeld = leftIsHeld; - g_right = false; - g_rightWasHeld = rightIsHeld; - g_isAccepted = false; - g_isDeclined = false; - g_isReset = false; + s_isRestartRequired = Config::Language != App::s_language || Config::ChannelConfiguration != g_optionCurrentChannelConfiguration; } void OptionsMenu::Init() @@ -907,6 +932,7 @@ void OptionsMenu::Open(bool isPause) s_state = OptionsMenuState::Opening; s_isVisible = true; s_isPause = isPause; + g_optionCurrentChannelConfiguration = Config::ChannelConfiguration; ResetSelection(); } @@ -920,7 +946,6 @@ void OptionsMenu::Close() g_stateTime = ImGui::GetTime(); g_cursorArrowsTime = g_stateTime; - Game_PlaySound("window_close"); Config::Save(); } diff --git a/MarathonRecomp/ui/options_menu.h b/MarathonRecomp/ui/options_menu.h index 0e57df4de..c253100f5 100644 --- a/MarathonRecomp/ui/options_menu.h +++ b/MarathonRecomp/ui/options_menu.h @@ -6,7 +6,8 @@ enum class OptionsMenuState { Opening, Idle, - Closing + Closing, + Restarting }; enum class OptionsMenuFlowState From 33d2373ca761f57c7f2404f2deadedcd818247b8 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Wed, 8 Oct 2025 01:35:54 +0100 Subject: [PATCH 08/86] Implemented intro and outro animations for controls --- .../patches/MainMenuTask_patches.cpp | 2 + MarathonRecomp/ui/options_menu.cpp | 290 +++++++++--------- MarathonRecomp/ui/options_menu.h | 2 + MarathonRecomp/user/config.cpp | 1 + 4 files changed, 154 insertions(+), 141 deletions(-) diff --git a/MarathonRecomp/patches/MainMenuTask_patches.cpp b/MarathonRecomp/patches/MainMenuTask_patches.cpp index f301c0c88..ba3527f70 100644 --- a/MarathonRecomp/patches/MainMenuTask_patches.cpp +++ b/MarathonRecomp/patches/MainMenuTask_patches.cpp @@ -8,6 +8,7 @@ PPC_FUNC(sub_824FFCF8) { auto pMainMenuTask = (Sonicteam::MainMenuTask*)(base + ctx.r3.u32); +#ifdef MARATHON_RECOMP_OPTIONS_MENU if (pMainMenuTask->m_State == Sonicteam::MainMenuTask::MainMenuState_MainMenu && pMainMenuTask->m_SelectedIndex == 3) { if (!OptionsMenu::s_isVisible && (pMainMenuTask->m_PressedButtons.get() & 0x10) != 0) @@ -20,6 +21,7 @@ PPC_FUNC(sub_824FFCF8) pMainMenuTask->m_PressedButtons = 0; } } +#endif if (OptionsMenu::s_isVisible) { diff --git a/MarathonRecomp/ui/options_menu.cpp b/MarathonRecomp/ui/options_menu.cpp index 9c8ef71ac..89b7ec6f4 100644 --- a/MarathonRecomp/ui/options_menu.cpp +++ b/MarathonRecomp/ui/options_menu.cpp @@ -14,16 +14,9 @@ #include #include -// #define USE_REFERENCE_PARAMETERS - -#ifdef USE_REFERENCE_PARAMETERS -static constexpr double CTRL_OFFSET_X = 5.0; -#else -static constexpr double CTRL_OFFSET_X = 30.0; -#endif - static double g_stateTime{}; static double g_flowStateTime{}; +static double g_categoryTime{}; static double g_cursorArrowsTime{}; static double g_scrollArrowsTime{}; @@ -167,10 +160,10 @@ static void DrawCategories(ImVec2 min, ImVec2 max) ImVec2 categoryMin = { min.x + Scale(42, true), min.y + Scale(147, true) + categoryOffsetY }; ImVec2 categoryMax = { categoryMin.x + categoryWidth, categoryMin.y + categoryHeight }; - auto categoryAlphaMotionTime = ComputeMotion(g_stateTime, 0, 5); - auto categoryAlphaMotion = IM_COL32(255, 255, 255, 255 * categoryAlphaMotionTime); + auto categoryMotionTime = ComputeMotion(g_stateTime, 0, 5, OptionsMenu::s_state != OptionsMenuState::Idle); + auto categoryMotion = IM_COL32(255, 255, 255, 255 * categoryMotionTime); - SetHorizontalGradient(categoryMin, categoryMax, categoryAlphaMotion, IM_COL32_WHITE_TRANS); + SetHorizontalGradient(categoryMin, categoryMax, categoryMotion, IM_COL32_WHITE_TRANS); drawList->AddImage(g_upTexMainMenu7.get(), categoryMin, categoryMax, GET_UV_COORDS(isCurrent ? selectedUVs : unselectedUVs)); ResetGradient(); @@ -187,7 +180,7 @@ static void DrawCategories(ImVec2 min, ImVec2 max) auto textSize = g_pFntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, text.c_str()); SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); - drawList->AddText(g_pFntRodin, fontSize, { categoryMin.x + Scale(129, true), categoryMin.y + Scale(6, true) }, categoryAlphaMotion, text.c_str()); + drawList->AddText(g_pFntRodin, fontSize, { categoryMin.x + Scale(129, true), categoryMin.y + Scale(6, true) }, categoryMotion, text.c_str()); SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); } } @@ -200,7 +193,8 @@ static void DrawOption(int rowIndex, ConfigDef* config, bool isAcce auto clipRectMax = drawList->GetClipRectMax(); auto fontSize = Scale(27, true); - auto accessibleColour = isAccessible ? IM_COL32_WHITE : IM_COL32(137, 137, 137, 255); + auto optionMotionTime = ComputeMotion(g_categoryTime, 0, 5, OptionsMenu::s_state != OptionsMenuState::Idle); + auto optionColourMotion = ColourLerp(IM_COL32_WHITE_TRANS, isAccessible ? IM_COL32_WHITE : IM_COL32(137, 137, 137, 255), optionMotionTime); auto optionHeight = Scale(106, true); auto offsetScroll = 0.0f; @@ -228,8 +222,8 @@ static void DrawOption(int rowIndex, ConfigDef* config, bool isAcce auto bgWidth = Scale(50, true); auto bgHeight = Scale(45, true); auto bgColourMotion = isSelected ? 255 : 0; - auto bgColour = IM_COL32(bgColourMotion, bgColourMotion, bgColourMotion, 245); - auto bgFadeColour = IM_COL32(bgColourMotion, bgColourMotion, bgColourMotion, 45); + auto bgColour = IM_COL32(bgColourMotion, bgColourMotion, bgColourMotion, 245 * optionMotionTime); + auto bgFadeColour = IM_COL32(bgColourMotion, bgColourMotion, bgColourMotion, 45 * optionMotionTime); ImVec2 titleBgEdgeMin = { clipRectMin.x, clipRectMin.y + offsetY }; ImVec2 titleBgEdgeMax = { titleBgEdgeMin.x + bgWidth, titleBgEdgeMin.y + bgHeight }; @@ -247,7 +241,7 @@ static void DrawOption(int rowIndex, ConfigDef* config, bool isAcce ImVec2 titlePos = { titleBgEdgeMin.x + Scale(51, true), titleBgEdgeMin.y + Scale(8, true) }; SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); - drawList->AddText(g_pFntRodin, fontSize, titlePos, accessibleColour, config->GetNameLocalised(Config::Language).c_str()); + drawList->AddText(g_pFntRodin, fontSize, titlePos, optionColourMotion, config->GetNameLocalised(Config::Language).c_str()); SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); if (isCurrent) @@ -257,7 +251,7 @@ static void DrawOption(int rowIndex, ConfigDef* config, bool isAcce if (config != g_optionCurrent) OptionsMenu::s_commonMenu.SetDescription(isAccessible ? config->GetDescription(Config::Language) : *inaccessibleReason); - DrawArrowCursor({ titleBgEdgeMin.x + Scale(8, true), titleBgEdgeMin.y + Scale(9, true)}, g_cursorArrowsTime, true, false, OptionsMenu::s_state != OptionsMenuState::Idle); + DrawArrowCursor({ titleBgEdgeMin.x + Scale(8, true), titleBgEdgeMin.y + Scale(9, true) }, g_cursorArrowsTime, true, false, OptionsMenu::s_state != OptionsMenuState::Idle); g_optionCurrent = config; } @@ -267,7 +261,10 @@ static void DrawOption(int rowIndex, ConfigDef* config, bool isAcce } } - ImVec2 ctrlBgLeftEdgeMin = { titleBgEdgeMin.x + Scale(CTRL_OFFSET_X, true), titleBgEdgeMin.y + Scale(54, true)}; + auto ctrlBgOffsetXMotion = Lerp(Scale(30, true), Scale(5, true), optionMotionTime); + auto ctrlBgColourMotion = ColourLerp(IM_COL32_WHITE_TRANS, optionColourMotion, optionMotionTime); + + ImVec2 ctrlBgLeftEdgeMin = { titleBgEdgeMin.x + ctrlBgOffsetXMotion, titleBgEdgeMin.y + Scale(54, true) }; ImVec2 ctrlBgLeftEdgeMax = { ctrlBgLeftEdgeMin.x + bgWidth, ctrlBgLeftEdgeMin.y + bgHeight }; ImVec2 ctrlBgStretchMin = { ctrlBgLeftEdgeMax.x, ctrlBgLeftEdgeMin.y }; ImVec2 ctrlBgStretchMax = { ctrlBgStretchMin.x + Scale(300, true), ctrlBgLeftEdgeMax.y }; @@ -280,9 +277,9 @@ static void DrawOption(int rowIndex, ConfigDef* config, bool isAcce ctrlBgLeftEdgeMin.y + ((ctrlBgLeftEdgeMax.y - ctrlBgLeftEdgeMin.y) / 2) }; - drawList->AddImage(g_upTexMainMenu8.get(), ctrlBgLeftEdgeMin, ctrlBgLeftEdgeMax, GET_UV_COORDS(bgEdgeUVs), accessibleColour); - drawList->AddImage(g_upTexMainMenu8.get(), ctrlBgStretchMin, ctrlBgStretchMax, GET_UV_COORDS(bgStretchUVs), accessibleColour); - AddImageFlipped(g_upTexMainMenu8.get(), ctrlBgRightEdgeMin, ctrlBgRightEdgeMax, GET_UV_COORDS(bgEdgeUVs), accessibleColour, true); + drawList->AddImage(g_upTexMainMenu8.get(), ctrlBgLeftEdgeMin, ctrlBgLeftEdgeMax, GET_UV_COORDS(bgEdgeUVs), ctrlBgColourMotion); + drawList->AddImage(g_upTexMainMenu8.get(), ctrlBgStretchMin, ctrlBgStretchMax, GET_UV_COORDS(bgStretchUVs), ctrlBgColourMotion); + AddImageFlipped(g_upTexMainMenu8.get(), ctrlBgRightEdgeMin, ctrlBgRightEdgeMax, GET_UV_COORDS(bgEdgeUVs), ctrlBgColourMotion, true); constexpr auto isSliderType = std::is_same_v || std::is_same_v; @@ -341,7 +338,7 @@ static void DrawOption(int rowIndex, ConfigDef* config, bool isAcce ImVec2 handleMin = { handleOffsetX, gaugeMin.y - (handleHeight / 2) + Scale(4.5, true) }; ImVec2 handleMax = { handleMin.x + handleWidth, handleMin.y + handleHeight }; - drawList->AddImage(g_upTexMainMenu8.get(), handleMin, handleMax, GET_UV_COORDS(handleUVs), accessibleColour); + drawList->AddImage(g_upTexMainMenu8.get(), handleMin, handleMax, GET_UV_COORDS(handleUVs), optionColourMotion); } else { @@ -576,7 +573,7 @@ static void DrawOption(int rowIndex, ConfigDef* config, bool isAcce valuePos.x = ctrlBgRightEdgeMax.x + Scale(10, true); SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); - drawList->AddText(g_pFntRodin, fontSize, valuePos, accessibleColour, valueText.data()); + drawList->AddText(g_pFntRodin, fontSize, valuePos, optionColourMotion, valueText.data()); SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); } @@ -719,7 +716,7 @@ static void DrawContainer(ImVec2 min, ImVec2 max) ImVec2 containerMin = { max.x - containerWidth - containerOffsetX, min.y + containerOffsetY }; ImVec2 containerMax = { containerMin.x + containerWidth, containerMin.y + containerHeight }; - auto containerAlphaMotionTime = ComputeMotion(g_stateTime, 0, 5); + auto containerAlphaMotionTime = ComputeMotion(g_stateTime, 0, 5, OptionsMenu::s_state != OptionsMenuState::Idle); auto containerAlphaMotion = IM_COL32(255, 255, 255, 255 * containerAlphaMotionTime); SetGradient(containerMin, containerMax, containerAlphaMotion, containerAlphaMotion, IM_COL32_WHITE_TRANS, IM_COL32(255, 255, 255, 20 * containerAlphaMotionTime)); @@ -752,167 +749,177 @@ void OptionsMenu::Draw() drawList->AddRectFilledMultiColor({ 0.0f, g_letterboxHeight }, { res.x, res.y - g_letterboxHeight }, gradientTop, gradientTop, gradientBottom, gradientBottom); - if (s_state == OptionsMenuState::Closing) - { - s_commonMenu.Draw(); + auto upIsHeld = false; + auto downIsHeld = false; + auto leftIsHeld = false; + auto rightIsHeld = false; - if (s_commonMenu.Close(s_isPause)) - s_isVisible = false; - } - else + switch (s_state) { - auto upIsHeld = false; - auto downIsHeld = false; - auto leftIsHeld = false; - auto rightIsHeld = false; + case OptionsMenuState::Opening: + s_commonMenu.Open(); + s_state = OptionsMenuState::Idle; + break; - switch (s_state) + case OptionsMenuState::Idle: { - case OptionsMenuState::Opening: - s_commonMenu.Open(); - s_state = OptionsMenuState::Idle; - break; - - case OptionsMenuState::Idle: + for (auto& spInputManager : App::s_pApp->m_pDoc->m_vspInputManager) { - for (auto& spInputManager : App::s_pApp->m_pDoc->m_vspInputManager) - { - auto& rPadState = spInputManager->m_PadState; - - if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DpadUp) || -rPadState.LeftStickVertical > 0.5f) - upIsHeld = true; + auto& rPadState = spInputManager->m_PadState; - if (!g_upWasHeld && upIsHeld) - g_up = true; + if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DpadUp) || -rPadState.LeftStickVertical > 0.5f) + upIsHeld = true; - if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DpadDown) || -rPadState.LeftStickVertical < -0.5f) - downIsHeld = true; + if (!g_upWasHeld && upIsHeld) + g_up = true; - if (!g_downWasHeld && downIsHeld) - g_down = true; + if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DpadDown) || -rPadState.LeftStickVertical < -0.5f) + downIsHeld = true; - if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DpadLeft) || -rPadState.LeftStickHorizontal > 0.5f) - leftIsHeld = true; + if (!g_downWasHeld && downIsHeld) + g_down = true; - if (!g_leftWasHeld && leftIsHeld) - g_left = true; + if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DpadLeft) || -rPadState.LeftStickHorizontal > 0.5f) + leftIsHeld = true; - if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DpadRight) || -rPadState.LeftStickHorizontal < -0.5f) - rightIsHeld = true; + if (!g_leftWasHeld && leftIsHeld) + g_left = true; - if (!g_rightWasHeld && rightIsHeld) - g_right = true; + if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DpadRight) || -rPadState.LeftStickHorizontal < -0.5f) + rightIsHeld = true; - if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_A)) - g_isAccepted = true; + if (!g_rightWasHeld && rightIsHeld) + g_right = true; - if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_B)) - g_isDeclined = true; + if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_A)) + g_isAccepted = true; - if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_X)) - g_isReset = true; - } + if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_B)) + g_isDeclined = true; - break; + if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_X)) + g_isReset = true; } - case OptionsMenuState::Restarting: - { - static int s_restartMessageResult = -1; + break; + } - if (MessageWindow::Open(Localise("Options_Message_Restart"), &s_restartMessageResult) == MSG_CLOSED) - Fader::FadeOut(1, []() { App::Restart(); }); + case OptionsMenuState::Closing: + { + if (s_commonMenu.Close()) + s_isVisible = false; - break; - } + break; } - switch (s_flowState) + case OptionsMenuState::Restarting: { - case OptionsMenuFlowState::CategoryCursor: + static bool s_restartPromptOpened = false; + static int s_restartMessageResult = -1; + + if (!s_restartPromptOpened) { - auto categoryCount = (int)OptionsMenuCategory::Count; + // Fade out description. + s_commonMenu.SetDescription(" "); + s_restartPromptOpened = true; + } - // Remove codes category from cursor select. - if (!s_isCodesUnlocked) - categoryCount -= 1; + if (MessageWindow::Open(Localise("Options_Message_Restart"), &s_restartMessageResult) == MSG_CLOSED) + Fader::FadeOut(1, []() { App::Restart(); }); - MoveCursor(g_categoryIndex, g_flowStateTime, 0, categoryCount, []() - { - ResetOptionSelection(); + break; + } + } - g_cursorArrowsTime = ImGui::GetTime(); - s_commonMenu.SetDescription(GetCategoryDescription((OptionsMenuCategory)g_categoryIndex)); - }); + switch (s_flowState) + { + case OptionsMenuFlowState::CategoryCursor: + { + auto categoryCount = (int)OptionsMenuCategory::Count; - if (CheckAndDiscard(g_isAccepted)) - { - Game_PlaySound("main_deside"); + // Remove codes category from cursor select. + if (!s_isCodesUnlocked) + categoryCount -= 1; - s_flowState = OptionsMenuFlowState::OptionCursor; - g_flowStateTime = ImGui::GetTime(); - g_cursorArrowsTime = g_flowStateTime; - g_scrollArrowsTime = g_flowStateTime; - } + MoveCursor(g_categoryIndex, g_flowStateTime, 0, categoryCount, []() + { + ResetOptionSelection(); - if (CheckAndDiscard(g_isDeclined)) - { - Game_PlaySound("window_close"); + g_categoryTime = ImGui::GetTime(); + g_cursorArrowsTime = g_categoryTime; + s_commonMenu.SetDescription(GetCategoryDescription((OptionsMenuCategory)g_categoryIndex)); + }); - if (s_isRestartRequired) - { - s_state = OptionsMenuState::Restarting; - } - else - { - Close(); - } - } + if (CheckAndDiscard(g_isAccepted)) + { + Game_PlaySound("main_deside"); - break; + s_flowState = OptionsMenuFlowState::OptionCursor; + g_flowStateTime = ImGui::GetTime(); + g_cursorArrowsTime = g_flowStateTime; + g_scrollArrowsTime = g_flowStateTime; } - case OptionsMenuFlowState::OptionCursor: + if (CheckAndDiscard(g_isDeclined)) { - MoveCursor(g_optionIndex, g_flowStateTime, 0, g_optionCount, []() - { - g_cursorArrowsTime = ImGui::GetTime(); - }); + Game_PlaySound("window_close"); + + g_stateTime = ImGui::GetTime(); + g_categoryTime = g_stateTime; - if (CheckAndDiscard(g_isDeclined)) + if (s_isRestartRequired) + { + s_state = OptionsMenuState::Restarting; + } + else { - Game_PlaySound("window_close"); + Close(); + } + } - s_flowState = OptionsMenuFlowState::CategoryCursor; - g_flowStateTime = ImGui::GetTime(); - g_cursorArrowsTime = g_flowStateTime; + break; + } - s_commonMenu.SetDescription(GetCategoryDescription((OptionsMenuCategory)g_categoryIndex)); - } + case OptionsMenuFlowState::OptionCursor: + { + MoveCursor(g_optionIndex, g_flowStateTime, 0, g_optionCount, []() + { + g_cursorArrowsTime = ImGui::GetTime(); + }); + + if (CheckAndDiscard(g_isDeclined)) + { + Game_PlaySound("window_close"); - break; + s_flowState = OptionsMenuFlowState::CategoryCursor; + g_flowStateTime = ImGui::GetTime(); + g_cursorArrowsTime = g_flowStateTime; + + s_commonMenu.SetDescription(GetCategoryDescription((OptionsMenuCategory)g_categoryIndex)); } - } - DrawArrows({ 0, res.y / 2 - Scale(10) }, res); - DrawCategories(min, max); - DrawContainer(min, max); - - s_commonMenu.Draw(); - - g_up = false; - g_upWasHeld = upIsHeld; - g_down = false; - g_downWasHeld = downIsHeld; - g_left = false; - g_leftWasHeld = leftIsHeld; - g_right = false; - g_rightWasHeld = rightIsHeld; - g_isAccepted = false; - g_isDeclined = false; - g_isReset = false; + break; + } } + DrawArrows({ 0, res.y / 2 - Scale(10) }, res); + DrawCategories(min, max); + DrawContainer(min, max); + + s_commonMenu.Draw(); + + g_up = false; + g_upWasHeld = upIsHeld; + g_down = false; + g_downWasHeld = downIsHeld; + g_left = false; + g_leftWasHeld = leftIsHeld; + g_right = false; + g_rightWasHeld = rightIsHeld; + g_isAccepted = false; + g_isDeclined = false; + g_isReset = false; + s_isRestartRequired = Config::Language != App::s_language || Config::ChannelConfiguration != g_optionCurrentChannelConfiguration; } @@ -927,6 +934,7 @@ void OptionsMenu::Open(bool isPause) { s_commonMenu = CommonMenu(Localise("Options_Header_Name"), "", isPause); g_stateTime = ImGui::GetTime(); + g_categoryTime = g_stateTime; g_cursorArrowsTime = g_stateTime; s_state = OptionsMenuState::Opening; diff --git a/MarathonRecomp/ui/options_menu.h b/MarathonRecomp/ui/options_menu.h index c253100f5..aceeffed4 100644 --- a/MarathonRecomp/ui/options_menu.h +++ b/MarathonRecomp/ui/options_menu.h @@ -2,6 +2,8 @@ #include +#define MARATHON_RECOMP_OPTIONS_MENU + enum class OptionsMenuState { Opening, diff --git a/MarathonRecomp/user/config.cpp b/MarathonRecomp/user/config.cpp index ca1a4aaa3..83a202060 100644 --- a/MarathonRecomp/user/config.cpp +++ b/MarathonRecomp/user/config.cpp @@ -745,6 +745,7 @@ void Config::CreateCallbacks() if (!App::s_isInit) return; + OptionsMenu::s_commonMenu.SetTitle(Localise("Options_Header_Name")); OptionsMenu::s_commonMenu.SetDescription(def->GetDescription(def->Value)); }; From 578c63f8f433a1e934aefbd8d92c3750a926c784 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Wed, 8 Oct 2025 08:27:16 +0100 Subject: [PATCH 09/86] Implemented button guide and reset button --- MarathonRecomp/CMakeLists.txt | 1 + MarathonRecomp/locale/locale.cpp | 22 +- .../patches/aspect_ratio_patches.cpp | 9 - MarathonRecomp/patches/aspect_ratio_patches.h | 2 - MarathonRecomp/ui/achievement_menu.cpp | 2 +- MarathonRecomp/ui/black_bar.cpp | 8 +- MarathonRecomp/ui/button_guide.cpp | 358 +++++++----------- MarathonRecomp/ui/button_guide.h | 33 +- MarathonRecomp/ui/common_menu.cpp | 4 +- MarathonRecomp/ui/installer_wizard.cpp | 123 +++--- MarathonRecomp/ui/options_menu.cpp | 24 +- 11 files changed, 233 insertions(+), 353 deletions(-) diff --git a/MarathonRecomp/CMakeLists.txt b/MarathonRecomp/CMakeLists.txt index 089f40d4b..861d75d8a 100644 --- a/MarathonRecomp/CMakeLists.txt +++ b/MarathonRecomp/CMakeLists.txt @@ -540,6 +540,7 @@ BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/bc_diff/bu BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/font/im_font_atlas.bin" DEST_FILE "${RESOURCES_OUTPUT_PATH}/font/im_font_atlas.bin" ARRAY_NAME "g_im_font_atlas" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/font/im_font_atlas.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/font/im_font_atlas.dds" ARRAY_NAME "g_im_font_atlas_texture" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/achievements_menu/trophy.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/achievements_menu/trophy.dds" ARRAY_NAME "g_trophy" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/button_window.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/button_window.dds" ARRAY_NAME "g_button_window" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/controller.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/controller.dds" ARRAY_NAME "g_controller" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/general_window.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/general_window.dds" ARRAY_NAME "g_general_window" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/sonicnext-dev.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/sonicnext-dev.dds" ARRAY_NAME "g_sonicnextdev" COMPRESSION_TYPE "zstd") diff --git a/MarathonRecomp/locale/locale.cpp b/MarathonRecomp/locale/locale.cpp index ded592c81..058cb890e 100644 --- a/MarathonRecomp/locale/locale.cpp +++ b/MarathonRecomp/locale/locale.cpp @@ -47,7 +47,7 @@ std::unordered_map> "Options_Header_Name", { { ELanguage::English, "OPTIONS" }, - { ELanguage::Japanese, "OPTION" }, + { ELanguage::Japanese, "OPTIONS" }, { ELanguage::German, "OPTIONEN" }, { ELanguage::French, "OPTIONS" }, { ELanguage::Spanish, "OPCIONES" }, @@ -786,9 +786,9 @@ std::unordered_map> { { ELanguage::English, "Select" }, { ELanguage::Japanese, "決定" }, - { ELanguage::German, "Auswählen" }, - { ELanguage::French, "Choisir" }, - { ELanguage::Spanish, "Seleccionar" }, + { ELanguage::German, "Auswählen " }, + { ELanguage::French, "Sélectionner" }, + { ELanguage::Spanish, " Seleccionar" }, { ELanguage::Italian, "Seleziona" } } }, @@ -796,10 +796,10 @@ std::unordered_map> "Common_Back", { { ELanguage::English, "Back" }, - { ELanguage::Japanese, "もどる" }, + { ELanguage::Japanese, "戻る" }, { ELanguage::German, "Zurück" }, { ELanguage::French, "Retour" }, - { ELanguage::Spanish, "Atrás" }, + { ELanguage::Spanish, " Atrás" }, { ELanguage::Italian, "Indietro" } } }, @@ -807,11 +807,11 @@ std::unordered_map> "Common_Quit", { { ELanguage::English, "Quit" }, - { ELanguage::Japanese, "終了" }, - { ELanguage::German, "Verlassen" }, - { ELanguage::French, "Quitter" }, - { ELanguage::Spanish, "Salir" }, - { ELanguage::Italian, "Esci" } + { ELanguage::Japanese, "やめる" }, + { ELanguage::German, "Abbrechen" }, + { ELanguage::French, "Annuler" }, + { ELanguage::Spanish, " Cancelar" }, + { ELanguage::Italian, "Annulla" } } }, { diff --git a/MarathonRecomp/patches/aspect_ratio_patches.cpp b/MarathonRecomp/patches/aspect_ratio_patches.cpp index 682e090f1..7aad517d6 100644 --- a/MarathonRecomp/patches/aspect_ratio_patches.cpp +++ b/MarathonRecomp/patches/aspect_ratio_patches.cpp @@ -181,15 +181,6 @@ void AspectRatioPatches::ComputeOffsets() g_aspectRatioNarrowScale = std::clamp((g_aspectRatio - NARROW_ASPECT_RATIO) / (WIDE_ASPECT_RATIO - NARROW_ASPECT_RATIO), 0.0f, 1.0f); g_radarMapScale = 256 * g_aspectRatioScale * g_aspectRatioGameplayScale; - - if (g_aspectRatio > WIDE_ASPECT_RATIO) - { - g_pillarboxWidth = (width - (height * 16.0f / 9.0f)) / 2.0f; - } - else if (WIDE_ASPECT_RATIO > g_aspectRatio) - { - g_letterboxHeight = (height - (width * 9.0f / 16.0f)) / 2.0f; - } } void EmplacePath(const void* key, const std::string_view& value) diff --git a/MarathonRecomp/patches/aspect_ratio_patches.h b/MarathonRecomp/patches/aspect_ratio_patches.h index 02ed17ce9..5dd140369 100644 --- a/MarathonRecomp/patches/aspect_ratio_patches.h +++ b/MarathonRecomp/patches/aspect_ratio_patches.h @@ -18,8 +18,6 @@ inline float g_aspectRatioScale; inline float g_aspectRatioGameplayScale; inline float g_aspectRatioNarrowScale; inline float g_radarMapScale; -inline float g_pillarboxWidth; -inline float g_letterboxHeight; class AspectRatioPatches { diff --git a/MarathonRecomp/ui/achievement_menu.cpp b/MarathonRecomp/ui/achievement_menu.cpp index 72622080b..56a5eda28 100644 --- a/MarathonRecomp/ui/achievement_menu.cpp +++ b/MarathonRecomp/ui/achievement_menu.cpp @@ -761,7 +761,7 @@ void AchievementMenu::Open() return std::get<1>(a) > std::get<1>(b); }); - ButtonGuide::Open(Button("Common_Back", FLT_MAX, EButtonIcon::B, EFontQuality::Low)); + ButtonGuide::Open(Button("Common_Back", EButtonIcon::B)); ResetSelection(); Game_PlaySound("sys_actstg_pausewinopen"); diff --git a/MarathonRecomp/ui/black_bar.cpp b/MarathonRecomp/ui/black_bar.cpp index 0aafe31fd..88fbe0e45 100644 --- a/MarathonRecomp/ui/black_bar.cpp +++ b/MarathonRecomp/ui/black_bar.cpp @@ -11,13 +11,13 @@ void BlackBar::Draw() if (g_aspectRatio > WIDE_ASPECT_RATIO) { - drawList->AddRectFilled({ 0.0f, 0.0f }, { g_pillarboxWidth, res.y }, IM_COL32_BLACK); - drawList->AddRectFilled({ res.x - g_pillarboxWidth, 0.0f }, res, IM_COL32_BLACK); + drawList->AddRectFilled({ 0.0f, 0.0f }, { g_aspectRatioOffsetX, res.y }, IM_COL32_BLACK); + drawList->AddRectFilled({ res.x - g_aspectRatioOffsetX, 0.0f }, res, IM_COL32_BLACK); } else if (WIDE_ASPECT_RATIO > g_aspectRatio) { - drawList->AddRectFilled({ 0.0f, 0.0f }, { res.x, g_letterboxHeight }, IM_COL32_BLACK); - drawList->AddRectFilled({ 0.0f, res.y - g_letterboxHeight }, res, IM_COL32_BLACK); + drawList->AddRectFilled({ 0.0f, 0.0f }, { res.x, g_aspectRatioOffsetY }, IM_COL32_BLACK); + drawList->AddRectFilled({ 0.0f, res.y - g_aspectRatioOffsetY }, res, IM_COL32_BLACK); } } diff --git a/MarathonRecomp/ui/button_guide.cpp b/MarathonRecomp/ui/button_guide.cpp index cc1adb1ba..1dc869ec3 100644 --- a/MarathonRecomp/ui/button_guide.cpp +++ b/MarathonRecomp/ui/button_guide.cpp @@ -1,319 +1,215 @@ #include "button_guide.h" -#include "imgui_utils.h" #include #include #include #include +#include +#include +#include +#include #include #include #include -#include -#include - -constexpr float DEFAULT_SIDE_MARGINS = 379; - -ImFont* g_rodinFont; +static std::vector