Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/options-window.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ What was added on top of that:
centered dialogs remain capped at 2 times their reference size. Text grows
with the active layout up to the dock scale, shrinking only when needed to
fit an existing control.
- **Classic bottom HUD** is an opt-in UI setting. It keeps the original 640-wide
bar uniformly scaled and centered on wide windows; leave it off to use the
responsive three-band layout. The change applies immediately.

## What it stores in `config.ini`

Expand All @@ -52,6 +55,8 @@ window writes to these sections:
`SavePassword`, `EncryptedUsername`, `EncryptedPassword`); see
"Remembering login credentials" below.
- `[ConnectionSettings]`
- `[UI]` - locale, font, and `BottomHudClassic` (`0` by default; `1` enables the
classic centered bottom bar).
- `[Camera]` - orbital wheel-zoom radius (`Zoom`). **New in this PR**, the
only key the camera rework added.

Expand Down
3 changes: 3 additions & 0 deletions src/Localization/Game.en.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1433,6 +1433,9 @@
<data name="Windowed mode" xml:space="preserve">
<value>Windowed mode</value>
</data>
<data name="Classic bottom HUD" xml:space="preserve">
<value>Classic bottom HUD</value>
</data>
<data name="Close388" xml:space="preserve">
<value>Close</value>
<comment>legacy_id=388,1002</comment>
Expand Down
6 changes: 6 additions & 0 deletions src/source/App/Platform/Windows/Winmain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,11 @@ void MuReapplyVSyncPreference()
g_hasPendingVSyncPreference = true;
}

void MuApplyBottomHudLayoutPreference()
{
UI::Scaling::SetBottomHudClassic(GameConfig::GetInstance().GetBottomHudClassic());
}

BOOL GetFileNameOfFilePath(wchar_t* lpszFile, wchar_t* lpszPath)
{
auto iFind = (int)'\\';
Expand Down Expand Up @@ -1916,6 +1921,7 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int nC

// Load game settings from INI file first
GameConfig::GetInstance().Load();
MuApplyBottomHudLayoutPreference();

// Check if animation task pool should be enabled (disabled by default)
{
Expand Down
7 changes: 7 additions & 0 deletions src/source/Data/GameConfig/GameConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ void GameConfig::Load()

m_uiLocale = ReadString(CfgSectionUI, CfgKeyUILocale, CfgDefaultUILocale);
m_fontSelection = ReadString(CfgSectionUI, CfgKeyFont, CfgDefaultFont);
m_bottomHudClassic = ReadBool(CfgSectionUI, CfgKeyBottomHudClassic, CfgDefaultBottomHudClassic);

m_zoom = ReadInt(CfgSectionCamera, CfgKeyZoom, CfgDefaultZoom);
m_sortParticleDraws = ReadBool(CfgSectionRender, CfgKeySortParticleDraws, CfgDefaultSortParticleDraws);
Expand Down Expand Up @@ -124,6 +125,7 @@ void GameConfig::Save()

WriteString(CfgSectionUI, CfgKeyUILocale, m_uiLocale);
WriteString(CfgSectionUI, CfgKeyFont, m_fontSelection);
WriteBool(CfgSectionUI, CfgKeyBottomHudClassic, m_bottomHudClassic);

WriteInt(CfgSectionCamera, CfgKeyZoom, m_zoom);
WriteBool(CfgSectionRender, CfgKeyVSync, m_vsyncEnabled);
Expand Down Expand Up @@ -226,6 +228,11 @@ void GameConfig::SetFontSelection(const std::wstring& font)
m_fontSelection = font;
}

void GameConfig::SetBottomHudClassic(bool classic)
{
m_bottomHudClassic = classic;
}

void GameConfig::SetEncryptedUsername(const std::wstring& encryptedUsername)
{
m_encryptedUsername = encryptedUsername;
Expand Down
6 changes: 6 additions & 0 deletions src/source/Data/GameConfig/GameConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ class GameConfig
// UI font family name (GDI face name). Empty = platform default.
std::wstring GetFontSelection() const { return m_fontSelection; }
void SetFontSelection(const std::wstring& font);
bool GetBottomHudClassic() const
{
return m_bottomHudClassic;
}
void SetBottomHudClassic(bool classic);

// Chat commands - the favourites and the named templates of the command
// window. They belong to the installation, not to a character.
Expand Down Expand Up @@ -127,6 +132,7 @@ class GameConfig

std::wstring m_uiLocale;
std::wstring m_fontSelection;
bool m_bottomHudClassic;

int m_zoom;
bool m_sortParticleDraws;
Expand Down
6 changes: 4 additions & 2 deletions src/source/Data/GameConfig/GameConfigConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ namespace CfgKeys

// UI
inline constexpr wchar_t CfgKeyUILocale[] = L"Locale";
inline constexpr wchar_t CfgKeyFont[] = L"Font";

inline constexpr wchar_t CfgKeyFont[] = L"Font";
inline constexpr wchar_t CfgKeyBottomHudClassic[] = L"BottomHudClassic";
// Camera
inline constexpr wchar_t CfgKeyZoom[] = L"Zoom";

Expand Down Expand Up @@ -75,6 +75,8 @@ namespace CfgDefaults
// Windows, fontconfig "sans-serif" on Linux), so the look is unchanged until
// the user picks a font. Any value is passed through as the GDI face name.
inline constexpr wchar_t CfgDefaultFont[] = L"";
// Keep the responsive three-band layout unless the player opts in.
inline constexpr bool CfgDefaultBottomHudClassic = false;

// Legacy config compatibility only. SDL GPU ignores this key and default.
inline constexpr bool CfgDefaultCoreProfile = true;
Expand Down
59 changes: 44 additions & 15 deletions src/source/UI/NewUI/Options/NewUIOptionWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "Audio/DSPlaySound.h"
#include "Data/GameConfig/GameConfig.h"
#include "Data/GameConfig/GameConfigConstants.h"
#include "UI/Scaling/UITransform.h"
#include "Audio/AudioPlayer.h"
#include <algorithm>
#include <cstring>
Expand Down Expand Up @@ -182,10 +183,16 @@ namespace
constexpr int FONT_LABEL_Y_LOCAL = 244;
constexpr int FONT_COMBO_X_LOCAL = 22;
constexpr int FONT_COMBO_Y_LOCAL = 257;
constexpr int FONT_COMBO_WIDTH = 148;
constexpr int FONT_COMBO_HEIGHT = 16;
constexpr int FONT_COMBO_WIDTH = 148;
constexpr int FONT_COMBO_HEIGHT = 16;
constexpr int FONT_COMBO_MAX_VISIBLE = 5;
}

constexpr int CLASSIC_BOTTOM_HUD_CHECKBOX_Y_LOCAL = 395;
constexpr int CLASSIC_BOTTOM_HUD_LABEL_Y_LOCAL = 400;
constexpr int OPTION_CLOSE_Y_LOCAL = 427;
constexpr int OPTION_WINDOW_HEIGHT = 458;
constexpr int OPTION_FRAME_SLAT_COUNT = 35;
} // namespace

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
Expand All @@ -206,6 +213,7 @@ SEASON3B::CNewUIOptionWindow::CNewUIOptionWindow()
m_bRenderAllEffects = true;
m_iResolutionIndex = 0;
m_bWindowedMode = (g_bUseWindowMode == TRUE);
m_bBottomHudClassic = GameConfig::GetInstance().GetBottomHudClassic();
m_iLanguageIndex = FindCurrentLanguageIndex();
m_iFontIndex = FindCurrentFontIndex();
}
Expand Down Expand Up @@ -299,7 +307,7 @@ void SEASON3B::CNewUIOptionWindow::SetButtonInfo()
{
m_BtnClose.ChangeTextBackColor(RGBA(255, 255, 255, 0));
m_BtnClose.ChangeButtonImgState(true, IMAGE_OPTION_BTN_CLOSE, true);
m_BtnClose.ChangeButtonInfo(m_Pos.x + 68, m_Pos.y + 388, 54, 30);
m_BtnClose.ChangeButtonInfo(m_Pos.x + 68, m_Pos.y + OPTION_CLOSE_Y_LOCAL, 54, 30);
m_BtnClose.ChangeImgColor(BUTTON_STATE_UP, RGBA(255, 255, 255, 255));
m_BtnClose.ChangeImgColor(BUTTON_STATE_DOWN, RGBA(255, 255, 255, 255));
}
Expand Down Expand Up @@ -392,12 +400,14 @@ bool SEASON3B::CNewUIOptionWindow::UpdateMouseEvent()
return false;
}

bool oldWindowedMode = m_bWindowedMode;
const bool oldWindowedMode = m_bWindowedMode;
const bool oldBottomHudClassic = m_bBottomHudClassic;
HandleCheckboxInputs();

if (m_bWindowedMode != oldWindowedMode)
ApplyWindowModeToggle();

if (m_bBottomHudClassic != oldBottomHudClassic)
ApplyBottomHudClassic();
if (HandleVolumeSlider(m_iVolumeLevel, 104))
OnSoundVolumeChanged();

Expand All @@ -408,7 +418,7 @@ bool SEASON3B::CNewUIOptionWindow::UpdateMouseEvent()

// Combo box already processed at the top. Just consume clicks inside the
// option window itself so they don't fall through to the world.
if (CheckMouseIn(m_Pos.x, m_Pos.y, 190, 419))
if (CheckMouseIn(m_Pos.x, m_Pos.y, 190, OPTION_WINDOW_HEIGHT))
return false;

return true;
Expand All @@ -418,11 +428,9 @@ void SEASON3B::CNewUIOptionWindow::HandleCheckboxInputs()
{
struct Checkbox { int yLocal; bool* target; };
const Checkbox boxes[] = {
{ 43, &m_bAutoAttack },
{ 65, &m_bWhisperSound },
{ 155, &m_bSlideHelp },
{ 238, &m_bRenderAllEffects },
{ 356, &m_bWindowedMode },
{43, &m_bAutoAttack}, {65, &m_bWhisperSound},
{155, &m_bSlideHelp}, {238, &m_bRenderAllEffects},
{356, &m_bWindowedMode}, {CLASSIC_BOTTOM_HUD_CHECKBOX_Y_LOCAL, &m_bBottomHudClassic},
};

constexpr int CHECKBOX_X_LOCAL = 150;
Expand Down Expand Up @@ -567,6 +575,7 @@ void SEASON3B::CNewUIOptionWindow::OpenningProcess()
m_FontCombo.SetSelectedIndex(m_iFontIndex);
m_FontCombo.Close();
m_bWindowedMode = (g_bUseWindowMode == TRUE);
m_bBottomHudClassic = GameConfig::GetInstance().GetBottomHudClassic();
}

void SEASON3B::CNewUIOptionWindow::ClosingProcess()
Expand Down Expand Up @@ -616,9 +625,8 @@ void SEASON3B::CNewUIOptionWindow::RenderFrame()
x = m_Pos.x;
y = m_Pos.y;
// Frame is composed of: 64px top + N*10px middle slats + 45px bottom. The
// slat count is tuned so the frame reaches the Close button (Y 388) plus the
// bottom border, after the Font/Language/Resolution/Windowed rows.
constexpr int SLAT_COUNT = 30;
// slat count reaches the Close button after the UI settings rows.
constexpr int SLAT_COUNT = OPTION_FRAME_SLAT_COUNT;
constexpr float FRAME_HEIGHT = 64.f + SLAT_COUNT * 10.f + 45.f;
RenderImage(IMAGE_OPTION_FRAME_BACK, x, y, 190.f, FRAME_HEIGHT);
RenderImage(IMAGE_OPTION_FRAME_UP, x, y, 190.f, 64.f);
Expand Down Expand Up @@ -695,6 +703,10 @@ void SEASON3B::CNewUIOptionWindow::RenderContents()
y += 39.f;
RenderImage(IMAGE_OPTION_POINT, x, y, 10.f, 10.f); // Windowed Mode
g_pRenderText->RenderText(m_Pos.x + 40, m_Pos.y + 361, I18N::Game::WindowedMode);

y += 39.f;
RenderImage(IMAGE_OPTION_POINT, x, y, 10.f, 10.f); // Classic Bottom HUD
g_pRenderText->RenderText(m_Pos.x + 40, m_Pos.y + CLASSIC_BOTTOM_HUD_LABEL_Y_LOCAL, I18N::Game::ClassicBottomHUD);
}

void SEASON3B::CNewUIOptionWindow::RenderButtons()
Expand Down Expand Up @@ -772,6 +784,16 @@ void SEASON3B::CNewUIOptionWindow::RenderButtons()
RenderImage(IMAGE_OPTION_BTN_CHECK, m_Pos.x + 150, m_Pos.y + 356, 15, 15, 0, 15.f);
}

if (m_bBottomHudClassic)
{
RenderImage(IMAGE_OPTION_BTN_CHECK, m_Pos.x + 150, m_Pos.y + CLASSIC_BOTTOM_HUD_CHECKBOX_Y_LOCAL, 15, 15, 0, 0);
}
else
{
RenderImage(IMAGE_OPTION_BTN_CHECK, m_Pos.x + 150, m_Pos.y + CLASSIC_BOTTOM_HUD_CHECKBOX_Y_LOCAL, 15, 15, 0,
15.f);
}

// Combo boxes drawn last so their expanded dropdowns sit on top of
// anything else in the window. Within the combo pair, render the
// closed one(s) first and any open dropdown last - otherwise a combo
Expand Down Expand Up @@ -966,3 +988,10 @@ void SEASON3B::CNewUIOptionWindow::ApplyWindowModeToggle()
// toggle again next frame; the user must release and click again.
g_pNewKeyInput->SetKeyState(VK_LBUTTON, SEASON3B::CNewKeyInput::KEY_NONE);
}

void SEASON3B::CNewUIOptionWindow::ApplyBottomHudClassic()
{
UI::Scaling::SetBottomHudClassic(m_bBottomHudClassic);
GameConfig::GetInstance().SetBottomHudClassic(m_bBottomHudClassic);
GameConfig::GetInstance().Save();
}
2 changes: 2 additions & 0 deletions src/source/UI/NewUI/Options/NewUIOptionWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ namespace SEASON3B
bool m_bRenderAllEffects;
int m_iResolutionIndex;
bool m_bWindowedMode;
bool m_bBottomHudClassic;
int m_iLanguageIndex;
int m_iFontIndex;

Expand All @@ -134,6 +135,7 @@ namespace SEASON3B
void InitResolutionCombo();
void SyncResolutionComboToWindow();
void ApplyWindowModeToggle();
void ApplyBottomHudClassic();

void ApplyLanguage();
int FindCurrentLanguageIndex();
Expand Down
34 changes: 31 additions & 3 deletions src/source/UI/Scaling/UITransform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ UI::Scaling::Transform BottomHudTransform(int windowWidth, int windowHeight, flo
return {scale, scale, offsetX, static_cast<float>(windowHeight) - kReferenceHeight * scale, scale};
}

UI::Scaling::Transform BottomHudClassicTransform(int windowWidth, int windowHeight)
{
const float scale = UI::Scaling::BottomHudScale(windowWidth, windowHeight);
const float offsetX = (static_cast<float>(windowWidth) - kReferenceWidth * scale) * 0.5f;
return BottomHudTransform(windowWidth, windowHeight, offsetX);
}

bool ContainsLogicalRect(const UI::Scaling::Transform& transform, float windowX, float windowY, float left,
float top, float right, float bottom)
{
Expand Down Expand Up @@ -82,11 +89,14 @@ float CappedUniformScale(int windowWidth, int windowHeight, float maximumScale)
UI::Scaling::Transform DockTransform(int windowWidth, int windowHeight)
{
const float scale = CappedUniformScale(windowWidth, windowHeight, kMaximumDockScale);
const float offsetY = static_cast<float>(RoundedBottomHudTop(windowWidth, windowHeight))
- UI::Scaling::DockLogicalBottom * scale;
const float offsetY =
static_cast<float>(RoundedBottomHudTop(windowWidth, windowHeight)) - UI::Scaling::DockLogicalBottom * scale;
return {scale, scale, 0.0f, offsetY, scale};
}
}

bool g_bottomHudClassic = false;

} // namespace

UI::Scaling::Transform UI::Scaling::ScreenOverlayTransform(int windowWidth, int windowHeight)
{
Expand Down Expand Up @@ -133,27 +143,45 @@ float UI::Scaling::BottomHudScale(int windowWidth, int windowHeight)
kMaximumHudScale * contentScale);
}

void UI::Scaling::SetBottomHudClassic(bool classic)
{
g_bottomHudClassic = classic;
}

bool UI::Scaling::IsBottomHudClassic()
{
return g_bottomHudClassic;
}

UI::Scaling::Transform UI::Scaling::BottomHudLeftTransform(int windowWidth, int windowHeight)
{
if (g_bottomHudClassic)
return BottomHudClassicTransform(windowWidth, windowHeight);
return BottomHudTransform(windowWidth, windowHeight, 0.0f);
}

UI::Scaling::Transform UI::Scaling::BottomHudCenterTransform(int windowWidth, int windowHeight)
{
if (g_bottomHudClassic)
return BottomHudClassicTransform(windowWidth, windowHeight);
const float scale = BottomHudScale(windowWidth, windowHeight);
return BottomHudTransform(windowWidth, windowHeight,
static_cast<float>(windowWidth) * 0.5f - 320.0f * scale);
}

UI::Scaling::Transform UI::Scaling::BottomHudRightTransform(int windowWidth, int windowHeight)
{
if (g_bottomHudClassic)
return BottomHudClassicTransform(windowWidth, windowHeight);
const float scale = BottomHudScale(windowWidth, windowHeight);
return BottomHudTransform(windowWidth, windowHeight,
static_cast<float>(windowWidth) - kReferenceWidth * scale);
}

UI::Scaling::Transform UI::Scaling::BottomHudExperienceTransform(int windowWidth, int windowHeight)
{
if (g_bottomHudClassic)
return BottomHudClassicTransform(windowWidth, windowHeight);
Transform transform = BottomHudLeftTransform(windowWidth, windowHeight);
transform.scaleX = static_cast<float>(windowWidth) / kReferenceWidth;
return transform;
Expand Down
2 changes: 2 additions & 0 deletions src/source/UI/Scaling/UITransform.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ namespace UI::Scaling
Transform LegacyUiTransform(int windowWidth, int windowHeight);
Transform PanelTransform(int windowWidth, int windowHeight);
float BottomHudScale(int windowWidth, int windowHeight);
void SetBottomHudClassic(bool classic);
bool IsBottomHudClassic();
Transform BottomHudLeftTransform(int windowWidth, int windowHeight);
Transform BottomHudCenterTransform(int windowWidth, int windowHeight);
Transform BottomHudRightTransform(int windowWidth, int windowHeight);
Expand Down
24 changes: 24 additions & 0 deletions tests/ui/test_ui_scaling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,30 @@ TEST_CASE("bottom HUD regional transforms round trip window positions [ui][scali
CHECK(UI::Scaling::LogicalY(center, UI::Scaling::PositionY(center, 450.0f)) == doctest::Approx(450.0f));
}

TEST_CASE("classic bottom HUD centers the whole bar and keeps the rail its width [ui][scaling]")
{
CHECK_FALSE(CfgDefaults::CfgDefaultBottomHudClassic);
CHECK_FALSE(UI::Scaling::IsBottomHudClassic());
UI::Scaling::SetBottomHudClassic(true);
const auto left = UI::Scaling::BottomHudLeftTransform(1920, 1080);
const auto center = UI::Scaling::BottomHudCenterTransform(1920, 1080);
const auto right = UI::Scaling::BottomHudRightTransform(1920, 1080);
const auto experience = UI::Scaling::BottomHudExperienceTransform(1920, 1080);
UI::Scaling::SetBottomHudClassic(false);
CHECK_FALSE(UI::Scaling::IsBottomHudClassic());

CHECK(left.scaleX == doctest::Approx(2.0f));
CHECK(left.offsetX == doctest::Approx(320.0f));
CHECK(center.offsetX == doctest::Approx(left.offsetX));
CHECK(right.offsetX == doctest::Approx(left.offsetX));
CHECK(UI::Scaling::PositionX(left, 0.0f) == doctest::Approx(320.0f));
CHECK(UI::Scaling::PositionX(right, 640.0f) == doctest::Approx(1600.0f));
CHECK(UI::Scaling::PositionX(center, 320.0f) == doctest::Approx(960.0f));
CHECK(experience.scaleX == doctest::Approx(2.0f));
CHECK(UI::Scaling::PositionX(experience, 640.0f) == doctest::Approx(1600.0f));
CHECK(UI::Scaling::PositionY(experience, 480.0f) == doctest::Approx(1080.0f));
}

TEST_CASE("experience transform spans the window with HUD vertical scale [ui][scaling]")
{
const auto experience = UI::Scaling::BottomHudExperienceTransform(1920, 1200);
Expand Down