Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add integer scaling feature and make client window resizable #557

Merged
merged 9 commits into from
Dec 1, 2024
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
16 changes: 14 additions & 2 deletions ClientCore/ClientConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class ClientConfiguration
private const string SETTINGS = "Settings";
private const string LINKS = "Links";
private const string TRANSLATIONS = "Translations";
private const string USER_DEFAULTS = "UserDefaults";

private const string CLIENT_SETTINGS = "DTACnCNetClient.ini";
private const string GAME_OPTIONS = "GameOptions.ini";
Expand Down Expand Up @@ -193,7 +194,8 @@ public void RefreshSettings()

public int MaximumRenderHeight => clientDefinitionsIni.GetIntValue(SETTINGS, "MaximumRenderHeight", 800);

public string[] RecommendedResolutions => clientDefinitionsIni.GetStringValue(SETTINGS, "RecommendedResolutions", "1280x720,2560x1440,3840x2160").Split(',');
public string[] RecommendedResolutions => clientDefinitionsIni.GetStringValue(SETTINGS, "RecommendedResolutions",
$"{MinimumRenderWidth}x{MinimumRenderHeight},{MaximumRenderWidth}x{MaximumRenderHeight}").Split(',');

public string WindowTitle => clientDefinitionsIni.GetStringValue(SETTINGS, "WindowTitle", string.Empty)
.L10N("INI:ClientDefinitions:WindowTitle");
Expand Down Expand Up @@ -405,6 +407,16 @@ public IEnumerable<string> SupplementalMapFileExtensions

#endregion

#region User default settings

public bool UserDefault_BorderlessWindowedClient => clientDefinitionsIni.GetBooleanValue(USER_DEFAULTS, "BorderlessWindowedClient", true);

public bool UserDefault_IntegerScaledClient => clientDefinitionsIni.GetBooleanValue(USER_DEFAULTS, "IntegerScaledClient", false);

public bool UserDefault_WriteInstallationPathToRegistry => clientDefinitionsIni.GetBooleanValue(USER_DEFAULTS, "WriteInstallationPathToRegistry", true);

#endregion

public List<string> GetIRCServers()
{
List<string> servers = [];
Expand All @@ -419,7 +431,7 @@ public List<string> GetIRCServers()
}

public bool DiscordIntegrationGloballyDisabled => string.IsNullOrWhiteSpace(DiscordAppId) || DisableDiscordIntegration;

public OSVersion GetOperatingSystemVersion()
{
#if NETFRAMEWORK
Expand Down
6 changes: 4 additions & 2 deletions ClientCore/Settings/UserINISettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ protected UserINISettings(IniFile iniFile)
Renderer = new StringSetting(iniFile, COMPATIBILITY, "Renderer", string.Empty);
WindowedMode = new BoolSetting(iniFile, VIDEO, WINDOWED_MODE_KEY, false);
BorderlessWindowedMode = new BoolSetting(iniFile, VIDEO, "NoWindowFrame", false);
BorderlessWindowedClient = new BoolSetting(iniFile, VIDEO, "BorderlessWindowedClient", true);
BorderlessWindowedClient = new BoolSetting(iniFile, VIDEO, "BorderlessWindowedClient", ClientConfiguration.Instance.UserDefault_BorderlessWindowedClient);
IntegerScaledClient = new BoolSetting(iniFile, VIDEO, "IntegerScaledClient", ClientConfiguration.Instance.UserDefault_IntegerScaledClient);
ClientFPS = new IntSetting(iniFile, VIDEO, "ClientFPS", 60);
DisplayToggleableExtraTextures = new BoolSetting(iniFile, VIDEO, "DisplayToggleableExtraTextures", true);

Expand All @@ -86,7 +87,7 @@ protected UserINISettings(IniFile iniFile)
ChatColor = new IntSetting(iniFile, MULTIPLAYER, "ChatColor", -1);
LANChatColor = new IntSetting(iniFile, MULTIPLAYER, "LANChatColor", -1);
PingUnofficialCnCNetTunnels = new BoolSetting(iniFile, MULTIPLAYER, "PingCustomTunnels", true);
WritePathToRegistry = new BoolSetting(iniFile, OPTIONS, "WriteInstallationPathToRegistry", true);
WritePathToRegistry = new BoolSetting(iniFile, OPTIONS, "WriteInstallationPathToRegistry", ClientConfiguration.Instance.UserDefault_WriteInstallationPathToRegistry);
PlaySoundOnGameHosted = new BoolSetting(iniFile, MULTIPLAYER, "PlaySoundOnGameHosted", true);
SkipConnectDialog = new BoolSetting(iniFile, MULTIPLAYER, "SkipConnectDialog", false);
PersistentMode = new BoolSetting(iniFile, MULTIPLAYER, "PersistentMode", false);
Expand Down Expand Up @@ -151,6 +152,7 @@ protected UserINISettings(IniFile iniFile)
public IntSetting ClientResolutionX { get; set; }
public IntSetting ClientResolutionY { get; set; }
public BoolSetting BorderlessWindowedClient { get; private set; }
public BoolSetting IntegerScaledClient { get; private set; }
public IntSetting ClientFPS { get; private set; }
public BoolSetting DisplayToggleableExtraTextures { get; private set; }

Expand Down
33 changes: 30 additions & 3 deletions DTAConfig/OptionPanels/DisplayOptionsPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public DisplayOptionsPanel(WindowManager windowManager, UserINISettings iniSetti
private XNAClientCheckBox chkBackBufferInVRAM;
private XNAClientPreferredItemDropDown ddClientResolution;
private XNAClientCheckBox chkBorderlessClient;
private XNAClientCheckBox chkIntegerScaledClient;
private XNAClientDropDown ddClientTheme;
private XNAClientDropDown ddTranslation;

Expand Down Expand Up @@ -86,7 +87,7 @@ public override void Initialize()
var maximumIngameResolution = new ScreenResolution(ClientConfiguration.Instance.MaximumIngameWidth, ClientConfiguration.Instance.MaximumIngameHeight);

#if XNA
if (!ScreenResolution.HiDefLimitResolution.Fit(maximumIngameResolution))
if (!ScreenResolution.HiDefLimitResolution.Fits(maximumIngameResolution))
maximumIngameResolution = ScreenResolution.HiDefLimitResolution;
#endif

Expand Down Expand Up @@ -224,18 +225,36 @@ .. scaledRecommendedResolutions
chkBorderlessClient.CheckedChanged += ChkBorderlessMenu_CheckedChanged;
chkBorderlessClient.Checked = true;

chkIntegerScaledClient = new XNAClientCheckBox(WindowManager);
chkIntegerScaledClient.Name = nameof(chkIntegerScaledClient);
chkIntegerScaledClient.ClientRectangle = new Rectangle(
lblClientResolution.X,
lblRenderer.Y, 0, 0);
chkIntegerScaledClient.Text = "Integer Scaled Client".L10N("Client:DTAConfig:IntegerScaledClient");
chkIntegerScaledClient.Checked = IniSettings.IntegerScaledClient.Value;
chkIntegerScaledClient.ToolTipText =
"""
Enable integer scaling for the client. This will cause the client to use
the closest fitting resolution that is required to maintain sharp graphics,
at the expense of black borders that may appear at some resolutions.

Additionally, enabling this option will also allow the client window
to be resized (does not affect the selected client resolution).
"""
.L10N("Client:DTAConfig:IntegerScaledClientToolTip");

var lblClientTheme = new XNALabel(WindowManager);
lblClientTheme.Name = "lblClientTheme";
lblClientTheme.ClientRectangle = new Rectangle(
lblClientResolution.X,
lblRenderer.Y, 0, 0);
chkWindowedMode.Y, 0, 0);
lblClientTheme.Text = "Client Theme:".L10N("Client:DTAConfig:ClientTheme");

ddClientTheme = new XNAClientDropDown(WindowManager);
ddClientTheme.Name = "ddClientTheme";
ddClientTheme.ClientRectangle = new Rectangle(
ddClientResolution.X,
ddRenderer.Y,
chkWindowedMode.Y,
ddClientResolution.Width,
ddRenderer.Height);

Expand Down Expand Up @@ -326,6 +345,7 @@ .. scaledRecommendedResolutions
AddChild(chkBorderlessWindowedMode);
AddChild(chkBackBufferInVRAM);
AddChild(chkBorderlessClient);
AddChild(chkIntegerScaledClient);
AddChild(lblClientTheme);
AddChild(ddClientTheme);
AddChild(lblTranslation);
Expand Down Expand Up @@ -769,13 +789,20 @@ public override bool Save()
clientRes.Height != IniSettings.ClientResolutionY.Value)
restartRequired = true;

// TODO: since DTAConfig must not rely on DXMainClient, we can't notify the client to dynamically change the resolution or togging borderless windowed mode. Thus, we need to restart the client as a workaround.

(IniSettings.ClientResolutionX.Value, IniSettings.ClientResolutionY.Value) = clientRes;

if (IniSettings.BorderlessWindowedClient.Value != chkBorderlessClient.Checked)
restartRequired = true;

IniSettings.BorderlessWindowedClient.Value = chkBorderlessClient.Checked;

if (IniSettings.IntegerScaledClient.Value != chkIntegerScaledClient.Checked)
restartRequired = true;

IniSettings.IntegerScaledClient.Value = chkIntegerScaledClient.Checked;

restartRequired = restartRequired || IniSettings.ClientTheme != (string)ddClientTheme.SelectedItem.Tag;

IniSettings.ClientTheme.Value = (string)ddClientTheme.SelectedItem.Tag;
Expand Down
28 changes: 25 additions & 3 deletions DTAConfig/ScreenResolution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace DTAConfig
Expand All @@ -28,6 +29,12 @@ public ScreenResolution(int width, int height)
Height = height;
}

public ScreenResolution(Rectangle rectangle)
{
Width = rectangle.Width;
Height = rectangle.Height;
}

public ScreenResolution(string resolution)
{
List<int> resolutionList = resolution.Trim().Split('x').Take(2).Select(int.Parse).ToList();
Expand All @@ -51,32 +58,44 @@ public void Deconstruct(out int width, out int height)

public static implicit operator (int Width, int Height)(ScreenResolution resolution) => new(resolution.Width, resolution.Height);

public bool Fit(ScreenResolution child) => this.Width >= child.Width && this.Height >= child.Height;
public bool Fits(ScreenResolution child) => this.Width >= child.Width && this.Height >= child.Height;

public int CompareTo(ScreenResolution other) => (this.Width, this.Height).CompareTo(other);

// Accessing GraphicsAdapter.DefaultAdapter requiring DXMainClient.GameClass has been constructed. Lazy loading prevents possible null reference issues for now.
private static ScreenResolution _desktopResolution = null;

/// <summary>
/// The resolution of primary monitor.
/// </summary>
public static ScreenResolution DesktopResolution =>
_desktopResolution ??= new(GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width, GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height);

// The default graphic profile supports resolution up to 4096x4096. The number gets even smaller in practice. Therefore, we select 3840 as the limit.
public static ScreenResolution HiDefLimitResolution { get; } = "3840x3840";

private static ScreenResolution _safeMaximumResolution = null;

/// <summary>
/// The resolution of primary monitor, or the maximum resolution supported by the graphic profile, whichever is smaller.
/// </summary>
public static ScreenResolution SafeMaximumResolution
{
get
{
#if XNA
return _safeMaximumResolution ??= HiDefLimitResolution.Fit(DesktopResolution) ? DesktopResolution : HiDefLimitResolution;
return _safeMaximumResolution ??= HiDefLimitResolution.Fits(DesktopResolution) ? DesktopResolution : HiDefLimitResolution;
#else
return _safeMaximumResolution ??= DesktopResolution;
#endif
}
}

private static ScreenResolution _safeFullScreenResolution = null;

/// <summary>
/// The maximum resolution supported by the graphic profile, or the largest full screen resolution supported by the primary monitor, whichever is smaller.
/// </summary>
public static ScreenResolution SafeFullScreenResolution => _safeFullScreenResolution ??= GetFullScreenResolutions(minWidth: 800, minHeight: 600).Max();

public static SortedSet<ScreenResolution> GetFullScreenResolutions(int minWidth, int minHeight) =>
Expand Down Expand Up @@ -123,7 +142,7 @@ public SortedSet<ScreenResolution> GetIntegerScaledResolutions(ScreenResolution
{
ScreenResolution scaledResolution = (this.Width * i, this.Height * i);

if (maxResolution.Fit(scaledResolution))
if (maxResolution.Fits(scaledResolution))
resolutions.Add(scaledResolution);
else
break;
Expand All @@ -149,6 +168,9 @@ public static SortedSet<ScreenResolution> GetWindowedResolutions(IEnumerable<Scr
if (optimalResolution.Width < minWidth || optimalResolution.Height < minHeight)
continue;

if (!maxResolution.Fits(optimalResolution))
continue;

windowedResolutions.Add(optimalResolution);
}

Expand Down
Loading
Loading