From f547c7ca6d5867e7561da06fea38fed19f0e98cf Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:05:40 +0000 Subject: [PATCH] window: implemented monitor switching --- UnleashedRecomp/exports.cpp | 5 + UnleashedRecomp/exports.h | 1 + UnleashedRecomp/gpu/video.h | 2 + UnleashedRecomp/locale/locale.cpp | 12 ++ UnleashedRecomp/ui/options_menu.cpp | 172 ++++++++++++++++------------ UnleashedRecomp/ui/window.cpp | 32 +++--- UnleashedRecomp/ui/window.h | 85 +++++++++++++- UnleashedRecomp/user/config.h | 10 +- 8 files changed, 224 insertions(+), 95 deletions(-) diff --git a/UnleashedRecomp/exports.cpp b/UnleashedRecomp/exports.cpp index ee4eb0dd..84cfba20 100644 --- a/UnleashedRecomp/exports.cpp +++ b/UnleashedRecomp/exports.cpp @@ -29,6 +29,11 @@ SWA_API void Game_PlaySound(const char* pName) } } +SWA_API void Window_SetDisplay(int displayIndex) +{ + Window::SetDisplay(displayIndex); +} + SWA_API void Window_SetFullscreen(bool isEnabled) { Window::SetFullscreen(isEnabled); diff --git a/UnleashedRecomp/exports.h b/UnleashedRecomp/exports.h index 8963c767..71eec063 100644 --- a/UnleashedRecomp/exports.h +++ b/UnleashedRecomp/exports.h @@ -1,4 +1,5 @@ #pragma once SWA_API void Game_PlaySound(const char* pName); +SWA_API void Window_SetDisplay(int displayIndex); SWA_API void Window_SetFullscreen(bool isEnabled); diff --git a/UnleashedRecomp/gpu/video.h b/UnleashedRecomp/gpu/video.h index 135a7dfa..a42b61d0 100644 --- a/UnleashedRecomp/gpu/video.h +++ b/UnleashedRecomp/gpu/video.h @@ -390,6 +390,8 @@ enum GuestTextureAddress D3DTADDRESS_BORDER = 6 }; +extern bool g_needsResize; + extern std::unique_ptr LoadTexture(const uint8_t* data, size_t dataSize, RenderComponentMapping componentMapping = RenderComponentMapping()); extern void VideoConfigValueChangedCallback(class IConfigDef* config); diff --git a/UnleashedRecomp/locale/locale.cpp b/UnleashedRecomp/locale/locale.cpp index 31048047..bb29c80b 100644 --- a/UnleashedRecomp/locale/locale.cpp +++ b/UnleashedRecomp/locale/locale.cpp @@ -50,6 +50,18 @@ std::unordered_map> g_lo { ELanguage::English, "This option is not available at this location." } } }, + { + "Options_Desc_NotAvailableWindowed", + { + { ELanguage::English, "This option is not available in windowed mode." } + } + }, + { + "Options_Desc_NotAvailableHardware", + { + { ELanguage::English, "This option is not available with your current hardware configuration." } + } + }, { "Options_Desc_NotAvailableMSAA", { diff --git a/UnleashedRecomp/ui/options_menu.cpp b/UnleashedRecomp/ui/options_menu.cpp index d9153783..3b495f8c 100644 --- a/UnleashedRecomp/ui/options_menu.cpp +++ b/UnleashedRecomp/ui/options_menu.cpp @@ -424,7 +424,7 @@ static bool DrawCategories() 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)) + T valueMin = T(0), T valueCenter = T(0.5), T valueMax = T(1), bool isSlider = true) { auto drawList = ImGui::GetForegroundDrawList(); auto clipRectMin = drawList->GetClipRectMin(); @@ -522,6 +522,10 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf // TODO: check if value was changed? VideoConfigValueChangedCallback(config); + // TODO: check if value was changed? + if (config->Callback) + config->Callback(config); + Game_PlaySound("sys_worldmap_decide"); } } @@ -568,44 +572,47 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf 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 constexpr (std::is_same_v || std::is_same_v) + if (isSlider) { - // 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); + if constexpr (std::is_same_v || std::is_same_v) + { + // 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.0f); - float yPadding = Scale(3.0f); + float xPadding = Scale(6.0f); + float yPadding = Scale(3.0f); - drawList->AddRectFilledMultiColor - ( - { min.x + xPadding, min.y + yPadding }, - { max.x - xPadding, max.y - yPadding }, - innerColor0, - innerColor0, - innerColor1, - innerColor1 - ); + 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); + // 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(1.0f); - yPadding += Scale(1.0f); + xPadding += Scale(1.0f); + yPadding += Scale(1.0f); - ImVec2 sliderMin = { min.x + xPadding, min.y + yPadding }; - ImVec2 sliderMax = { max.x - xPadding, max.y - yPadding }; - float factor; + 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; + 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; - drawList->AddRectFilledMultiColor(sliderMin, sliderMax, sliderColor0, sliderColor0, sliderColor1, sliderColor1); + sliderMax.x = sliderMin.x + (sliderMax.x - sliderMin.x) * factor; + + drawList->AddRectFilledMultiColor(sliderMin, sliderMax, sliderColor0, sliderColor0, sliderColor1, sliderColor1); + } } SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); @@ -732,7 +739,7 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf config->Value = std::clamp(config->Value, valueMin, valueMax); } - if (config->Callback) + if ((increment || decrement) && config->Callback) config->Callback(config); } @@ -743,7 +750,10 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf } else if constexpr (std::is_same_v) { - valueText = config->Value >= valueMax ? Localise("Options_Value_Max") : fmt::format("{}", config->Value); + valueText = fmt::format("{}", config->Value); + + if (isSlider && config->Value >= valueMax) + valueText = Localise("Options_Value_Max"); } else { @@ -798,45 +808,61 @@ static void DrawConfigOptions() // 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::Hints, !isStage, cmnReason); - DrawConfigOption(rowCount++, yOffset, &Config::ControlTutorial, !isStage, cmnReason); - DrawConfigOption(rowCount++, yOffset, &Config::AchievementNotifications, true); - DrawConfigOption(rowCount++, yOffset, &Config::TimeOfDayTransition, true); - break; - case 1: // INPUT - DrawConfigOption(rowCount++, yOffset, &Config::InvertCameraX, true); - DrawConfigOption(rowCount++, yOffset, &Config::InvertCameraY, true); - DrawConfigOption(rowCount++, yOffset, &Config::AllowBackgroundInput, true); - DrawConfigOption(rowCount++, yOffset, &Config::AllowDPadMovement, true); - DrawConfigOption(rowCount++, yOffset, &Config::ControllerIcons, true); - break; - case 2: // AUDIO - DrawConfigOption(rowCount++, yOffset, &Config::MusicVolume, true); - DrawConfigOption(rowCount++, yOffset, &Config::EffectsVolume, true); - DrawConfigOption(rowCount++, yOffset, &Config::VoiceLanguage, OptionsMenu::s_pauseMenuType == SWA::eMenuType_WorldMap, cmnReason); - DrawConfigOption(rowCount++, yOffset, &Config::Subtitles, true); - DrawConfigOption(rowCount++, yOffset, &Config::MusicAttenuation, AudioPatches::CanAttenuate(), &Localise("Options_Desc_OSNotSupported")); - DrawConfigOption(rowCount++, yOffset, &Config::BattleTheme, true); - break; - case 3: // VIDEO - // TODO: expose WindowWidth/WindowHeight as WindowSize. - 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, 15, 120, 240); - 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::MovieScaleMode, true); - DrawConfigOption(rowCount++, yOffset, &Config::UIScaleMode, true); - break; + case 0: // SYSTEM + DrawConfigOption(rowCount++, yOffset, &Config::Language, !OptionsMenu::s_isPause, cmnReason); + 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, true); + break; + + case 1: // INPUT + DrawConfigOption(rowCount++, yOffset, &Config::InvertCameraX, true); + DrawConfigOption(rowCount++, yOffset, &Config::InvertCameraY, true); + DrawConfigOption(rowCount++, yOffset, &Config::AllowBackgroundInput, true); + DrawConfigOption(rowCount++, yOffset, &Config::AllowDPadMovement, true); + DrawConfigOption(rowCount++, yOffset, &Config::ControllerIcons, true); + break; + + case 2: // AUDIO + DrawConfigOption(rowCount++, yOffset, &Config::MusicVolume, true); + DrawConfigOption(rowCount++, yOffset, &Config::EffectsVolume, true); + DrawConfigOption(rowCount++, yOffset, &Config::VoiceLanguage, OptionsMenu::s_pauseMenuType == SWA::eMenuType_WorldMap, cmnReason); + DrawConfigOption(rowCount++, yOffset, &Config::Subtitles, true); + DrawConfigOption(rowCount++, yOffset, &Config::MusicAttenuation, AudioPatches::CanAttenuate(), &Localise("Options_Desc_OSNotSupported")); + DrawConfigOption(rowCount++, yOffset, &Config::BattleTheme, true); + break; + + case 3: // VIDEO + { + // TODO: expose WindowWidth/WindowHeight as WindowSize. + + auto displayCount = Window::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, 15, 120, 240); + 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::MovieScaleMode, true); + DrawConfigOption(rowCount++, yOffset, &Config::UIScaleMode, true); + + break; + } } auto inputState = SWA::CInputState::GetInstance(); @@ -948,7 +974,7 @@ static void DrawInfoPanel() auto clipRectMin = drawList->GetClipRectMin(); auto clipRectMax = drawList->GetClipRectMax(); - ImVec2 thumbnailMax = { clipRectMin.x + ScaleX(343.0f), clipRectMin.y + ScaleY(198.0f) }; + ImVec2 thumbnailMax = { clipRectMax.x, clipRectMin.y + Scale(198.0f) }; if (g_selectedItem) { diff --git a/UnleashedRecomp/ui/window.cpp b/UnleashedRecomp/ui/window.cpp index 767062b9..01cab078 100644 --- a/UnleashedRecomp/ui/window.cpp +++ b/UnleashedRecomp/ui/window.cpp @@ -3,6 +3,7 @@ #include #include #include +#include bool m_isFullscreenKeyReleased = true; bool m_isResizing = false; @@ -33,8 +34,14 @@ int Window_OnSDLEvent(void*, SDL_Event* event) Config::Fullscreen = Window::SetFullscreen(!Window::IsFullscreen()); - if (!Config::Fullscreen) + if (Config::Fullscreen) + { + Config::Monitor = Window::GetDisplay(); + } + else + { Config::WindowState = Window::SetMaximised(Config::WindowState == EWindowState::Maximised); + } // Block holding ALT+ENTER spamming window changes. m_isFullscreenKeyReleased = false; @@ -45,7 +52,7 @@ int Window_OnSDLEvent(void*, SDL_Event* event) // Restore original window dimensions on F2. case SDLK_F2: Config::Fullscreen = Window::SetFullscreen(false); - Window::SetDimensions(DEFAULT_WIDTH, DEFAULT_HEIGHT); + Window::ResetDimensions(); break; // Recentre window on F3. @@ -118,11 +125,9 @@ int Window_OnSDLEvent(void*, SDL_Event* event) } case SDL_USER_EVILSONIC: - { Window::s_isIconNight = event->user.code; Window::SetIcon(Window::s_isIconNight); break; - } } if (!Window::IsFullscreen()) @@ -158,23 +163,15 @@ void Window::Init() s_x = s_y = SDL_WINDOWPOS_CENTERED; if (!IsPositionValid()) - { - s_x = SDL_WINDOWPOS_CENTERED; - s_y = SDL_WINDOWPOS_CENTERED; - s_width = DEFAULT_WIDTH; - s_height = DEFAULT_HEIGHT; - - Config::WindowX = s_x; - Config::WindowY = s_y; - Config::WindowWidth = s_width; - Config::WindowHeight = s_height; - } + Window::ResetDimensions(); s_pWindow = SDL_CreateWindow("SWA", s_x, s_y, s_width, s_height, GetWindowFlags()); if (IsFullscreen()) SDL_ShowCursor(SDL_DISABLE); + SetDisplay(Config::Monitor); + SetIcon(); SetTitle(); SDL_SetWindowMinimumSize(s_pWindow, 640, 480); @@ -192,7 +189,7 @@ void Window::Init() void Window::Update() { - if (!Window::IsFullscreen() && !Window::IsMaximised()) + if (!Window::IsFullscreen() && !Window::IsMaximised() && !s_isChangingDisplay) { Config::WindowX = Window::s_x; Config::WindowY = Window::s_y; @@ -205,4 +202,7 @@ void Window::Update() SetTitle(); m_isResizing = false; } + + if (g_needsResize) + s_isChangingDisplay = false; } diff --git a/UnleashedRecomp/ui/window.h b/UnleashedRecomp/ui/window.h index 8b036a8f..3d980f4d 100644 --- a/UnleashedRecomp/ui/window.h +++ b/UnleashedRecomp/ui/window.h @@ -29,6 +29,7 @@ public: static inline bool s_isFocused; static inline bool s_isIconNight; static inline bool s_isFullscreenCursorVisible; + static inline bool s_isChangingDisplay; static SDL_Surface* GetIconSurface(void* pIconBmp, size_t iconSize) { @@ -107,7 +108,9 @@ public: { SDL_SetWindowFullscreen(s_pWindow, 0); SDL_ShowCursor(SDL_ENABLE); + SetIcon(Window::s_isIconNight); + SetDimensions(Config::WindowWidth, Config::WindowHeight, Config::WindowX, Config::WindowY); } return isEnabled; @@ -172,6 +175,19 @@ public: SDL_MoveEvent(s_pWindow, x, y); } + static void ResetDimensions() + { + s_x = SDL_WINDOWPOS_CENTERED; + s_y = SDL_WINDOWPOS_CENTERED; + s_width = DEFAULT_WIDTH; + s_height = DEFAULT_HEIGHT; + + Config::WindowX = s_x; + Config::WindowY = s_y; + Config::WindowWidth = s_width; + Config::WindowHeight = s_height; + } + static uint32_t GetWindowFlags() { uint32_t flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE; @@ -185,19 +201,78 @@ public: return flags; } - static bool IsPositionValid() + static int GetDisplayCount() { - auto displayCount = SDL_GetNumVideoDisplays(); + auto result = SDL_GetNumVideoDisplays(); - if (displayCount <= 0) + if (result < 0) { - LOGF_ERROR("Failed to validate window position: {}", SDL_GetError()); - return false; + LOGF_ERROR("Failed to get display count: {}", SDL_GetError()); + return 1; } + return result; + } + + static int GetDisplay() + { + auto displayCount = GetDisplayCount(); + for (int i = 0; i < displayCount; i++) { SDL_Rect bounds; + + if (SDL_GetDisplayBounds(i, &bounds) == 0) + { + auto x = s_x; + auto y = s_y; + + if (x == SDL_WINDOWPOS_CENTERED) + x = bounds.w / 2 - s_width / 2; + + if (y == SDL_WINDOWPOS_CENTERED) + y = bounds.h / 2 - s_height / 2; + + if (x >= bounds.x && x < bounds.x + bounds.w && + y >= bounds.y && y < bounds.y + bounds.h) + { + return i; + } + } + } + + return 0; + } + + static void SetDisplay(int displayIndex) + { + if (!IsFullscreen()) + return; + + s_isChangingDisplay = true; + + SDL_Rect bounds; + + if (SDL_GetDisplayBounds(displayIndex, &bounds) == 0) + { + SetFullscreen(false); + SetDimensions(bounds.w, bounds.h, bounds.x, bounds.y); + SetFullscreen(true); + } + else + { + ResetDimensions(); + } + } + + static bool IsPositionValid() + { + auto displayCount = GetDisplayCount(); + + for (int i = 0; i < displayCount; i++) + { + SDL_Rect bounds; + if (SDL_GetDisplayBounds(i, &bounds) == 0) { auto x = s_x; diff --git a/UnleashedRecomp/user/config.h b/UnleashedRecomp/user/config.h index d06fae84..668dcc78 100644 --- a/UnleashedRecomp/user/config.h +++ b/UnleashedRecomp/user/config.h @@ -34,7 +34,14 @@ public: 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", size_t, Monitor, 0); + + CONFIG_DEFINE_CALLBACK("Video", int32_t, Monitor, 0, + { + def->Locale = &g_Monitor_locale; + + Window_SetDisplay(def->Value); + }); + CONFIG_DEFINE_ENUM_LOCALISED("Video", EAspectRatio, AspectRatio, EAspectRatio::Auto); CONFIG_DEFINE_CALLBACK("Video", float, ResolutionScale, 1.0f, @@ -48,6 +55,7 @@ public: def->Locale = &g_Fullscreen_locale; Window_SetFullscreen(def->Value); + Window_SetDisplay(Monitor); }); CONFIG_DEFINE_LOCALISED("Video", bool, VSync, true);