diff --git a/UnleashedRecomp/locale/config_locale.cpp b/UnleashedRecomp/locale/config_locale.cpp index 5defd50..ed330b4 100644 --- a/UnleashedRecomp/locale/config_locale.cpp +++ b/UnleashedRecomp/locale/config_locale.cpp @@ -150,6 +150,11 @@ 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." } } }; +CONFIG_DEFINE_LOCALE(WindowSize) +{ + { ELanguage::English, { "Window Size", "Adjust the size of the game window in windowed mode." } } +}; + CONFIG_DEFINE_LOCALE(Monitor) { { ELanguage::English, { "Monitor", "Change which monitor to display the game on." } } diff --git a/UnleashedRecomp/locale/locale.cpp b/UnleashedRecomp/locale/locale.cpp index 9ad4ad1..6e80386 100644 --- a/UnleashedRecomp/locale/locale.cpp +++ b/UnleashedRecomp/locale/locale.cpp @@ -51,6 +51,12 @@ std::unordered_map> g_lo { ELanguage::English, "This option is not available at this location." } } }, + { + "Options_Desc_NotAvailableFullscreen", + { + { ELanguage::English, "This option is not available in fullscreen mode." } + } + }, { "Options_Desc_NotAvailableWindowed", { diff --git a/UnleashedRecomp/ui/achievement_menu.cpp b/UnleashedRecomp/ui/achievement_menu.cpp index d5c7b9d..fbf8557 100644 --- a/UnleashedRecomp/ui/achievement_menu.cpp +++ b/UnleashedRecomp/ui/achievement_menu.cpp @@ -80,7 +80,7 @@ static void DrawSelectionContainer(ImVec2 min, ImVec2 max) auto drawList = ImGui::GetForegroundDrawList(); static auto breatheStart = ImGui::GetTime(); - auto alpha = Lerp(1.0f, 0.75f, (sin((ImGui::GetTime() - breatheStart) * (2.0f * M_PI / (55.0f / 60.0f))) + 1.0f) / 2.0f); + auto alpha = Lerp(1.0f, 0.65f, (sin((ImGui::GetTime() - breatheStart) * (2.0f * M_PI / (55.0f / 60.0f))) + 1.0f) / 2.0f); auto colour = IM_COL32(255, 255, 255, 255 * alpha); auto commonWidth = Scale(11); diff --git a/UnleashedRecomp/ui/game_window.cpp b/UnleashedRecomp/ui/game_window.cpp index dd857f1..41e57b1 100644 --- a/UnleashedRecomp/ui/game_window.cpp +++ b/UnleashedRecomp/ui/game_window.cpp @@ -1,9 +1,18 @@ #include "game_window.h" -#include "sdl_listener.h" -#include -#include -#include #include +#include +#include +#include +#include +#include + +#if _WIN32 +#include +#pragma comment(lib, "dwmapi.lib") +#endif + +#include +#include bool m_isFullscreenKeyReleased = true; bool m_isResizing = false; @@ -110,6 +119,7 @@ int Window_OnSDLEvent(void*, SDL_Event* event) case SDL_WINDOWEVENT_RESIZED: m_isResizing = true; + Config::WindowSize = -1; GameWindow::s_width = event->window.data1; GameWindow::s_height = event->window.data2; GameWindow::SetTitle(fmt::format("{} - [{}x{}]", GameWindow::GetTitle(), GameWindow::s_width, GameWindow::s_height).c_str()); @@ -171,6 +181,20 @@ void GameWindow::Init(const char* sdlVideoDriver) SetProcessDPIAware(); #endif + Config::WindowSize.ApplyCallback = [](ConfigDef* def) + { + auto displayModes = GetDisplayModes(); + + // Use largest supported resolution if overflowed. + if (def->Value >= displayModes.size()) + def->Value = displayModes.size() - 1; + + auto& mode = displayModes[def->Value]; + auto centre = SDL_WINDOWPOS_CENTERED_DISPLAY(GetDisplay()); + + SetDimensions(mode.w, mode.h, centre, centre); + }; + s_x = Config::WindowX; s_y = Config::WindowY; s_width = Config::WindowWidth; @@ -188,10 +212,10 @@ void GameWindow::Init(const char* sdlVideoDriver) SDL_ShowCursor(SDL_DISABLE); SetDisplay(Config::Monitor); - SetIcon(); SetTitle(); - SDL_SetWindowMinimumSize(s_pWindow, 640, 480); + + SDL_SetWindowMinimumSize(s_pWindow, MIN_WIDTH, MIN_HEIGHT); SDL_SysWMinfo info; SDL_VERSION(&info.version); @@ -199,7 +223,6 @@ void GameWindow::Init(const char* sdlVideoDriver) #if defined(_WIN32) s_renderWindow = info.info.win.window; - SetDarkTitleBar(true); #elif defined(SDL_VULKAN_ENABLED) s_renderWindow = s_pWindow; #elif defined(__linux__) @@ -208,6 +231,8 @@ void GameWindow::Init(const char* sdlVideoDriver) static_assert(false, "Unknown platform."); #endif + SetDarkTitleBar(true); + SDL_ShowWindow(s_pWindow); } @@ -230,3 +255,315 @@ void GameWindow::Update() if (g_needsResize) s_isChangingDisplay = false; } + +SDL_Surface* GameWindow::GetIconSurface(void* pIconBmp, size_t iconSize) +{ + auto rw = SDL_RWFromMem(pIconBmp, iconSize); + auto surface = SDL_LoadBMP_RW(rw, 1); + + if (!surface) + LOGF_ERROR("Failed to load icon: {}", SDL_GetError()); + + return surface; +} + +void GameWindow::SetIcon(void* pIconBmp, size_t iconSize) +{ + if (auto icon = GetIconSurface(pIconBmp, iconSize)) + { + SDL_SetWindowIcon(s_pWindow, icon); + SDL_FreeSurface(icon); + } +} + +void GameWindow::SetIcon(bool isNight) +{ + if (isNight) + { + SetIcon(g_game_icon_night, sizeof(g_game_icon_night)); + } + else + { + SetIcon(g_game_icon, sizeof(g_game_icon)); + } +} + +const char* GameWindow::GetTitle() +{ + return Config::Language == ELanguage::Japanese + ? "SONIC WORLD ADVENTURE" + : "SONIC UNLEASHED"; +} + +void GameWindow::SetTitle(const char* title) +{ + SDL_SetWindowTitle(s_pWindow, title ? title : GetTitle()); +} + +void GameWindow::SetDarkTitleBar(bool isEnabled) +{ +#if _WIN32 + auto version = os::version::GetOSVersion(); + + if (version.Major < 10 || version.Build <= 17763) + return; + + auto flag = version.Build >= 18985 + ? DWMWA_USE_IMMERSIVE_DARK_MODE + : 19; // DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 + + const DWORD useImmersiveDarkMode = isEnabled; + DwmSetWindowAttribute(s_renderWindow, flag, &useImmersiveDarkMode, sizeof(useImmersiveDarkMode)); +#endif +} + +bool GameWindow::IsFullscreen() +{ + return SDL_GetWindowFlags(s_pWindow) & SDL_WINDOW_FULLSCREEN_DESKTOP; +} + +bool GameWindow::SetFullscreen(bool isEnabled) +{ + if (isEnabled) + { + SDL_SetWindowFullscreen(s_pWindow, SDL_WINDOW_FULLSCREEN_DESKTOP); + SDL_ShowCursor(s_isFullscreenCursorVisible ? SDL_ENABLE : SDL_DISABLE); + } + else + { + SDL_SetWindowFullscreen(s_pWindow, 0); + SDL_ShowCursor(SDL_ENABLE); + + SetIcon(GameWindow::s_isIconNight); + SetDimensions(Config::WindowWidth, Config::WindowHeight, Config::WindowX, Config::WindowY); + } + + return isEnabled; +} + +void GameWindow::SetFullscreenCursorVisibility(bool isVisible) +{ + s_isFullscreenCursorVisible = isVisible; + + if (IsFullscreen()) + { + SDL_ShowCursor(s_isFullscreenCursorVisible ? SDL_ENABLE : SDL_DISABLE); + } + else + { + SDL_ShowCursor(SDL_ENABLE); + } +} + +bool GameWindow::IsMaximised() +{ + return SDL_GetWindowFlags(s_pWindow) & SDL_WINDOW_MAXIMIZED; +} + +EWindowState GameWindow::SetMaximised(bool isEnabled) +{ + if (isEnabled) + { + SDL_MaximizeWindow(s_pWindow); + } + else + { + SDL_RestoreWindow(s_pWindow); + } + + return isEnabled + ? EWindowState::Maximised + : EWindowState::Normal; +} + +SDL_Rect GameWindow::GetDimensions() +{ + SDL_Rect rect{}; + + SDL_GetWindowPosition(s_pWindow, &rect.x, &rect.y); + SDL_GetWindowSize(s_pWindow, &rect.w, &rect.h); + + return rect; +} + +void GameWindow::SetDimensions(int w, int h, int x, int y) +{ + s_width = w; + s_height = h; + s_x = x; + s_y = y; + + SDL_SetWindowSize(s_pWindow, w, h); + SDL_ResizeEvent(s_pWindow, w, h); + + SDL_SetWindowPosition(s_pWindow, x, y); + SDL_MoveEvent(s_pWindow, x, y); +} + +void GameWindow::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; +} + +uint32_t GameWindow::GetWindowFlags() +{ + uint32_t flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE; + + if (Config::WindowState == EWindowState::Maximised) + flags |= SDL_WINDOW_MAXIMIZED; + + if (Config::Fullscreen) + flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + +#ifdef SDL_VULKAN_ENABLED + flags |= SDL_WINDOW_VULKAN; +#endif + + return flags; +} + +int GameWindow::GetDisplayCount() +{ + auto result = SDL_GetNumVideoDisplays(); + + if (result < 0) + { + LOGF_ERROR("Failed to get display count: {}", SDL_GetError()); + return 1; + } + + return result; +} + +int GameWindow::GetDisplay() +{ + return SDL_GetWindowDisplayIndex(s_pWindow); +} + +void GameWindow::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(); + } +} + +std::vector GameWindow::GetDisplayModes(bool ignoreInvalidModes, bool ignoreRefreshRates) +{ + auto result = std::vector(); + auto uniqueResolutions = std::set>(); + auto displayIndex = GetDisplay(); + auto modeCount = SDL_GetNumDisplayModes(displayIndex); + + if (modeCount <= 0) + return result; + + for (int i = modeCount - 1; i >= 0; i--) + { + SDL_DisplayMode mode; + + if (SDL_GetDisplayMode(displayIndex, i, &mode) == 0) + { + if (ignoreInvalidModes) + { + if (mode.w < MIN_WIDTH || mode.h < MIN_HEIGHT) + continue; + + SDL_DisplayMode desktopMode; + + if (SDL_GetDesktopDisplayMode(displayIndex, &desktopMode) == 0) + { + if (mode.w >= desktopMode.w || mode.h >= desktopMode.h) + continue; + } + } + + if (ignoreRefreshRates) + { + auto res = std::make_pair(mode.w, mode.h); + + if (uniqueResolutions.find(res) == uniqueResolutions.end()) + { + uniqueResolutions.insert(res); + result.push_back(mode); + } + } + else + { + result.push_back(mode); + } + } + } + + return result; +} + +int GameWindow::FindMatchingDisplayMode() +{ + auto displayModes = GetDisplayModes(); + + for (int i = 0; i < displayModes.size(); i++) + { + auto& mode = displayModes[i]; + + if (mode.w == s_width && mode.h == s_height) + return i; + } + + return -1; +} + +bool GameWindow::IsPositionValid() +{ + 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; + + // Window spans across the entire display in windowed mode, which is invalid. + if (!Config::Fullscreen && s_width == bounds.w && s_height == bounds.h) + return false; + + if (x == SDL_WINDOWPOS_CENTERED_DISPLAY(i)) + x = bounds.w / 2 - s_width / 2; + + if (y == SDL_WINDOWPOS_CENTERED_DISPLAY(i)) + 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 true; + } + } + } + + return false; +} diff --git a/UnleashedRecomp/ui/game_window.h b/UnleashedRecomp/ui/game_window.h index 87f4639..dadd407 100644 --- a/UnleashedRecomp/ui/game_window.h +++ b/UnleashedRecomp/ui/game_window.h @@ -1,20 +1,13 @@ #pragma once -#include -#include -#include -#include +#include #include #include -#include - -#if _WIN32 -#include -#pragma comment(lib, "dwmapi.lib") -#endif #define DEFAULT_WIDTH 1280 #define DEFAULT_HEIGHT 720 +#define MIN_WIDTH 640 +#define MIN_HEIGHT 480 class GameWindow { @@ -32,277 +25,27 @@ public: static inline bool s_isFullscreenCursorVisible; static inline bool s_isChangingDisplay; - static SDL_Surface* GetIconSurface(void* pIconBmp, size_t iconSize) - { - auto rw = SDL_RWFromMem(pIconBmp, iconSize); - auto surface = SDL_LoadBMP_RW(rw, 1); - - if (!surface) - LOGF_ERROR("Failed to load icon: {}", SDL_GetError()); - - return surface; - } - - static void SetIcon(void* pIconBmp, size_t iconSize) - { - if (auto icon = GetIconSurface(pIconBmp, iconSize)) - { - SDL_SetWindowIcon(s_pWindow, icon); - SDL_FreeSurface(icon); - } - } - - static void SetIcon(bool isNight = false) - { - if (isNight) - { - SetIcon(g_game_icon_night, sizeof(g_game_icon_night)); - } - else - { - SetIcon(g_game_icon, sizeof(g_game_icon)); - } - } - - static const char* GetTitle() - { - return Config::Language == ELanguage::Japanese - ? "SONIC WORLD ADVENTURE" - : "SONIC UNLEASHED"; - } - - static void SetTitle(const char* title = nullptr) - { - SDL_SetWindowTitle(s_pWindow, title ? title : GetTitle()); - } - - static void SetDarkTitleBar(bool isEnabled) - { -#if _WIN32 - auto version = os::version::GetOSVersion(); - - if (version.Major < 10 || version.Build <= 17763) - return; - - auto flag = version.Build >= 18985 - ? DWMWA_USE_IMMERSIVE_DARK_MODE - : 19; // DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 - - const DWORD useImmersiveDarkMode = isEnabled; - DwmSetWindowAttribute(s_renderWindow, flag, &useImmersiveDarkMode, sizeof(useImmersiveDarkMode)); -#endif - } - - static bool IsFullscreen() - { - return SDL_GetWindowFlags(s_pWindow) & SDL_WINDOW_FULLSCREEN_DESKTOP; - } - - static bool SetFullscreen(bool isEnabled) - { - if (isEnabled) - { - SDL_SetWindowFullscreen(s_pWindow, SDL_WINDOW_FULLSCREEN_DESKTOP); - SDL_ShowCursor(s_isFullscreenCursorVisible ? SDL_ENABLE : SDL_DISABLE); - } - else - { - SDL_SetWindowFullscreen(s_pWindow, 0); - SDL_ShowCursor(SDL_ENABLE); - - SetIcon(GameWindow::s_isIconNight); - SetDimensions(Config::WindowWidth, Config::WindowHeight, Config::WindowX, Config::WindowY); - } - - return isEnabled; - } - - static void SetFullscreenCursorVisibility(bool isVisible) - { - s_isFullscreenCursorVisible = isVisible; - - if (IsFullscreen()) - { - SDL_ShowCursor(s_isFullscreenCursorVisible ? SDL_ENABLE : SDL_DISABLE); - } - else - { - SDL_ShowCursor(SDL_ENABLE); - } - } - - static bool IsMaximised() - { - return SDL_GetWindowFlags(s_pWindow) & SDL_WINDOW_MAXIMIZED; - } - - static EWindowState SetMaximised(bool isEnabled) - { - if (isEnabled) - { - SDL_MaximizeWindow(s_pWindow); - } - else - { - SDL_RestoreWindow(s_pWindow); - } - - return isEnabled - ? EWindowState::Maximised - : EWindowState::Normal; - } - - static SDL_Rect GetDimensions() - { - SDL_Rect rect{}; - - SDL_GetWindowPosition(s_pWindow, &rect.x, &rect.y); - SDL_GetWindowSize(s_pWindow, &rect.w, &rect.h); - - return rect; - } - - static void SetDimensions(int w, int h, int x = SDL_WINDOWPOS_CENTERED, int y = SDL_WINDOWPOS_CENTERED) - { - s_width = w; - s_height = h; - s_x = x; - s_y = y; - - SDL_SetWindowSize(s_pWindow, w, h); - SDL_ResizeEvent(s_pWindow, w, h); - - SDL_SetWindowPosition(s_pWindow, x, y); - 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; - - if (Config::WindowState == EWindowState::Maximised) - flags |= SDL_WINDOW_MAXIMIZED; - - if (Config::Fullscreen) - flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; - -#ifdef SDL_VULKAN_ENABLED - flags |= SDL_WINDOW_VULKAN; -#endif - - return flags; - } - - static int GetDisplayCount() - { - auto result = SDL_GetNumVideoDisplays(); - - if (result < 0) - { - 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; - auto y = s_y; - - if (!Config::Fullscreen && s_width == bounds.w && s_height == bounds.h) - return false; - - 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 true; - } - } - } - - return false; - } - - static void Init(const char* sdlVideoDriver); + static SDL_Surface* GetIconSurface(void* pIconBmp, size_t iconSize); + static void SetIcon(void* pIconBmp, size_t iconSize); + static void SetIcon(bool isNight = false); + static const char* GetTitle(); + static void SetTitle(const char* title = nullptr); + static void SetDarkTitleBar(bool isEnabled); + static bool IsFullscreen(); + static bool SetFullscreen(bool isEnabled); + static void SetFullscreenCursorVisibility(bool isVisible); + static bool IsMaximised(); + static EWindowState SetMaximised(bool isEnabled); + static SDL_Rect GetDimensions(); + static void SetDimensions(int w, int h, int x = SDL_WINDOWPOS_CENTERED, int y = SDL_WINDOWPOS_CENTERED); + static void ResetDimensions(); + static uint32_t GetWindowFlags(); + static int GetDisplayCount(); + static int GetDisplay(); + static void SetDisplay(int displayIndex); + static std::vector GetDisplayModes(bool ignoreInvalidModes = true, bool ignoreRefreshRates = true); + static int FindMatchingDisplayMode(); + static bool IsPositionValid(); + static void Init(const char* sdlVideoDriver = nullptr); static void Update(); }; diff --git a/UnleashedRecomp/ui/options_menu.cpp b/UnleashedRecomp/ui/options_menu.cpp index 93b853f..332d44a 100644 --- a/UnleashedRecomp/ui/options_menu.cpp +++ b/UnleashedRecomp/ui/options_menu.cpp @@ -509,10 +509,15 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf } else { - // released lock, call video callbacks if value is different + // released lock, do callbacks if value is different if (config->Value != s_oldValue) + { VideoConfigValueChangedCallback(config); + if (config->ApplyCallback) + config->ApplyCallback(config); + } + Game_PlaySound("sys_worldmap_finaldecide"); } } @@ -533,13 +538,14 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf { config->MakeDefault(); - // TODO: check if value was changed? VideoConfigValueChangedCallback(config); - // TODO: check if value was changed? if (config->Callback) config->Callback(config); + if (config->ApplyCallback) + config->ApplyCallback(config); + Game_PlaySound("sys_worldmap_decide"); } } @@ -568,8 +574,12 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf DrawTextWithMarquee(g_seuratFont, size, textPos, min, max, textColour, configName.c_str(), g_rowSelectionTime, 0.9, Scale(250.0)); - // Show reset button if this option is accessible or not a language option. - g_canReset = g_isControlsVisible && !g_lockedOnOption && g_selectedItem->GetName().find("Language") == std::string::npos && isAccessible; + // large + g_canReset = g_isControlsVisible && + !g_lockedOnOption && + g_selectedItem->GetName().find("Language") == std::string::npos && + g_selectedItem != &Config::WindowSize && + isAccessible; } else { @@ -753,8 +763,11 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf config->Value = std::clamp(config->Value, valueMin, valueMax); } - if ((increment || decrement) && config->Callback) - config->Callback(config); + if (!config->ApplyCallback) + { + if ((increment || decrement) && config->Callback) + config->Callback(config); + } } std::string valueText; @@ -764,10 +777,32 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf } else if constexpr (std::is_same_v) { - valueText = fmt::format("{}", config->Value); + if (config == &Config::WindowSize) + { + auto displayModes = GameWindow::GetDisplayModes(); - if (isSlider && config->Value >= valueMax) - valueText = Localise("Options_Value_Max"); + // Try matching the current window size with a known configuration. + if (config->Value < 0) + config->Value = GameWindow::FindMatchingDisplayMode(); + + 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 + { + valueText = fmt::format("{}", config->Value); + + if (isSlider && config->Value >= valueMax) + valueText = Localise("Options_Value_Max"); + } } else { @@ -851,7 +886,9 @@ static void DrawConfigOptions() case 3: // VIDEO { - // TODO: expose WindowWidth/WindowHeight as WindowSize. + 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; diff --git a/UnleashedRecomp/ui/options_menu_thumbnails.cpp b/UnleashedRecomp/ui/options_menu_thumbnails.cpp index d2b968c..dc1701e 100644 --- a/UnleashedRecomp/ui/options_menu_thumbnails.cpp +++ b/UnleashedRecomp/ui/options_menu_thumbnails.cpp @@ -87,6 +87,7 @@ void LoadThumbnails() g_configThumbnails[&Config::Subtitles] = LOAD_ZSTD_TEXTURE(g_subtitles); g_configThumbnails[&Config::MusicAttenuation] = LOAD_ZSTD_TEXTURE(g_music_attenuation); g_configThumbnails[&Config::BattleTheme] = LOAD_ZSTD_TEXTURE(g_battle_theme); + 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::ResolutionScale] = LOAD_ZSTD_TEXTURE(g_resolution_scale); diff --git a/UnleashedRecomp/user/config.h b/UnleashedRecomp/user/config.h index cd450b9..365e56c 100644 --- a/UnleashedRecomp/user/config.h +++ b/UnleashedRecomp/user/config.h @@ -294,6 +294,7 @@ public: std::map EnumTemplateReverse{}; CONFIG_ENUM_LOCALE(T)* EnumLocale{}; std::function*)> Callback; + std::function*)> ApplyCallback; // CONFIG_DEFINE ConfigDef(std::string section, std::string name, T defaultValue) : Section(section), Name(name), DefaultValue(defaultValue) @@ -619,6 +620,7 @@ public: CONFIG_DEFINE("Video", int32_t, WindowX, WINDOWPOS_CENTRED); CONFIG_DEFINE("Video", int32_t, WindowY, WINDOWPOS_CENTRED); + CONFIG_DEFINE_LOCALISED("Video", int32_t, WindowSize, -1); CONFIG_DEFINE("Video", int32_t, WindowWidth, 1280); CONFIG_DEFINE("Video", int32_t, WindowHeight, 720); CONFIG_DEFINE_ENUM("Video", EWindowState, WindowState, EWindowState::Normal); @@ -638,7 +640,7 @@ public: def->Value = std::clamp(def->Value, 0.25f, 2.0f); }); - CONFIG_DEFINE_CALLBACK("Video", bool, Fullscreen, false, + CONFIG_DEFINE_CALLBACK("Video", bool, Fullscreen, true, { def->Locale = &g_Fullscreen_locale;