core (0.4): Implement separate display vs game scaling (thanks, NotNite!)
This commit is contained in:
parent
78ff318397
commit
ff12d438c6
16 changed files with 1126 additions and 597 deletions
5
.editorconfig
Normal file
5
.editorconfig
Normal file
|
@ -0,0 +1,5 @@
|
|||
[*.cs]
|
||||
|
||||
# CS9084: Struct member returns 'this' or other instance members by reference
|
||||
# While this would be a huge no-no in fully managed projects, we use structs to poke game memory.
|
||||
dotnet_diagnostic.CS9084.severity = silent
|
|
@ -7,6 +7,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomResolution2782", "Cus
|
|||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5ECEDCE5-D60F-4A8A-AB33-4131F5C7371C}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
Directory.Build.props = Directory.Build.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
|
|
79
CustomResolution2782/Cmds/GameResCmd.cs
Normal file
79
CustomResolution2782/Cmds/GameResCmd.cs
Normal file
|
@ -0,0 +1,79 @@
|
|||
using System.Globalization;
|
||||
using System;
|
||||
|
||||
namespace CustomResolution.Cmds;
|
||||
|
||||
public sealed class GameResCmd : Cmd
|
||||
{
|
||||
public override string Name => "gres";
|
||||
|
||||
public override string HelpMessage => $"Open / adjust the CustomResolution game scale settings.\n" +
|
||||
$"\tExamples:\n" +
|
||||
$"\tTo open the settings:\n\t\t{FullName}\n" +
|
||||
$"\tTo enable / disable it:\n\t\t{FullName} on\n\t\t{FullName} off\n\t\t{FullName} toggle\n" +
|
||||
$"\tTo set the game scale:\n\t\t{FullName} 1.5\n" +
|
||||
$"\tTo set the game resolution:\n\t\t{FullName} 1920 1080";
|
||||
|
||||
public override void Run(string arguments)
|
||||
{
|
||||
if (string.IsNullOrEmpty(arguments))
|
||||
{
|
||||
Service.PluginUI.SettingsVisible = !Service.PluginUI.SettingsVisible;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int indexOfSplit = arguments.IndexOf(' ');
|
||||
|
||||
if (indexOfSplit != -1)
|
||||
{
|
||||
if (!uint.TryParse(arguments.AsSpan(0, indexOfSplit), CultureInfo.InvariantCulture, out var width) ||
|
||||
!uint.TryParse(arguments.AsSpan(indexOfSplit + 1), CultureInfo.InvariantCulture, out var height))
|
||||
{
|
||||
Service.PrintChat("Invalid parameters.");
|
||||
return;
|
||||
}
|
||||
|
||||
Service.Config._.Game.IsScale = false;
|
||||
Service.Config._.Game.Width = width;
|
||||
Service.Config._.Game.Height = height;
|
||||
Service.Config.Save();
|
||||
|
||||
Service.PrintChat("Updated custom game resolution.");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (arguments.ToLowerInvariant())
|
||||
{
|
||||
case "on":
|
||||
Service.Config._.Game.IsEnabled = true;
|
||||
Service.Config.Save();
|
||||
Service.PrintChat("Enabled custom game resolution.");
|
||||
return;
|
||||
|
||||
case "off":
|
||||
Service.Config._.Game.IsEnabled = false;
|
||||
Service.Config.Save();
|
||||
Service.PrintChat("Disabled custom game resolution.");
|
||||
return;
|
||||
|
||||
case "toggle":
|
||||
Service.Config._.Game.IsEnabled = !Service.Config._.Game.IsEnabled;
|
||||
Service.Config.Save();
|
||||
Service.PrintChat($"{(Service.Config._.Game.IsEnabled ? "Enabled" : "Disabled")} custom game resolution.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!float.TryParse(arguments, CultureInfo.InvariantCulture, out float value))
|
||||
{
|
||||
Service.PrintChat("Invalid parameters.");
|
||||
return;
|
||||
}
|
||||
|
||||
Service.Config._.Game.IsScale = true;
|
||||
Service.Config._.Game.Scale = value;
|
||||
Service.Config.Save();
|
||||
|
||||
Service.PrintChat("Updated custom game resolution scale.");
|
||||
}
|
||||
}
|
|
@ -7,12 +7,12 @@ public sealed class MainCmd : Cmd
|
|||
{
|
||||
public override string Name => "cres";
|
||||
|
||||
public override string HelpMessage => $"Open / adjust the CustomResolution settings.\n" +
|
||||
public override string HelpMessage => $"Open / adjust the CustomResolution display scale settings.\n" +
|
||||
$"\tExamples:\n" +
|
||||
$"\tTo open the settings:\n\t\t{FullName}\n" +
|
||||
$"\tTo enable / disable it:\n\t\t{FullName} on\n\t\t{FullName} off\n\t\t{FullName} toggle\n" +
|
||||
$"\tTo set the scale:\n\t\t{FullName} 1.5\n" +
|
||||
$"\tTo set the resolution:\n\t\t{FullName} 1920 1080";
|
||||
$"\tTo set the display scale:\n\t\t{FullName} 1.5\n" +
|
||||
$"\tTo set the display resolution:\n\t\t{FullName} 1920 1080";
|
||||
|
||||
public override void Run(string arguments)
|
||||
{
|
||||
|
@ -34,33 +34,33 @@ public sealed class MainCmd : Cmd
|
|||
return;
|
||||
}
|
||||
|
||||
Service.Config.IsScale = false;
|
||||
Service.Config.Width = width;
|
||||
Service.Config.Height = height;
|
||||
Service.Config._.Display.IsScale = false;
|
||||
Service.Config._.Display.Width = width;
|
||||
Service.Config._.Display.Height = height;
|
||||
Service.Config.Save();
|
||||
|
||||
Service.PrintChat("Updated custom resolution.");
|
||||
Service.PrintChat("Updated custom display resolution.");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (arguments.ToLowerInvariant())
|
||||
{
|
||||
case "on":
|
||||
Service.Config.IsEnabled = true;
|
||||
Service.Config._.Display.IsEnabled = true;
|
||||
Service.Config.Save();
|
||||
Service.PrintChat("Enabled custom resolution.");
|
||||
Service.PrintChat("Enabled custom display resolution.");
|
||||
return;
|
||||
|
||||
case "off":
|
||||
Service.Config.IsEnabled = false;
|
||||
Service.Config._.Display.IsEnabled = false;
|
||||
Service.Config.Save();
|
||||
Service.PrintChat("Disabled custom resolution.");
|
||||
Service.PrintChat("Disabled custom display resolution.");
|
||||
return;
|
||||
|
||||
case "toggle":
|
||||
Service.Config.IsEnabled = !Service.Config.IsEnabled;
|
||||
Service.Config._.Display.IsEnabled = !Service.Config._.Display.IsEnabled;
|
||||
Service.Config.Save();
|
||||
Service.PrintChat($"{(Service.Config.IsEnabled ? "Enabled" : "Disabled")} custom resolution.");
|
||||
Service.PrintChat($"{(Service.Config._.Display.IsEnabled ? "Enabled" : "Disabled")} custom display resolution.");
|
||||
return;
|
||||
|
||||
case "debugon":
|
||||
|
@ -95,10 +95,10 @@ public sealed class MainCmd : Cmd
|
|||
return;
|
||||
}
|
||||
|
||||
Service.Config.IsScale = true;
|
||||
Service.Config.Scale = value;
|
||||
Service.Config._.Display.IsScale = true;
|
||||
Service.Config._.Display.Scale = value;
|
||||
Service.Config.Save();
|
||||
|
||||
Service.PrintChat("Updated custom resolution scale.");
|
||||
Service.PrintChat("Updated custom display resolution scale.");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,151 +1,77 @@
|
|||
using Dalamud.Configuration;
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Plugin;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace CustomResolution;
|
||||
|
||||
[Serializable]
|
||||
public class Configuration : IPluginConfiguration
|
||||
{
|
||||
public int Version { get; set; } = 0;
|
||||
public ConfigurationV1 V1 = new();
|
||||
public int Version { get; set; } = 1;
|
||||
|
||||
[JsonIgnore]
|
||||
public ref ConfigurationV1 _ => ref V1;
|
||||
|
||||
[Obsolete]
|
||||
public bool IsEnabled = true;
|
||||
[Obsolete]
|
||||
public bool IsScale = true;
|
||||
[Obsolete]
|
||||
public float Scale = 1f;
|
||||
[Obsolete]
|
||||
public uint Width = 1024;
|
||||
[Obsolete]
|
||||
public uint Height = 1024;
|
||||
|
||||
[Obsolete]
|
||||
public VirtualKey HotkeyKey = VirtualKey.NO_KEY;
|
||||
[Obsolete]
|
||||
public ModifierKey HotkeyModifier = ModifierKey.NONE;
|
||||
|
||||
[Obsolete]
|
||||
public DXVKDWMHackMode DXVKDWMHackMode = DXVKDWMHackMode.Off;
|
||||
|
||||
[Obsolete]
|
||||
public MinSizeMode MinSizeMode = MinSizeMode.Unchanged;
|
||||
|
||||
[NonSerialized]
|
||||
private IDalamudPluginInterface? pluginInterface;
|
||||
private IDalamudPluginInterface pluginInterface = null!;
|
||||
|
||||
internal void Initialize(IDalamudPluginInterface pluginInterface)
|
||||
{
|
||||
this.pluginInterface = pluginInterface;
|
||||
Upgrade();
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
pluginInterface!.SavePluginConfig(this);
|
||||
_.Display.Clamp();
|
||||
_.Game.Clamp();
|
||||
pluginInterface.SavePluginConfig(this);
|
||||
Service.PluginUI.UpdateFromConfig();
|
||||
}
|
||||
}
|
||||
|
||||
// Must explicitly map to integer values, blame Newtonsoft.JSON
|
||||
public enum DXVKDWMHackMode
|
||||
{
|
||||
Off = 0,
|
||||
UnsetPopup = 1,
|
||||
SetClientEdgeResize = 3,
|
||||
SetClientEdgeBorder = 2,
|
||||
}
|
||||
|
||||
public static class DXVKDWMHackModeExt
|
||||
{
|
||||
public static string ToHumanNameString(this DXVKDWMHackMode mode) => mode switch
|
||||
private void Upgrade()
|
||||
{
|
||||
DXVKDWMHackMode.Off => "Off",
|
||||
DXVKDWMHackMode.UnsetPopup => "Best: -WS_POPUP",
|
||||
DXVKDWMHackMode.SetClientEdgeResize => "+WS_EX_CLIENTEDGE (resize)",
|
||||
DXVKDWMHackMode.SetClientEdgeBorder => "+WS_EX_CLIENTEDGE (border)",
|
||||
_ => mode.ToString(),
|
||||
};
|
||||
|
||||
public static string? ToHumanInfoString(this DXVKDWMHackMode mode) => mode switch
|
||||
#pragma warning disable CS0612 // Type or member is obsolete, but we want to migrate from it.
|
||||
#pragma warning disable CS0618 // Type or member is obsolete, but we want to migrate from it.
|
||||
if (Version == 0)
|
||||
{
|
||||
DXVKDWMHackMode.UnsetPopup => "Least intrusive option, try this first.\nWorks best with NVIDIA GPUs.",
|
||||
DXVKDWMHackMode.SetClientEdgeResize => "Extends the game window 1 pixel to the bottom.\nDon't use if it makes text look blurry!",
|
||||
DXVKDWMHackMode.SetClientEdgeBorder => "Adds a 1 pixel border around the game.",
|
||||
_ => null
|
||||
};
|
||||
|
||||
public static bool IsUnsetPopup(this DXVKDWMHackMode mode) => mode == DXVKDWMHackMode.UnsetPopup;
|
||||
|
||||
public static bool IsSetClientEdge(this DXVKDWMHackMode mode) => mode switch
|
||||
{
|
||||
DXVKDWMHackMode.SetClientEdgeResize => true,
|
||||
DXVKDWMHackMode.SetClientEdgeBorder => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
public enum ModifierKey
|
||||
{
|
||||
NONE = 0,
|
||||
CTRL = 1,
|
||||
ALT = 2,
|
||||
SHIFT = 4
|
||||
}
|
||||
|
||||
public static class VirtualKeyExt
|
||||
{
|
||||
private static readonly Dictionary<VirtualKey, string> _strings = new();
|
||||
|
||||
static VirtualKeyExt()
|
||||
{
|
||||
foreach (var field in typeof(VirtualKey).GetFields(BindingFlags.Public | BindingFlags.Static))
|
||||
{
|
||||
var key = (VirtualKey) field.GetValue(null)!;
|
||||
_strings[key] = key.GetFancyName();
|
||||
Service.PluginLog.Info("Migrating config V0 to V1");
|
||||
V1 = new();
|
||||
// V1.Display.IsEnabled = IsEnabled; // Was enabled by default before, but let's disable.
|
||||
V1.Display.IsScale = IsScale;
|
||||
V1.Display.Scale = Scale;
|
||||
V1.Display.Width = Width;
|
||||
V1.Display.Height = Height;
|
||||
V1.Display.HotkeyKey = HotkeyKey;
|
||||
V1.Display.HotkeyModifier = HotkeyModifier;
|
||||
V1.DXVKDWMHackMode = DXVKDWMHackMode;
|
||||
V1.MinSizeMode = MinSizeMode;
|
||||
V1.Display.Clamp();
|
||||
Version++;
|
||||
}
|
||||
#pragma warning restore CS0612
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
public static string ToHumanNameString(this VirtualKey mode)
|
||||
{
|
||||
return _strings[mode];
|
||||
}
|
||||
|
||||
public static string? ToHumanInfoString(this VirtualKey mode) => mode switch
|
||||
{
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public static class ModifierKeyExt
|
||||
{
|
||||
public static string ToHumanNameString(this ModifierKey mode) => mode switch
|
||||
{
|
||||
ModifierKey.NONE => "None",
|
||||
ModifierKey.CTRL => "Ctrl",
|
||||
ModifierKey.ALT => "Alt",
|
||||
ModifierKey.SHIFT => "Shift",
|
||||
_ => mode.ToString(),
|
||||
};
|
||||
|
||||
public static string? ToHumanInfoString(this ModifierKey mode) => mode switch
|
||||
{
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public enum MinSizeMode
|
||||
{
|
||||
Unchanged = 0,
|
||||
Unlocked = 1
|
||||
}
|
||||
|
||||
public static class MinSizeModeExt
|
||||
{
|
||||
public static string ToHumanNameString(this MinSizeMode mode) => mode switch
|
||||
{
|
||||
MinSizeMode.Unchanged => "Unchanged (1024x768)",
|
||||
MinSizeMode.Unlocked => "Unlocked",
|
||||
_ => mode.ToString(),
|
||||
};
|
||||
|
||||
public static string? ToHumanInfoString(this MinSizeMode mode) => mode switch
|
||||
{
|
||||
MinSizeMode.Unchanged => "The game normally doesn't allow resizing to smaller than 1024x768.",
|
||||
MinSizeMode.Unlocked => "Allow resizing the game in windowed mode to smaller than 1024x768. Works best with scaling over 100%.",
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
|
147
CustomResolution2782/ConfigurationEnums.cs
Normal file
147
CustomResolution2782/ConfigurationEnums.cs
Normal file
|
@ -0,0 +1,147 @@
|
|||
using Dalamud.Configuration;
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Plugin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace CustomResolution;
|
||||
|
||||
|
||||
// Must explicitly map to integer values, blame Newtonsoft.JSON
|
||||
public enum DXVKDWMHackMode
|
||||
{
|
||||
Off = 0,
|
||||
UnsetPopup = 1,
|
||||
SetClientEdgeResize = 3,
|
||||
SetClientEdgeBorder = 2,
|
||||
}
|
||||
|
||||
public static class DXVKDWMHackModeExt
|
||||
{
|
||||
public static string ToHumanNameString(this DXVKDWMHackMode mode) => mode switch
|
||||
{
|
||||
DXVKDWMHackMode.Off => "Off",
|
||||
DXVKDWMHackMode.UnsetPopup => "Best: -WS_POPUP",
|
||||
DXVKDWMHackMode.SetClientEdgeResize => "+WS_EX_CLIENTEDGE (resize)",
|
||||
DXVKDWMHackMode.SetClientEdgeBorder => "+WS_EX_CLIENTEDGE (border)",
|
||||
_ => mode.ToString(),
|
||||
};
|
||||
|
||||
public static string? ToHumanInfoString(this DXVKDWMHackMode mode) => mode switch
|
||||
{
|
||||
DXVKDWMHackMode.UnsetPopup => "Least intrusive option, try this first.\nWorks best with NVIDIA GPUs.",
|
||||
DXVKDWMHackMode.SetClientEdgeResize => "Extends the game window 1 pixel to the bottom.\nDon't use if it makes text look blurry!",
|
||||
DXVKDWMHackMode.SetClientEdgeBorder => "Adds a 1 pixel border around the game.",
|
||||
_ => null
|
||||
};
|
||||
|
||||
public static bool IsUnsetPopup(this DXVKDWMHackMode mode) => mode == DXVKDWMHackMode.UnsetPopup;
|
||||
|
||||
public static bool IsSetClientEdge(this DXVKDWMHackMode mode) => mode switch
|
||||
{
|
||||
DXVKDWMHackMode.SetClientEdgeResize => true,
|
||||
DXVKDWMHackMode.SetClientEdgeBorder => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
public enum ModifierKey
|
||||
{
|
||||
NONE = 0,
|
||||
CTRL = 1,
|
||||
ALT = 2,
|
||||
SHIFT = 4
|
||||
}
|
||||
|
||||
public static class VirtualKeyExt
|
||||
{
|
||||
private static readonly Dictionary<VirtualKey, string> _strings = new();
|
||||
|
||||
static VirtualKeyExt()
|
||||
{
|
||||
foreach (var field in typeof(VirtualKey).GetFields(BindingFlags.Public | BindingFlags.Static))
|
||||
{
|
||||
var key = (VirtualKey) field.GetValue(null)!;
|
||||
_strings[key] = key.GetFancyName();
|
||||
}
|
||||
}
|
||||
|
||||
public static string ToHumanNameString(this VirtualKey mode)
|
||||
{
|
||||
return _strings[mode];
|
||||
}
|
||||
|
||||
public static string? ToHumanInfoString(this VirtualKey mode) => mode switch
|
||||
{
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public static class ModifierKeyExt
|
||||
{
|
||||
public static string ToHumanNameString(this ModifierKey mode) => mode switch
|
||||
{
|
||||
ModifierKey.NONE => "None",
|
||||
ModifierKey.CTRL => "Ctrl",
|
||||
ModifierKey.ALT => "Alt",
|
||||
ModifierKey.SHIFT => "Shift",
|
||||
_ => mode.ToString(),
|
||||
};
|
||||
|
||||
public static string? ToHumanInfoString(this ModifierKey mode) => mode switch
|
||||
{
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public enum MinSizeMode
|
||||
{
|
||||
Unchanged = 0,
|
||||
Unlocked = 1
|
||||
}
|
||||
|
||||
public static class MinSizeModeExt
|
||||
{
|
||||
public static string ToHumanNameString(this MinSizeMode mode) => mode switch
|
||||
{
|
||||
MinSizeMode.Unchanged => "Unchanged (1024x768)",
|
||||
MinSizeMode.Unlocked => "Unlocked",
|
||||
_ => mode.ToString(),
|
||||
};
|
||||
|
||||
public static string? ToHumanInfoString(this MinSizeMode mode) => mode switch
|
||||
{
|
||||
MinSizeMode.Unchanged => "The game normally doesn't allow resizing to smaller than 1024x768.",
|
||||
MinSizeMode.Unlocked => "Allow resizing the game in windowed mode to smaller than 1024x768. Works best with scaling over 100%.",
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public enum ResolutionScalingMode
|
||||
{
|
||||
Fast = 0,
|
||||
FSR = 1,
|
||||
DLSS = 2
|
||||
}
|
||||
|
||||
public static class ResolutionScalingModeExt
|
||||
{
|
||||
public static string ToHumanNameString(this ResolutionScalingMode mode) => mode switch
|
||||
{
|
||||
ResolutionScalingMode.Fast => "Fast Pixelation",
|
||||
ResolutionScalingMode.FSR => "AMD FSR",
|
||||
ResolutionScalingMode.DLSS => "NVIDIA DLSS",
|
||||
_ => mode.ToString(),
|
||||
};
|
||||
|
||||
public static string? ToHumanInfoString(this ResolutionScalingMode mode) => mode switch
|
||||
{
|
||||
ResolutionScalingMode.Fast => "Lowest quality option which results in blurry pixelation.",
|
||||
ResolutionScalingMode.FSR => "Upscaling which works on any GPU for low performance overhead.",
|
||||
ResolutionScalingMode.DLSS => "DLSS in FFXIV is buggy, even without any plugins.",
|
||||
_ => null
|
||||
};
|
||||
|
||||
public static bool IsUnsupported(this ResolutionScalingMode mode) => mode == ResolutionScalingMode.DLSS;
|
||||
}
|
66
CustomResolution2782/ConfigurationV1.cs
Normal file
66
CustomResolution2782/ConfigurationV1.cs
Normal file
|
@ -0,0 +1,66 @@
|
|||
using Dalamud.Configuration;
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Plugin;
|
||||
using System;
|
||||
|
||||
namespace CustomResolution;
|
||||
|
||||
[Serializable]
|
||||
public struct ConfigurationV1
|
||||
{
|
||||
public SizeConfig Display = new();
|
||||
public SizeConfig Game = new();
|
||||
|
||||
public ResolutionScalingMode ResolutionScalingMode = ResolutionScalingMode.FSR;
|
||||
public DXVKDWMHackMode DXVKDWMHackMode = DXVKDWMHackMode.Off;
|
||||
public MinSizeMode MinSizeMode = MinSizeMode.Unchanged;
|
||||
|
||||
public ConfigurationV1()
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct SizeConfig
|
||||
{
|
||||
public bool IsEnabled = false;
|
||||
public bool IsScale = true;
|
||||
public float Scale = 1f;
|
||||
[NonSerialized]
|
||||
public unsafe fixed int WH[2];
|
||||
public unsafe uint Width
|
||||
{
|
||||
get => (uint) WH[0];
|
||||
set => WH[0] = (int) value;
|
||||
}
|
||||
public unsafe uint Height
|
||||
{
|
||||
get => (uint) WH[1];
|
||||
set => WH[1] = (int) value;
|
||||
}
|
||||
|
||||
public VirtualKey HotkeyKey = VirtualKey.NO_KEY;
|
||||
public ModifierKey HotkeyModifier = ModifierKey.NONE;
|
||||
|
||||
public SizeConfig()
|
||||
{
|
||||
Width = 1024;
|
||||
Height = 1024;
|
||||
}
|
||||
|
||||
public void Clamp()
|
||||
{
|
||||
if (Width < 256)
|
||||
{
|
||||
Width = 256;
|
||||
}
|
||||
if (Height < 256)
|
||||
{
|
||||
Height = 256;
|
||||
}
|
||||
if (Scale < 0.001f)
|
||||
{
|
||||
Scale = 0.001f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
<PropertyGroup>
|
||||
<Authors>0x0ade</Authors>
|
||||
<Company></Company>
|
||||
<Version>0.3.2.0</Version>
|
||||
<Version>0.4.0.0</Version>
|
||||
<Description></Description>
|
||||
<Copyright></Copyright>
|
||||
<PackageProjectUrl></PackageProjectUrl>
|
||||
|
|
407
CustomResolution2782/DisplaySizeState.cs
Normal file
407
CustomResolution2782/DisplaySizeState.cs
Normal file
|
@ -0,0 +1,407 @@
|
|||
using CustomResolution.Hooks;
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Game.Config;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||
using FFXIVClientStructs.Interop;
|
||||
using ImGuiNET;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using TerraFX.Interop.Windows;
|
||||
using static TerraFX.Interop.Windows.Windows;
|
||||
|
||||
namespace CustomResolution;
|
||||
|
||||
public unsafe class DisplaySizeState : IDisposable
|
||||
{
|
||||
private bool _wasEnabled = false;
|
||||
private HWND _currentHwnd;
|
||||
private bool _currentHwndMismatch = false;
|
||||
private RECT _currentClientRect;
|
||||
private DXVKDWMHackMode _currentDXVKDWMHackMode = DXVKDWMHackMode.Off;
|
||||
private bool _currentlyFakeResize = false;
|
||||
private bool _requestedResolutionChange = false;
|
||||
|
||||
public HWND CurrentHWND => _currentHwnd;
|
||||
|
||||
public uint CurrentWidth { get; private set; }
|
||||
public uint CurrentHeight { get; private set; }
|
||||
public uint CurrentWindowWidth { get; private set; }
|
||||
public uint CurrentWindowHeight { get; private set; }
|
||||
|
||||
public bool CurrentBorderlessFullscreen { get; private set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Service.Framework.RunOnFrameworkThread(Update);
|
||||
}
|
||||
|
||||
public void ConvertCoordsWinToGame(ref int x, ref int y)
|
||||
{
|
||||
if (CurrentWidth == CurrentWindowWidth && CurrentHeight == CurrentWindowHeight)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var scaleX = CurrentWidth / (float) CurrentWindowWidth;
|
||||
var scaleY = CurrentHeight / (float) CurrentWindowHeight;
|
||||
|
||||
x = (int) Math.Round(x * scaleX);
|
||||
y = (int) Math.Round(y * scaleY);
|
||||
}
|
||||
|
||||
public void ConvertCoordsGameToWin(ref int x, ref int y)
|
||||
{
|
||||
if (CurrentWidth == CurrentWindowWidth && CurrentHeight == CurrentWindowHeight)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var scaleX = CurrentWindowWidth / (float) CurrentWidth;
|
||||
var scaleY = CurrentWindowHeight / (float) CurrentHeight;
|
||||
|
||||
x = (int) Math.Round(x * scaleX);
|
||||
y = (int) Math.Round(y * scaleY);
|
||||
}
|
||||
|
||||
public void ConvertCoordsGlobalToGame(ref int x, ref int y)
|
||||
{
|
||||
if (CurrentWidth == CurrentWindowWidth && CurrentHeight == CurrentWindowHeight)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var scaleX = CurrentWidth / (float) CurrentWindowWidth;
|
||||
var scaleY = CurrentHeight / (float) CurrentWindowHeight;
|
||||
|
||||
var p = new POINT(x, y);
|
||||
|
||||
Service.CursorPosHooks.ScreenToClientOrig(_currentHwnd, &p);
|
||||
|
||||
p.x = (int) Math.Round(p.x * scaleX);
|
||||
p.y = (int) Math.Round(p.y * scaleY);
|
||||
|
||||
Service.CursorPosHooks.ClientToScreenOrig(_currentHwnd, &p);
|
||||
|
||||
x = p.x;
|
||||
y = p.y;
|
||||
}
|
||||
|
||||
public void ConvertCoordsGameToGlobal(ref int x, ref int y)
|
||||
{
|
||||
if (CurrentWidth == CurrentWindowWidth && CurrentHeight == CurrentWindowHeight)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var scaleX = CurrentWindowWidth / (float) CurrentWidth;
|
||||
var scaleY = CurrentWindowHeight / (float) CurrentHeight;
|
||||
|
||||
var p = new POINT(x, y);
|
||||
|
||||
Service.CursorPosHooks.ScreenToClientOrig(_currentHwnd, &p);
|
||||
|
||||
p.x = (int) Math.Round(p.x * scaleX);
|
||||
p.y = (int) Math.Round(p.y * scaleY);
|
||||
|
||||
Service.CursorPosHooks.ClientToScreenOrig(_currentHwnd, &p);
|
||||
|
||||
x = p.x;
|
||||
y = p.y;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
bool unloading = Service.Plugin.Unloading;
|
||||
|
||||
ref var cfg = ref Service.Config._.Display;
|
||||
|
||||
var dev = (DeviceEx*) Device.Instance();
|
||||
var framework = Framework.Instance();
|
||||
var win = framework->GameWindow;
|
||||
|
||||
_currentHwnd = (HWND) win->WindowHandle;
|
||||
|
||||
// As a safety measure, don't mess with the structs if we're reading garbage.
|
||||
// This isn't perfect, but it's better than nothing.
|
||||
if (_currentHwnd != dev->hWnd)
|
||||
{
|
||||
if (!_currentHwndMismatch)
|
||||
{
|
||||
_currentHwndMismatch = true;
|
||||
Service.PluginLog.Error($"HWND MISMATCH between GameWindow and Device: 0x{(long) (IntPtr) (_currentHwnd):X16} vs 0x{(long) dev->hWnd:X16}");
|
||||
Service.PluginLog.Info($"dev is at: 0x{(long) (IntPtr) dev:X16}");
|
||||
CheckHWND("GameWindow", _currentHwnd);
|
||||
CheckHWND("Device", (HWND) dev->hWnd);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_currentHwndMismatch = false;
|
||||
|
||||
fixed (RECT* currentClientRectPtr = &_currentClientRect)
|
||||
{
|
||||
Service.WindowRectHooks.GetClientRectOrig(_currentHwnd, currentClientRectPtr);
|
||||
}
|
||||
|
||||
var rectWidth = _currentClientRect.right - _currentClientRect.left;
|
||||
var rectHeight = _currentClientRect.bottom - _currentClientRect.top;
|
||||
|
||||
if ((rectWidth <= 0 || rectHeight <= 0) && !unloading)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var enabled = !unloading && cfg.IsEnabled;
|
||||
|
||||
uint width, height;
|
||||
|
||||
if (cfg.IsScale || !enabled)
|
||||
{
|
||||
var scale = enabled ? cfg.Scale : 1f;
|
||||
|
||||
width = (uint) Math.Round(rectWidth * scale);
|
||||
height = (uint) Math.Round(rectHeight * scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
width = cfg.Width;
|
||||
height = cfg.Height;
|
||||
}
|
||||
|
||||
if (width < 256)
|
||||
{
|
||||
width = 256;
|
||||
}
|
||||
|
||||
if (height < 256)
|
||||
{
|
||||
height = 256;
|
||||
}
|
||||
|
||||
_requestedResolutionChange |= dev->RequestResolutionChange != 0;
|
||||
|
||||
if (!_currentlyFakeResize && (unloading || enabled || _wasEnabled || _requestedResolutionChange) &&
|
||||
(width != dev->Width || height != dev->Height))
|
||||
{
|
||||
Service.PluginLog.Info($"Changing graphics resolution from {dev->Width} x {dev->Height} to {width} x {height}");
|
||||
var mode = Service.DebugConfig.ForceSizeMode;
|
||||
if (_requestedResolutionChange)
|
||||
{
|
||||
// Let's try to be cautious about other plugins (f.e. SimpleTweaks) changing the game resolution.
|
||||
Service.PluginLog.Info($"Game resolution was changed externally - forcing resize via device, not window resize.");
|
||||
mode = ForceSizeMode.LegacyRequestResolutionChange;
|
||||
_requestedResolutionChange = false;
|
||||
}
|
||||
switch (mode)
|
||||
{
|
||||
case ForceSizeMode.Skip:
|
||||
break;
|
||||
case ForceSizeMode.LegacyRequestResolutionChange:
|
||||
dev->NewWidth = width;
|
||||
dev->NewHeight = height;
|
||||
dev->RequestResolutionChange = 1;
|
||||
Service.PluginLog.Info($"Changing game window from {win->WindowWidth} x {win->WindowHeight} to {width} x {height}");
|
||||
win->WindowWidth = (int) width;
|
||||
win->WindowHeight = (int) height;
|
||||
break;
|
||||
case ForceSizeMode.FakeWindowResize:
|
||||
var adjustedClientRect = new RECT(0, 0, rectWidth, rectHeight);
|
||||
AdjustWindowRect(&adjustedClientRect, (uint) GetWindowLongPtr(_currentHwnd, GWL.GWL_STYLE), false);
|
||||
var adjustedWidth = adjustedClientRect.right - adjustedClientRect.left;
|
||||
var adjustedHeight = adjustedClientRect.bottom - adjustedClientRect.top;
|
||||
Service.PluginLog.Info($"Resizing window to {adjustedWidth} x {adjustedHeight}");
|
||||
try
|
||||
{
|
||||
_currentlyFakeResize = true;
|
||||
SendMessage(_currentHwnd, WM.WM_ENTERSIZEMOVE, 0, 0);
|
||||
SendMessage(_currentHwnd, WM.WM_SIZE, SIZE_MAXSHOW, WndProcHook.SizeToParam((uint) adjustedWidth, (uint) adjustedHeight));
|
||||
SendMessage(_currentHwnd, WM.WM_PAINT, 0, 0);
|
||||
SendMessage(_currentHwnd, WM.WM_EXITSIZEMOVE, 0, 0);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_currentlyFakeResize = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Service.PluginLog.Error($"Unknown ForceSizeMode: {Service.DebugConfig.ForceSizeMode}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_wasEnabled != enabled)
|
||||
{
|
||||
Service.PluginLog.Info($"Changed state to: {enabled}");
|
||||
_wasEnabled = enabled;
|
||||
}
|
||||
|
||||
//Service.PluginLog.Debug($"NewWidth 0x{(long) (IntPtr) (&dev->NewWidth):X16}");
|
||||
//Service.PluginLog.Debug($"GameWindow->Width 0x{(long) (IntPtr) (&win->WindowWidth):X16}");
|
||||
|
||||
//Service.PluginLog.Info($"Game window at {win->WindowWidth} x {win->WindowHeight}");
|
||||
|
||||
//Service.PluginLog.Info($"AAA {Service.GameConfig.System.GetUInt(nameof(SystemConfigOption.UiHighScale))}");
|
||||
|
||||
CurrentBorderlessFullscreen = win->Borderless;
|
||||
|
||||
if (Service.Config._.DXVKDWMHackMode != DXVKDWMHackMode.Off && !unloading)
|
||||
{
|
||||
SetDXVKDWMHack(Service.Config._.DXVKDWMHackMode);
|
||||
}
|
||||
else if (Service.Config._.DXVKDWMHackMode == DXVKDWMHackMode.Off && _currentDXVKDWMHackMode != DXVKDWMHackMode.Off)
|
||||
{
|
||||
SetDXVKDWMHack(DXVKDWMHackMode.Off);
|
||||
}
|
||||
|
||||
CurrentWidth = width;
|
||||
CurrentHeight = height;
|
||||
CurrentWindowWidth = (uint) rectWidth;
|
||||
CurrentWindowHeight = (uint) rectHeight;
|
||||
}
|
||||
|
||||
private void SetDXVKDWMHack(DXVKDWMHackMode mode)
|
||||
{
|
||||
/* Default maximized style / exstyle is 0x95000000 / 0.
|
||||
* WS.WS_POPUP | WS.WS_VISIBLE | WS.WS_CLIPSIBLINGS | WS.WS_MAXIMIZE
|
||||
* Default windowed style / exstyle is 0x14CF0000 / 0.
|
||||
* WS.WS_VISIBLE | WS.WS_CLIPSIBLINGS | WS.WS_CAPTION | WS.WS_SYSMENU | WS.WS_THICKFRAME | WS.WS_MINIMIZEBOX | WS.WS_MAXIMIZEBOX
|
||||
*/
|
||||
var styleOrig = (uint) GetWindowLong(_currentHwnd, GWL.GWL_STYLE);
|
||||
var exstyleOrig = (uint) GetWindowLong(_currentHwnd, GWL.GWL_EXSTYLE);
|
||||
|
||||
var style = styleOrig;
|
||||
var exstyle = exstyleOrig;
|
||||
|
||||
var fullscreen = (style & WS.WS_SYSMENU) == 0;
|
||||
|
||||
if (Service.DebugConfig.IsDebug)
|
||||
{
|
||||
Service.PluginLog.Info("--------");
|
||||
Service.PluginLog.Info($"STYLE: 0x{style:X8}");
|
||||
Service.PluginLog.Info($"EXSTYLE: 0x{GetWindowLong(_currentHwnd, GWL.GWL_EXSTYLE):X8}");
|
||||
|
||||
Span<ushort> name = stackalloc ushort[256];
|
||||
GetClassName(_currentHwnd, name.GetPointer(0), name.Length);
|
||||
WNDCLASSEXW wce;
|
||||
GetClassInfoEx(GetModuleHandle(null), name.GetPointer(0), &wce);
|
||||
|
||||
Service.PluginLog.Info($"CLASS: {new string((char*) name.GetPointer(0))}");
|
||||
Service.PluginLog.Info($"CLASS.style: 0x{wce.style:X8}");
|
||||
}
|
||||
|
||||
if (fullscreen)
|
||||
{
|
||||
if (mode == DXVKDWMHackMode.UnsetPopup)
|
||||
{
|
||||
style &= ~WS.WS_POPUP;
|
||||
}
|
||||
else
|
||||
{
|
||||
style |= WS.WS_POPUP;
|
||||
}
|
||||
}
|
||||
|
||||
if (fullscreen && mode.IsSetClientEdge())
|
||||
{
|
||||
exstyle |= WS.WS_EX_CLIENTEDGE;
|
||||
exstyle |= WS.WS_EX_COMPOSITED;
|
||||
}
|
||||
else
|
||||
{
|
||||
exstyle &= ~(uint) WS.WS_EX_CLIENTEDGE;
|
||||
exstyle &= ~(uint) WS.WS_EX_COMPOSITED;
|
||||
}
|
||||
|
||||
if (Service.DebugConfig.IsDebug)
|
||||
{
|
||||
Service.PluginLog.Info($"NEWSTYLE: 0x{style:X8}");
|
||||
Service.PluginLog.Info($"NEWEXSTYLE: 0x{exstyle:X8}");
|
||||
}
|
||||
|
||||
if (style != styleOrig || exstyle != exstyleOrig || _currentDXVKDWMHackMode != mode)
|
||||
{
|
||||
if (Service.DebugConfig.IsDebug)
|
||||
{
|
||||
Service.PluginLog.Info("UPDATE");
|
||||
}
|
||||
|
||||
SetWindowLong(_currentHwnd, GWL.GWL_STYLE, (int) style);
|
||||
SetWindowLong(_currentHwnd, GWL.GWL_EXSTYLE, (int) exstyle);
|
||||
|
||||
SetWindowPos(_currentHwnd, HWND.NULL, 0, 0, 0, 0, SWP.SWP_NOZORDER | SWP.SWP_NOMOVE | SWP.SWP_NOSIZE | SWP.SWP_NOACTIVATE | SWP.SWP_DRAWFRAME);
|
||||
ShowWindow(_currentHwnd, SW.SW_SHOW);
|
||||
}
|
||||
else if (Service.DebugConfig.IsDebug)
|
||||
{
|
||||
Service.PluginLog.Info("SAME");
|
||||
}
|
||||
|
||||
_currentDXVKDWMHackMode = mode;
|
||||
}
|
||||
|
||||
private void CheckHWND(string from, HWND hwnd)
|
||||
{
|
||||
RECT rect = default;
|
||||
if (!GetClientRect(hwnd, &rect))
|
||||
{
|
||||
Service.PluginLog.Info($"{from} is sus: {Marshal.GetLastPInvokeErrorMessage()}");
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
private struct DeviceEx
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public Device _;
|
||||
|
||||
public ref byte RequestResolutionChange => ref _.RequestResolutionChange;
|
||||
|
||||
public ref uint Width => ref _.Width;
|
||||
|
||||
public ref uint Height => ref _.Height;
|
||||
|
||||
#if CRES_CLEAN || true
|
||||
public ref void* hWnd => ref _.hWnd;
|
||||
|
||||
public ref uint NewWidth => ref _.NewWidth;
|
||||
|
||||
public ref uint NewHeight => ref _.NewHeight;
|
||||
#else
|
||||
// 7.2 adds 1B8 before hWnd (previously 820, now 9D8)
|
||||
[FieldOffset(0x9D8)]
|
||||
public unsafe void* hWnd;
|
||||
|
||||
[FieldOffset(0x9D8 + 0x10)]
|
||||
public uint NewWidth;
|
||||
|
||||
[FieldOffset(0x9D8 + 0x14)]
|
||||
public uint NewHeight;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public enum ForceSizeMode
|
||||
{
|
||||
Skip = -1,
|
||||
|
||||
/// <summary>
|
||||
/// Fake a window resize event.
|
||||
/// </summary>
|
||||
FakeWindowResize = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Legacy mode, went through more initial testing, works well except for conflicts with other plugins.
|
||||
/// Might revert or remove depending on how FakeWindowResize testing / fixups proceed.
|
||||
///
|
||||
/// Update the graphics device width / height and set RequestResolutionChange.
|
||||
/// This is the same method that other plugins such as SimpleTweaks or XIVWindowResizer use.
|
||||
/// </summary>
|
||||
LegacyRequestResolutionChange = 1
|
||||
}
|
149
CustomResolution2782/GameSizeState.cs
Normal file
149
CustomResolution2782/GameSizeState.cs
Normal file
|
@ -0,0 +1,149 @@
|
|||
using CustomResolution.Hooks;
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Game.Config;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||
using FFXIVClientStructs.Interop;
|
||||
using ImGuiNET;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Xml.Linq;
|
||||
using TerraFX.Interop.Windows;
|
||||
using static TerraFX.Interop.Windows.Windows;
|
||||
|
||||
namespace CustomResolution;
|
||||
|
||||
public unsafe class GameSizeState : IDisposable
|
||||
{
|
||||
private bool _wasEnabled = false;
|
||||
|
||||
private bool? _forceDynRezoConfig;
|
||||
|
||||
public bool ConfigDynRezo { get; private set; }
|
||||
public ResolutionScalingMode ConfigGraphicsRezoType { get; private set; }
|
||||
public float ConfigGraphicsRezoScale { get; private set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Service.Framework.RunOnFrameworkThread(Update);
|
||||
}
|
||||
|
||||
public void ForceDynRezoConfig(bool value)
|
||||
{
|
||||
_forceDynRezoConfig = value;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
ref var cfg = ref Service.Config._.Game;
|
||||
|
||||
if (_forceDynRezoConfig.HasValue)
|
||||
{
|
||||
Service.GameConfig.System.Set(SystemConfigOption.DynamicRezoType.ToString(), _forceDynRezoConfig.Value ? 1U : 0U);
|
||||
_forceDynRezoConfig = null;
|
||||
}
|
||||
|
||||
ConfigDynRezo = Service.GameConfig.System.GetUInt(SystemConfigOption.DynamicRezoType.ToString()) != 0U;
|
||||
// GameConfig starts with FSR at 0; GraphicsConfig starts with FSR at 1
|
||||
ConfigGraphicsRezoType = (ResolutionScalingMode) (Service.GameConfig.System.GetUInt(SystemConfigOption.GraphicsRezoUpscaleType.ToString()) + 1);
|
||||
ConfigGraphicsRezoScale = Service.GameConfig.System.GetUInt(SystemConfigOption.GraphicsRezoScale.ToString()) / 100f;
|
||||
|
||||
var dev = Device.Instance();
|
||||
var gfx = GraphicsConfig.Instance();
|
||||
var rtm = (RenderTargetManagerEx*) RenderTargetManager.Instance();
|
||||
|
||||
//Service.PluginLog.Debug($"GraphicsRezoScale 0x{(long) (IntPtr) (&gfx->GraphicsRezoScale):X16}");
|
||||
//Service.PluginLog.Debug($"RTM 0x{(long) (IntPtr) rtm:X16}");
|
||||
|
||||
bool unloading = Service.Plugin.Unloading;
|
||||
bool enabled = !unloading && cfg.IsEnabled;
|
||||
|
||||
if (enabled || _wasEnabled)
|
||||
{
|
||||
gfx->GraphicsRezoScale = enabled ? cfg.Scale : ConfigGraphicsRezoScale;
|
||||
gfx->DynamicRezoEnable = (byte) (ConfigDynRezo || enabled ? 1 : 0);
|
||||
if (enabled)
|
||||
{
|
||||
gfx->GraphicsRezoUpscaleType = (byte) (cfg.Scale <= 1f ? Service.Config._.ResolutionScalingMode : ResolutionScalingMode.Fast);
|
||||
}
|
||||
else
|
||||
{
|
||||
gfx->GraphicsRezoUpscaleType = (byte) ConfigGraphicsRezoType;
|
||||
}
|
||||
_wasEnabled = enabled;
|
||||
}
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
rtm->DynamicResolutionMinimumHeight = rtm->DynamicResolutionMaximumHeight;
|
||||
}
|
||||
|
||||
/*
|
||||
rtm->Resolution_Width = (ushort) (dev->Width * gfx->GraphicsRezoScale);
|
||||
rtm->Resolution_Height = (ushort) (dev->Height * gfx->GraphicsRezoScale);
|
||||
rtm->DynamicResolutionActualTargetHeight = (ushort) (dev->Height * gfx->GraphicsRezoScale);
|
||||
rtm->DynamicResolutionTargetHeight = (ushort) (dev->Height * gfx->GraphicsRezoScale);
|
||||
rtm->DynamicResolutionMaximumHeight = (ushort) (dev->Height * gfx->GraphicsRezoScale);
|
||||
rtm->DynamicResolutionMinimumHeight = (ushort) (dev->Height * gfx->GraphicsRezoScale);
|
||||
*/
|
||||
|
||||
#if false
|
||||
Service.PluginLog.Debug($"DR {gfx->DynamicRezoEnable}");
|
||||
Service.PluginLog.Debug($"RTM {rtm->Resolution_Width} x {rtm->Resolution_Height}");
|
||||
Service.PluginLog.Debug($"RTM H {rtm->DynamicResolutionActualTargetHeight} {rtm->DynamicResolutionTargetHeight} {rtm->DynamicResolutionMaximumHeight} {rtm->DynamicResolutionMinimumHeight}");
|
||||
Service.PluginLog.Debug($"RTM S {rtm->GraphicsRezoScales[0]} {rtm->GraphicsRezoScales[1]} {rtm->GraphicsRezoScales[2]} {rtm->GraphicsRezoScales[3]} {rtm->GraphicsRezoScales[4]}");
|
||||
#endif
|
||||
}
|
||||
|
||||
// RenderTargetManager is updated very sporadically, and some fields we need flew out
|
||||
// https://github.com/aers/FFXIVClientStructs/commit/589df2aa5cd9c98b4d62269034cd6da903f49b5f#diff-8e7d9b03cb91cb07a8d7b463b5be4672793a328703bde393e7acd890822a72cf
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
private struct RenderTargetManagerEx
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public RenderTargetManager _;
|
||||
|
||||
#if CRES_CLEAN || false
|
||||
public ref uint Resolution_Width => ref _.Resolution_Width;
|
||||
public ref uint Resolution_Height => ref _.Resolution_Height;
|
||||
public ref ushort DynamicResolutionActualTargetHeight => ref _.DynamicResolutionActualTargetHeight;
|
||||
public ref ushort DynamicResolutionTargetHeight => ref _.DynamicResolutionTargetHeight;
|
||||
public ref ushort DynamicResolutionMaximumHeight => ref _.DynamicResolutionMaximumHeight;
|
||||
public ref ushort DynamicResolutionMinimumHeight => ref _.DynamicResolutionMinimumHeight;
|
||||
public ref float GraphicsRezoScale => ref _.GraphicsRezoScale;
|
||||
#else
|
||||
[FieldOffset(0x428)]
|
||||
public uint Resolution_Width;
|
||||
[FieldOffset(0x428 + 0x4)]
|
||||
public uint Resolution_Height;
|
||||
|
||||
[FieldOffset(0x6F0 + 2 * 0)]
|
||||
public ushort DynamicResolutionActualTargetHeight;
|
||||
[FieldOffset(0x6F0 + 2 * 1)]
|
||||
public ushort DynamicResolutionTargetHeight;
|
||||
[FieldOffset(0x6F0 + 2 * 2)]
|
||||
public ushort DynamicResolutionMaximumHeight;
|
||||
[FieldOffset(0x6F0 + 2 * 3)]
|
||||
public ushort DynamicResolutionMinimumHeight;
|
||||
|
||||
public const int GraphicsRezoScalesCount = 5;
|
||||
[FieldOffset(0x70C)]
|
||||
public fixed float GraphicsRezoScales[GraphicsRezoScalesCount];
|
||||
[FieldOffset(0x70C + 4 * 0)]
|
||||
public float GraphicsRezoScalePrev;
|
||||
[FieldOffset(0x70C + 4 * 1)]
|
||||
public float GraphicsRezoScaleGlassWidth;
|
||||
[FieldOffset(0x70C + 4 * 2)]
|
||||
public float GraphicsRezoScaleGlassHeight;
|
||||
[FieldOffset(0x70C + 4 * 3)]
|
||||
public float GraphicsRezoScaleUnk1;
|
||||
[FieldOffset(0x70C + 4 * 4)]
|
||||
public float GraphicsRezoScaleUnk2;
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -80,7 +80,7 @@ public sealed unsafe class CursorPosHooks : IDisposable
|
|||
Service.PluginLog.Debug($"GetCursorPos A @ {lpPoint->x} {lpPoint -> y}");
|
||||
#endif
|
||||
|
||||
Service.Plugin.ConvertCoordsGlobalToGame(ref lpPoint->x, ref lpPoint->y);
|
||||
Service.DisplaySize.ConvertCoordsGlobalToGame(ref lpPoint->x, ref lpPoint->y);
|
||||
|
||||
#if false
|
||||
Service.PluginLog.Debug($"GetCursorPos B @ {lpPoint->x} {lpPoint->y}");
|
||||
|
@ -95,7 +95,7 @@ public sealed unsafe class CursorPosHooks : IDisposable
|
|||
Service.PluginLog.Debug($"SetCursorPos A @ {x} {y}");
|
||||
#endif
|
||||
|
||||
Service.Plugin.ConvertCoordsGameToGlobal(ref x, ref y);
|
||||
Service.DisplaySize.ConvertCoordsGameToGlobal(ref x, ref y);
|
||||
|
||||
#if false
|
||||
Service.PluginLog.Debug($"SetCursorPos B @ {x} {y}");
|
||||
|
|
|
@ -50,9 +50,9 @@ public sealed unsafe class WindowRectHooks : IDisposable
|
|||
Service.PluginLog.Debug($"GetClientRectDetour A @ {lpRect->left} {lpRect->top} {lpRect->right} {lpRect->bottom}");
|
||||
#endif
|
||||
|
||||
if (hWnd == Service.Plugin.CurrentHWND)
|
||||
if (hWnd == Service.DisplaySize.CurrentHWND)
|
||||
{
|
||||
Service.Plugin.ConvertCoordsWinToGame(ref lpRect->right, ref lpRect->bottom);
|
||||
Service.DisplaySize.ConvertCoordsWinToGame(ref lpRect->right, ref lpRect->bottom);
|
||||
}
|
||||
|
||||
#if false
|
||||
|
@ -73,9 +73,9 @@ public sealed unsafe class WindowRectHooks : IDisposable
|
|||
Service.PluginLog.Debug($"SetWindowPosDetour A @ {X} {Y} {cx} {cy}");
|
||||
#endif
|
||||
|
||||
if (Service.DebugConfig.SetWindowSizeMode == SetWindowSizeMode.LegacyHookSetWindowPos && hWnd == Service.Plugin.CurrentHWND)
|
||||
if (Service.DebugConfig.SetWindowSizeMode == SetWindowSizeMode.LegacyHookSetWindowPos && hWnd == Service.DisplaySize.CurrentHWND)
|
||||
{
|
||||
Service.Plugin.ConvertCoordsGameToWin(ref cx, ref cy);
|
||||
Service.DisplaySize.ConvertCoordsGameToWin(ref cx, ref cy);
|
||||
}
|
||||
|
||||
#if false
|
||||
|
|
|
@ -112,7 +112,7 @@ public sealed unsafe class WndProcHook : IDisposable
|
|||
}
|
||||
|
||||
if (args.Message == WM.WM_WINDOWPOSCHANGING &&
|
||||
Service.Config.MinSizeMode == MinSizeMode.Unlocked)
|
||||
Service.Config._.MinSizeMode == MinSizeMode.Unlocked)
|
||||
{
|
||||
args.SuppressCall = true;
|
||||
}
|
||||
|
@ -127,8 +127,8 @@ public sealed unsafe class WndProcHook : IDisposable
|
|||
{
|
||||
ParamToCoords(args.LParam, out int x, out int y);
|
||||
Service.PluginLog.Debug($"WM_SIZE {x} x {y}");
|
||||
Service.Plugin.Update();
|
||||
args.LParam = SizeToParam(Service.Plugin.CurrentWidth, Service.Plugin.CurrentHeight);
|
||||
Service.DisplaySize.Update();
|
||||
args.LParam = SizeToParam(Service.DisplaySize.CurrentWidth, Service.DisplaySize.CurrentHeight);
|
||||
}
|
||||
|
||||
if (WM.WM_MOUSEFIRST <= args.Message && args.Message <= WM.WM_MOUSELAST)
|
||||
|
@ -139,7 +139,7 @@ public sealed unsafe class WndProcHook : IDisposable
|
|||
Service.PluginLog.Debug($"WM_MOUSE A @ {x} {y}");
|
||||
#endif
|
||||
|
||||
plugin.ConvertCoordsWinToGame(ref x, ref y);
|
||||
Service.DisplaySize.ConvertCoordsWinToGame(ref x, ref y);
|
||||
|
||||
#if false
|
||||
Service.PluginLog.Debug($"WM_MOUSE B @ {x} {y}");
|
||||
|
@ -148,8 +148,8 @@ public sealed unsafe class WndProcHook : IDisposable
|
|||
args.LParam = CoordsToParam(x, y);
|
||||
}
|
||||
|
||||
if (args.Message == WM.WM_NCCALCSIZE && args.WParam != 0 && plugin.CurrentBorderlessFullscreen &&
|
||||
Service.Config.DXVKDWMHackMode.IsSetClientEdge())
|
||||
if (args.Message == WM.WM_NCCALCSIZE && args.WParam != 0 && Service.DisplaySize.CurrentBorderlessFullscreen &&
|
||||
Service.Config._.DXVKDWMHackMode.IsSetClientEdge())
|
||||
{
|
||||
var ncsize = (NCCALCSIZE_PARAMS*) args.LParam;
|
||||
MONITORINFO monitorInfo = new()
|
||||
|
@ -157,12 +157,12 @@ public sealed unsafe class WndProcHook : IDisposable
|
|||
cbSize = (uint) sizeof(MONITORINFO)
|
||||
};
|
||||
|
||||
if (MonitorFromWindow(plugin.CurrentHWND, MONITOR.MONITOR_DEFAULTTONEAREST) is { } monitor && monitor != HMONITOR.NULL &&
|
||||
if (MonitorFromWindow(Service.DisplaySize.CurrentHWND, MONITOR.MONITOR_DEFAULTTONEAREST) is { } monitor && monitor != HMONITOR.NULL &&
|
||||
GetMonitorInfo(monitor, &monitorInfo))
|
||||
{
|
||||
ncsize->rgrc[0] = monitorInfo.rcMonitor;
|
||||
|
||||
switch (Service.Config.DXVKDWMHackMode)
|
||||
switch (Service.Config._.DXVKDWMHackMode)
|
||||
{
|
||||
case DXVKDWMHackMode.SetClientEdgeResize:
|
||||
ncsize->rgrc[0].bottom += 1;
|
||||
|
|
|
@ -4,6 +4,7 @@ using Dalamud.Game.Config;
|
|||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||
using FFXIVClientStructs.Interop;
|
||||
using ImGuiNET;
|
||||
|
@ -11,6 +12,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Xml.Linq;
|
||||
using TerraFX.Interop.Windows;
|
||||
using static TerraFX.Interop.Windows.Windows;
|
||||
|
||||
|
@ -20,28 +22,18 @@ public sealed unsafe class Plugin : IDalamudPlugin
|
|||
{
|
||||
private object _disposeLock = new();
|
||||
|
||||
private readonly HRGN _invisibleRgn;
|
||||
|
||||
private readonly List<Cmd> _cmds;
|
||||
private bool _unloading = false;
|
||||
private bool _wasEnabled = false;
|
||||
private HWND _currentHwnd;
|
||||
private bool _currentHwndMismatch = false;
|
||||
private RECT _currentClientRect;
|
||||
private DXVKDWMHackMode _currentDXVKDWMHackMode = DXVKDWMHackMode.Off;
|
||||
private bool _ignoreConfigChanges = false;
|
||||
private bool _currentlyFakeResize = false;
|
||||
private bool _requestedResolutionChange = false;
|
||||
|
||||
public Plugin(IDalamudPluginInterface pluginInterface)
|
||||
{
|
||||
_invisibleRgn = CreateRectRgn(0, 0, -1, -1);
|
||||
|
||||
pluginInterface.Create<Service>();
|
||||
|
||||
Service.Plugin = this;
|
||||
|
||||
Service.DebugConfig = new DebugConfiguration();
|
||||
Service.DebugConfig = new();
|
||||
Service.DisplaySize = new();
|
||||
Service.GameSize = new();
|
||||
|
||||
Service.Config = Service.PluginInterface.GetPluginConfig() as Configuration ?? new();
|
||||
Service.Config.Initialize(Service.PluginInterface);
|
||||
|
@ -68,25 +60,18 @@ public sealed unsafe class Plugin : IDalamudPlugin
|
|||
|
||||
public string Name => "CustomResolution";
|
||||
|
||||
public HWND CurrentHWND => _currentHwnd;
|
||||
|
||||
public uint CurrentWidth { get; private set; }
|
||||
public uint CurrentHeight { get; private set; }
|
||||
public uint CurrentWindowWidth { get; private set; }
|
||||
public uint CurrentWindowHeight { get; private set; }
|
||||
|
||||
public bool CurrentBorderlessFullscreen { get; private set; }
|
||||
public bool Unloading { get; private set; } = false;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_unloading = true;
|
||||
Unloading = true;
|
||||
|
||||
lock (_disposeLock)
|
||||
{
|
||||
Service.Framework.Update -= OnFrameworkUpdate;
|
||||
Service.GameConfig.SystemChanged -= OnSystemConfigChanged;
|
||||
|
||||
Service.Framework.RunOnFrameworkThread(Update);
|
||||
Service.DisplaySize.Dispose();
|
||||
Service.GameSize.Dispose();
|
||||
}
|
||||
|
||||
foreach (Cmd cmd in _cmds)
|
||||
|
@ -103,338 +88,13 @@ public sealed unsafe class Plugin : IDalamudPlugin
|
|||
Service.Plugin = null!;
|
||||
}
|
||||
|
||||
public void ConvertCoordsWinToGame(ref int x, ref int y)
|
||||
{
|
||||
if (CurrentWidth == CurrentWindowWidth && CurrentHeight == CurrentWindowHeight)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var scaleX = CurrentWidth / (float) CurrentWindowWidth;
|
||||
var scaleY = CurrentHeight / (float) CurrentWindowHeight;
|
||||
|
||||
x = (int) Math.Round(x * scaleX);
|
||||
y = (int) Math.Round(y * scaleY);
|
||||
}
|
||||
|
||||
public void ConvertCoordsGameToWin(ref int x, ref int y)
|
||||
{
|
||||
if (CurrentWidth == CurrentWindowWidth && CurrentHeight == CurrentWindowHeight)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var scaleX = CurrentWindowWidth / (float) CurrentWidth;
|
||||
var scaleY = CurrentWindowHeight / (float) CurrentHeight;
|
||||
|
||||
x = (int) Math.Round(x * scaleX);
|
||||
y = (int) Math.Round(y * scaleY);
|
||||
}
|
||||
|
||||
public void ConvertCoordsGlobalToGame(ref int x, ref int y)
|
||||
{
|
||||
if (CurrentWidth == CurrentWindowWidth && CurrentHeight == CurrentWindowHeight)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var scaleX = CurrentWidth / (float) CurrentWindowWidth;
|
||||
var scaleY = CurrentHeight / (float) CurrentWindowHeight;
|
||||
|
||||
var p = new POINT(x, y);
|
||||
|
||||
Service.CursorPosHooks.ScreenToClientOrig(_currentHwnd, &p);
|
||||
|
||||
p.x = (int) Math.Round(p.x * scaleX);
|
||||
p.y = (int) Math.Round(p.y * scaleY);
|
||||
|
||||
Service.CursorPosHooks.ClientToScreenOrig(_currentHwnd, &p);
|
||||
|
||||
x = p.x;
|
||||
y = p.y;
|
||||
}
|
||||
|
||||
public void ConvertCoordsGameToGlobal(ref int x, ref int y)
|
||||
{
|
||||
if (CurrentWidth == CurrentWindowWidth && CurrentHeight == CurrentWindowHeight)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var scaleX = CurrentWindowWidth / (float) CurrentWidth;
|
||||
var scaleY = CurrentWindowHeight / (float) CurrentHeight;
|
||||
|
||||
var p = new POINT(x, y);
|
||||
|
||||
Service.CursorPosHooks.ScreenToClientOrig(_currentHwnd, &p);
|
||||
|
||||
p.x = (int) Math.Round(p.x * scaleX);
|
||||
p.y = (int) Math.Round(p.y * scaleY);
|
||||
|
||||
Service.CursorPosHooks.ClientToScreenOrig(_currentHwnd, &p);
|
||||
|
||||
x = p.x;
|
||||
y = p.y;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
var dev = Device.Instance();
|
||||
var framework = Framework.Instance();
|
||||
var win = framework->GameWindow;
|
||||
|
||||
_currentHwnd = (HWND) win->WindowHandle;
|
||||
|
||||
#if CRES_CLEAN || true
|
||||
var _dev_hWnd = (IntPtr*) &dev->hWnd;
|
||||
var _dev_NewWidth = &dev->NewWidth;
|
||||
var _dev_NewHeight = &dev->NewHeight;
|
||||
var _dev_RequestResolutionChange = &dev->RequestResolutionChange;
|
||||
#else
|
||||
// 7.2 adds 1B8 before hWnd (previously 820, now 9D8)
|
||||
var _dev_hWnd = (nint*) ((IntPtr) dev + 0x9D8);
|
||||
var _dev_NewWidth = (uint*) ((IntPtr) dev + 0x9D8 + 0x10);
|
||||
var _dev_NewHeight = (uint*) ((IntPtr) dev + 0x9D8 + 0x14);
|
||||
var _dev_RequestResolutionChange = &dev->RequestResolutionChange;
|
||||
#endif
|
||||
|
||||
// As a safety measure, don't mess with the structs if we're reading garbage.
|
||||
// This isn't perfect, but it's better than nothing.
|
||||
if (_currentHwnd != *_dev_hWnd)
|
||||
{
|
||||
if (!_currentHwndMismatch)
|
||||
{
|
||||
_currentHwndMismatch = true;
|
||||
Service.PluginLog.Error($"HWND MISMATCH between GameWindow and Device: 0x{(long) (IntPtr) (_currentHwnd):X16} vs 0x{(long) *_dev_hWnd:X16}");
|
||||
Service.PluginLog.Info($"dev is at: 0x{(long) (IntPtr) dev:X16}");
|
||||
CheckHWND("GameWindow", _currentHwnd);
|
||||
CheckHWND("Device", (HWND) (*_dev_hWnd));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_currentHwndMismatch = false;
|
||||
|
||||
fixed (RECT* currentClientRectPtr = &_currentClientRect)
|
||||
{
|
||||
Service.WindowRectHooks.GetClientRectOrig(_currentHwnd, currentClientRectPtr);
|
||||
}
|
||||
|
||||
var rectWidth = _currentClientRect.right - _currentClientRect.left;
|
||||
var rectHeight = _currentClientRect.bottom - _currentClientRect.top;
|
||||
|
||||
if ((rectWidth <= 0 || rectHeight <= 0) && !_unloading)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var enabled = !_unloading && Service.Config.IsEnabled;
|
||||
|
||||
uint width, height;
|
||||
|
||||
if (Service.Config.IsScale || !enabled)
|
||||
{
|
||||
var scale = enabled ? Service.Config.Scale : 1f;
|
||||
|
||||
width = (uint) Math.Round(rectWidth * scale);
|
||||
height = (uint) Math.Round(rectHeight * scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
width = Service.Config.Width;
|
||||
height = Service.Config.Height;
|
||||
}
|
||||
|
||||
if (width < 256)
|
||||
{
|
||||
width = 256;
|
||||
}
|
||||
|
||||
if (height < 256)
|
||||
{
|
||||
height = 256;
|
||||
}
|
||||
|
||||
_requestedResolutionChange |= (*_dev_RequestResolutionChange) != 0;
|
||||
|
||||
if (!_currentlyFakeResize && (_unloading || enabled || _wasEnabled || _requestedResolutionChange) &&
|
||||
(width != dev->Width || height != dev->Height))
|
||||
{
|
||||
Service.PluginLog.Info($"Changing graphics resolution from {dev->Width} x {dev->Height} to {width} x {height}");
|
||||
var mode = Service.DebugConfig.ForceSizeMode;
|
||||
if (_requestedResolutionChange)
|
||||
{
|
||||
// Let's try to be cautious about other plugins (f.e. SimpleTweaks) changing the game resolution.
|
||||
Service.PluginLog.Info($"Game resolution was changed externally - forcing resize via device, not window resize.");
|
||||
mode = ForceSizeMode.LegacyRequestResolutionChange;
|
||||
_requestedResolutionChange = false;
|
||||
}
|
||||
switch (mode)
|
||||
{
|
||||
case ForceSizeMode.Skip:
|
||||
break;
|
||||
case ForceSizeMode.LegacyRequestResolutionChange:
|
||||
*_dev_NewWidth = width;
|
||||
*_dev_NewHeight = height;
|
||||
*_dev_RequestResolutionChange = 1;
|
||||
Service.PluginLog.Info($"Changing game window from {win->WindowWidth} x {win->WindowHeight} to {width} x {height}");
|
||||
win->WindowWidth = (int) width;
|
||||
win->WindowHeight = (int) height;
|
||||
break;
|
||||
case ForceSizeMode.FakeWindowResize:
|
||||
var adjustedClientRect = new RECT(0, 0, rectWidth, rectHeight);
|
||||
AdjustWindowRect(&adjustedClientRect, (uint) GetWindowLongPtr(_currentHwnd, GWL.GWL_STYLE), false);
|
||||
var adjustedWidth = adjustedClientRect.right - adjustedClientRect.left;
|
||||
var adjustedHeight = adjustedClientRect.bottom - adjustedClientRect.top;
|
||||
Service.PluginLog.Info($"Resizing window to {adjustedWidth} x {adjustedHeight}");
|
||||
try
|
||||
{
|
||||
_currentlyFakeResize = true;
|
||||
SendMessage(_currentHwnd, WM.WM_ENTERSIZEMOVE, 0, 0);
|
||||
SendMessage(_currentHwnd, WM.WM_SIZE, SIZE_MAXSHOW, WndProcHook.SizeToParam((uint) adjustedWidth, (uint) adjustedHeight));
|
||||
SendMessage(_currentHwnd, WM.WM_PAINT, 0, 0);
|
||||
SendMessage(_currentHwnd, WM.WM_EXITSIZEMOVE, 0, 0);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_currentlyFakeResize = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Service.PluginLog.Error($"Unknown ForceSizeMode: {Service.DebugConfig.ForceSizeMode}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_wasEnabled != enabled)
|
||||
{
|
||||
Service.PluginLog.Info($"Changed state to: {enabled}");
|
||||
_wasEnabled = enabled;
|
||||
}
|
||||
|
||||
//Service.PluginLog.Debug($"NewWidth 0x{(long) (IntPtr) (&dev->NewWidth):X16}");
|
||||
//Service.PluginLog.Debug($"GameWindow->Width 0x{(long) (IntPtr) (&win->WindowWidth):X16}");
|
||||
|
||||
//Service.PluginLog.Info($"Game window at {win->WindowWidth} x {win->WindowHeight}");
|
||||
|
||||
//Service.PluginLog.Info($"AAA {Service.GameConfig.System.GetUInt(nameof(SystemConfigOption.UiHighScale))}");
|
||||
|
||||
CurrentBorderlessFullscreen = win->Borderless;
|
||||
|
||||
if (Service.Config.DXVKDWMHackMode != DXVKDWMHackMode.Off && !_unloading)
|
||||
{
|
||||
SetDXVKDWMHack(Service.Config.DXVKDWMHackMode);
|
||||
}
|
||||
else if (Service.Config.DXVKDWMHackMode == DXVKDWMHackMode.Off && _currentDXVKDWMHackMode != DXVKDWMHackMode.Off)
|
||||
{
|
||||
SetDXVKDWMHack(DXVKDWMHackMode.Off);
|
||||
}
|
||||
|
||||
CurrentWidth = width;
|
||||
CurrentHeight = height;
|
||||
CurrentWindowWidth = (uint) rectWidth;
|
||||
CurrentWindowHeight = (uint) rectHeight;
|
||||
}
|
||||
|
||||
private void SetDXVKDWMHack(DXVKDWMHackMode mode)
|
||||
{
|
||||
/* Default maximized style / exstyle is 0x95000000 / 0.
|
||||
* WS.WS_POPUP | WS.WS_VISIBLE | WS.WS_CLIPSIBLINGS | WS.WS_MAXIMIZE
|
||||
* Default windowed style / exstyle is 0x14CF0000 / 0.
|
||||
* WS.WS_VISIBLE | WS.WS_CLIPSIBLINGS | WS.WS_CAPTION | WS.WS_SYSMENU | WS.WS_THICKFRAME | WS.WS_MINIMIZEBOX | WS.WS_MAXIMIZEBOX
|
||||
*/
|
||||
var styleOrig = (uint) GetWindowLong(_currentHwnd, GWL.GWL_STYLE);
|
||||
var exstyleOrig = (uint) GetWindowLong(_currentHwnd, GWL.GWL_EXSTYLE);
|
||||
|
||||
var style = styleOrig;
|
||||
var exstyle = exstyleOrig;
|
||||
|
||||
var fullscreen = (style & WS.WS_SYSMENU) == 0;
|
||||
|
||||
if (Service.DebugConfig.IsDebug)
|
||||
{
|
||||
Service.PluginLog.Info("--------");
|
||||
Service.PluginLog.Info($"STYLE: 0x{style:X8}");
|
||||
Service.PluginLog.Info($"EXSTYLE: 0x{GetWindowLong(_currentHwnd, GWL.GWL_EXSTYLE):X8}");
|
||||
|
||||
Span<ushort> name = stackalloc ushort[256];
|
||||
GetClassName(_currentHwnd, name.GetPointer(0), name.Length);
|
||||
WNDCLASSEXW wce;
|
||||
GetClassInfoEx(GetModuleHandle(null), name.GetPointer(0), &wce);
|
||||
|
||||
Service.PluginLog.Info($"CLASS: {new string((char*) name.GetPointer(0))}");
|
||||
Service.PluginLog.Info($"CLASS.style: 0x{wce.style:X8}");
|
||||
}
|
||||
|
||||
if (fullscreen)
|
||||
{
|
||||
if (mode == DXVKDWMHackMode.UnsetPopup)
|
||||
{
|
||||
style &= ~WS.WS_POPUP;
|
||||
}
|
||||
else
|
||||
{
|
||||
style |= WS.WS_POPUP;
|
||||
}
|
||||
}
|
||||
|
||||
if (fullscreen && mode.IsSetClientEdge())
|
||||
{
|
||||
exstyle |= WS.WS_EX_CLIENTEDGE;
|
||||
exstyle |= WS.WS_EX_COMPOSITED;
|
||||
}
|
||||
else
|
||||
{
|
||||
exstyle &= ~(uint) WS.WS_EX_CLIENTEDGE;
|
||||
exstyle &= ~(uint) WS.WS_EX_COMPOSITED;
|
||||
}
|
||||
|
||||
if (Service.DebugConfig.IsDebug)
|
||||
{
|
||||
Service.PluginLog.Info($"NEWSTYLE: 0x{style:X8}");
|
||||
Service.PluginLog.Info($"NEWEXSTYLE: 0x{exstyle:X8}");
|
||||
}
|
||||
|
||||
if (style != styleOrig || exstyle != exstyleOrig || _currentDXVKDWMHackMode != mode)
|
||||
{
|
||||
if (Service.DebugConfig.IsDebug)
|
||||
{
|
||||
Service.PluginLog.Info("UPDATE");
|
||||
}
|
||||
|
||||
SetWindowLong(_currentHwnd, GWL.GWL_STYLE, (int) style);
|
||||
SetWindowLong(_currentHwnd, GWL.GWL_EXSTYLE, (int) exstyle);
|
||||
|
||||
SetWindowPos(_currentHwnd, HWND.NULL, 0, 0, 0, 0, SWP.SWP_NOZORDER | SWP.SWP_NOMOVE | SWP.SWP_NOSIZE | SWP.SWP_NOACTIVATE | SWP.SWP_DRAWFRAME);
|
||||
ShowWindow(_currentHwnd, SW.SW_SHOW);
|
||||
}
|
||||
else if (Service.DebugConfig.IsDebug)
|
||||
{
|
||||
Service.PluginLog.Info("SAME");
|
||||
}
|
||||
|
||||
_currentDXVKDWMHackMode = mode;
|
||||
}
|
||||
|
||||
private void CheckHWND(string from, HWND hwnd)
|
||||
{
|
||||
RECT rect = default;
|
||||
if (!GetClientRect(hwnd, &rect))
|
||||
{
|
||||
Service.PluginLog.Info($"{from} is sus: {Marshal.GetLastPInvokeErrorMessage()}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFrameworkUpdate(IFramework framework)
|
||||
{
|
||||
lock (_disposeLock)
|
||||
private void OnFrameworkUpdateForSize(ref ConfigurationV1.SizeConfig size)
|
||||
{
|
||||
var io = ImGui.GetIO();
|
||||
|
||||
if (Service.Config.HotkeyKey != VirtualKey.NO_KEY &&
|
||||
(ImGuiNative.igIsKeyPressed((ImGuiKey) Service.Config.HotkeyKey, 0) != 0 || Service.KeyState.GetRawValue(Service.Config.HotkeyKey) != 0) &&
|
||||
(Service.Config.HotkeyModifier switch
|
||||
if (size.HotkeyKey != VirtualKey.NO_KEY &&
|
||||
(ImGuiNative.igIsKeyPressed((ImGuiKey) size.HotkeyKey, 0) != 0 || Service.KeyState.GetRawValue(size.HotkeyKey) != 0) &&
|
||||
(size.HotkeyModifier switch
|
||||
{
|
||||
ModifierKey.NONE => !io.KeyCtrl && !io.KeyAlt && !io.KeyShift,
|
||||
ModifierKey.CTRL => io.KeyCtrl && !io.KeyAlt && !io.KeyShift,
|
||||
|
@ -443,11 +103,20 @@ public sealed unsafe class Plugin : IDalamudPlugin
|
|||
_ => false,
|
||||
}))
|
||||
{
|
||||
Service.Config.IsEnabled = !Service.Config.IsEnabled;
|
||||
size.IsEnabled = !size.IsEnabled;
|
||||
Service.Config.Save();
|
||||
}
|
||||
}
|
||||
|
||||
Update();
|
||||
private void OnFrameworkUpdate(IFramework framework)
|
||||
{
|
||||
lock (_disposeLock)
|
||||
{
|
||||
OnFrameworkUpdateForSize(ref Service.Config._.Display);
|
||||
OnFrameworkUpdateForSize(ref Service.Config._.Game);
|
||||
|
||||
Service.DisplaySize.Update();
|
||||
Service.GameSize.Update();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -469,11 +138,12 @@ public sealed unsafe class Plugin : IDalamudPlugin
|
|||
case SystemConfigOption.ScreenHeight:
|
||||
if (Service.DebugConfig.SetWindowSizeMode == SetWindowSizeMode.InterceptSystemConfig)
|
||||
{
|
||||
var d = Service.DisplaySize;
|
||||
var name = e.ConfigOption.ToString();
|
||||
var valueOrig = Service.GameConfig.System.GetUInt(name);
|
||||
|
||||
var isW = e.ConfigOption == SystemConfigOption.ScreenWidth;
|
||||
var scale = (isW ? CurrentWindowWidth : CurrentWindowHeight) / (float) (isW ? CurrentWidth : CurrentHeight);
|
||||
var scale = (isW ? d.CurrentWindowWidth : d.CurrentWindowHeight) / (float) (isW ? d.CurrentWidth : d.CurrentHeight);
|
||||
var valueNew = (uint) Math.Round(valueOrig * scale);
|
||||
|
||||
Service.PluginLog.Info($"Intercepting config value change for {name}: {valueOrig} -> {valueNew}");
|
||||
|
@ -492,22 +162,3 @@ public sealed unsafe class Plugin : IDalamudPlugin
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ForceSizeMode
|
||||
{
|
||||
Skip = -1,
|
||||
|
||||
/// <summary>
|
||||
/// Fake a window resize event.
|
||||
/// </summary>
|
||||
FakeWindowResize = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Legacy mode, went through more initial testing, works well except for conflicts with other plugins.
|
||||
/// Might revert or remove depending on how FakeWindowResize testing / fixups proceed.
|
||||
///
|
||||
/// Update the graphics device width / height and set RequestResolutionChange.
|
||||
/// This is the same method that other plugins such as SimpleTweaks or XIVWindowResizer use.
|
||||
/// </summary>
|
||||
LegacyRequestResolutionChange = 1
|
||||
}
|
||||
|
|
|
@ -10,6 +10,9 @@ public sealed class Service
|
|||
{
|
||||
public static Plugin Plugin { get; internal set; } = null!;
|
||||
|
||||
public static DisplaySizeState DisplaySize { get; internal set; } = null!;
|
||||
public static GameSizeState GameSize { get; internal set; } = null!;
|
||||
|
||||
public static Configuration Config { get; internal set; } = null!;
|
||||
|
||||
public static DebugConfiguration DebugConfig { get; internal set; } = null!;
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using ImGuiNET;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Xml.Linq;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
namespace CustomResolution.Windows;
|
||||
|
||||
|
@ -11,17 +14,10 @@ public class ConfigWindow : Window, IDisposable
|
|||
{
|
||||
private VirtualKey[] _validKeys = new VirtualKey[] { VirtualKey.NO_KEY }.Union(Service.KeyState.GetValidVirtualKeys()).ToArray();
|
||||
|
||||
private int[] _displayCurrentWH = new int[2];
|
||||
private int[] _displayCurrentWindowWH = new int[2];
|
||||
private readonly int[] _displayCurrentWH = new int[2];
|
||||
private readonly int[] _displayCurrentWindowWH = new int[2];
|
||||
|
||||
private bool _configIsEnabled;
|
||||
private VirtualKey _configHotkeyKey;
|
||||
private ModifierKey _configHotkeyModifier;
|
||||
private bool _configIsScale;
|
||||
private float _configScale;
|
||||
private int[] _configWH = new int[2];
|
||||
private DXVKDWMHackMode _configDXVKDWMHackMode;
|
||||
private MinSizeMode _configMinSizeMode;
|
||||
private ConfigurationV1 _;
|
||||
|
||||
public ConfigWindow() : base(
|
||||
"CustomResolution",
|
||||
|
@ -36,65 +32,152 @@ public class ConfigWindow : Window, IDisposable
|
|||
|
||||
public void UpdateFromConfig()
|
||||
{
|
||||
var config = Service.Config;
|
||||
|
||||
_configIsEnabled = config.IsEnabled;
|
||||
_configHotkeyKey = config.HotkeyKey;
|
||||
_configHotkeyModifier = config.HotkeyModifier;
|
||||
_configIsScale = config.IsScale;
|
||||
_configScale = config.Scale;
|
||||
_configWH[0] = (int) config.Width;
|
||||
_configWH[1] = (int) config.Height;
|
||||
_configDXVKDWMHackMode = config.DXVKDWMHackMode;
|
||||
_configMinSizeMode = config.MinSizeMode;
|
||||
_ = Service.Config._;
|
||||
}
|
||||
|
||||
public void UpdateToConfig()
|
||||
{
|
||||
var config = Service.Config;
|
||||
|
||||
config.IsEnabled = _configIsEnabled;
|
||||
config.HotkeyKey = _configHotkeyKey;
|
||||
config.HotkeyModifier = _configHotkeyModifier;
|
||||
config.IsScale = _configIsScale;
|
||||
config.Scale = _configScale;
|
||||
config.Width = (uint) _configWH[0];
|
||||
config.Height = (uint) _configWH[1];
|
||||
config.DXVKDWMHackMode = _configDXVKDWMHackMode;
|
||||
config.MinSizeMode = _configMinSizeMode;
|
||||
|
||||
config.Save();
|
||||
Service.Config._ = _;
|
||||
Service.Config.Save();
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
var plugin = Service.Plugin;
|
||||
var save = false;
|
||||
|
||||
_displayCurrentWH[0] = (int) plugin.CurrentWidth;
|
||||
_displayCurrentWH[1] = (int) plugin.CurrentHeight;
|
||||
_displayCurrentWindowWH[0] = (int) plugin.CurrentWindowWidth;
|
||||
_displayCurrentWindowWH[1] = (int) plugin.CurrentWindowHeight;
|
||||
_displayCurrentWH[0] = (int) Service.DisplaySize.CurrentWidth;
|
||||
_displayCurrentWH[1] = (int) Service.DisplaySize.CurrentHeight;
|
||||
_displayCurrentWindowWH[0] = (int) Service.DisplaySize.CurrentWindowWidth;
|
||||
_displayCurrentWindowWH[1] = (int) Service.DisplaySize.CurrentWindowHeight;
|
||||
|
||||
ImGui.BeginDisabled();
|
||||
ImGui.InputInt2("Current window size", ref _displayCurrentWindowWH[0]);
|
||||
ImGui.InputInt2("Current render size", ref _displayCurrentWH[0]);
|
||||
ImGui.InputInt2("Current display size", ref _displayCurrentWH[0]);
|
||||
ImGui.EndDisabled();
|
||||
|
||||
ImGui.Checkbox("Enabled", ref _configIsEnabled);
|
||||
using (var imTabBar = ImRaii.TabBar("MalfunctionMainTabs"))
|
||||
{
|
||||
DrawDisplayTab();
|
||||
|
||||
DrawGameTab();
|
||||
|
||||
DrawCommonTab();
|
||||
}
|
||||
|
||||
if (ImGui.Button("Save and apply"))
|
||||
{
|
||||
save = true;
|
||||
}
|
||||
|
||||
if (save)
|
||||
{
|
||||
UpdateToConfig();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawDisplayTab()
|
||||
{
|
||||
using var imTab = ImRaii.TabItem($"Display##DisplayTab");
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip(@"The display resolution affects everything,
|
||||
including screenshots taken in-game and via ReShade.
|
||||
|
||||
This works best with the ""High Resolution UI Settings""
|
||||
in the system configuration, as it also affects the UI.
|
||||
|
||||
In general: Use it as a fake fullscreen / window resolution,
|
||||
best suited for screenshots.
|
||||
Changed via /cres");
|
||||
}
|
||||
|
||||
if (!imTab.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DrawSizeTabContents(ref _.Display, "Display");
|
||||
}
|
||||
|
||||
private void DrawGameTab()
|
||||
{
|
||||
using var imTab = ImRaii.TabItem($"Gameplay##GameTab");
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip(@"The gameplay resolution affects only the quality of your character
|
||||
and everything around them, leaving the HUD untouched.
|
||||
This also doesn't touch the screenshot resolution.
|
||||
|
||||
This works as an advanced version of the in-game ""3D Resolution Scaling"" graphics options,
|
||||
allowing you to go smaller than 0.5x for more FPS, or higher than 1x for higher quality.
|
||||
|
||||
This overrides dynamic resolution!
|
||||
|
||||
In general: Use it for gameplay, not for screenshots.
|
||||
Changed via /gres");
|
||||
}
|
||||
|
||||
if (!imTab.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if false
|
||||
if (Service.GameSize.CurrentDynRezoConfig)
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Border, 0xff00ffff))
|
||||
{
|
||||
if (ImGui.Button(@"This might not work properly while in-game dynamic resolution is enabled!
|
||||
Click here to disable it."))
|
||||
{
|
||||
Service.GameSize.ForceDynRezoConfig(false);
|
||||
}
|
||||
|
||||
ImGui.Dummy(new Vector2(0, ImGui.GetTextLineHeight() * 0.1f));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
DrawSizeTabContents(ref _.Game, "Game", scaleOnly: true);
|
||||
|
||||
if (ImGui.BeginCombo("Graphics Upscaling", _.ResolutionScalingMode.ToHumanNameString()))
|
||||
{
|
||||
foreach (var mode in Enum.GetValues<ResolutionScalingMode>())
|
||||
{
|
||||
if (ImGui.Selectable(mode.ToHumanNameString(), _.ResolutionScalingMode == mode, mode.IsUnsupported() ? ImGuiSelectableFlags.Disabled : ImGuiSelectableFlags.None))
|
||||
{
|
||||
_.ResolutionScalingMode = mode;
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled) && mode.ToHumanInfoString() is { } info)
|
||||
{
|
||||
ImGui.SetTooltip(info);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndCombo();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void DrawSizeTabContents(ref ConfigurationV1.SizeConfig size, string name, bool scaleOnly = false)
|
||||
{
|
||||
ImGui.Checkbox("Enabled", ref size.IsEnabled);
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.Dummy(new Vector2(20, 0));
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.SetNextItemWidth(80);
|
||||
if (ImGui.BeginCombo("##_configHotkeyModifier", _configHotkeyModifier.ToHumanNameString()))
|
||||
if (ImGui.BeginCombo($"##_{name}_configHotkeyModifier", size.HotkeyModifier.ToHumanNameString()))
|
||||
{
|
||||
foreach (var key in Enum.GetValues<ModifierKey>())
|
||||
{
|
||||
if (ImGui.Selectable(key.ToHumanNameString(), _configHotkeyModifier == key, ImGuiSelectableFlags.None))
|
||||
if (ImGui.Selectable(key.ToHumanNameString(), size.HotkeyModifier == key, ImGuiSelectableFlags.None))
|
||||
{
|
||||
_configHotkeyModifier = key;
|
||||
size.HotkeyModifier = key;
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered() && key.ToHumanInfoString() is { } info)
|
||||
|
@ -108,13 +191,13 @@ public class ConfigWindow : Window, IDisposable
|
|||
|
||||
ImGui.SameLine();
|
||||
ImGui.SetNextItemWidth(160);
|
||||
if (ImGui.BeginCombo("##_configHotkeyKey", _configHotkeyKey.ToHumanNameString()))
|
||||
if (ImGui.BeginCombo($"##_{name}_configHotkeyKey", size.HotkeyKey.ToHumanNameString()))
|
||||
{
|
||||
foreach (var key in _validKeys)
|
||||
{
|
||||
if (ImGui.Selectable(key.ToHumanNameString(), _configHotkeyKey == key, ImGuiSelectableFlags.None))
|
||||
if (ImGui.Selectable(key.ToHumanNameString(), size.HotkeyKey == key, ImGuiSelectableFlags.None))
|
||||
{
|
||||
_configHotkeyKey = key;
|
||||
size.HotkeyKey = key;
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered() && key.ToHumanInfoString() is { } info)
|
||||
|
@ -130,34 +213,27 @@ public class ConfigWindow : Window, IDisposable
|
|||
ImGui.Text("Hotkey");
|
||||
|
||||
|
||||
ImGui.Checkbox("Use scale", ref _configIsScale);
|
||||
|
||||
if (_configIsScale)
|
||||
if (!scaleOnly)
|
||||
{
|
||||
ImGui.InputFloat("Scale", ref _configScale, 0.01f, 0.1f, "%.3f", ImGuiInputTextFlags.EnterReturnsTrue);
|
||||
ImGui.Checkbox("Use scale", ref size.IsScale);
|
||||
}
|
||||
|
||||
if (size.IsScale || scaleOnly)
|
||||
{
|
||||
ImGui.InputFloat("Scale", ref size.Scale, 0.01f, 0.1f, "%.3f", ImGuiInputTextFlags.EnterReturnsTrue);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.InputInt2("Size in pixels", ref _configWH[0]);
|
||||
unsafe
|
||||
{
|
||||
ImGui.InputInt2("Size in pixels", ref size.WH[0]);
|
||||
}
|
||||
|
||||
if (ImGui.BeginCombo("Borderless window workaround", _configDXVKDWMHackMode.ToHumanNameString()))
|
||||
{
|
||||
foreach (var mode in Enum.GetValues<DXVKDWMHackMode>())
|
||||
{
|
||||
if (ImGui.Selectable(mode.ToHumanNameString(), _configDXVKDWMHackMode == mode, ImGuiSelectableFlags.None))
|
||||
{
|
||||
_configDXVKDWMHackMode = mode;
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered() && mode.ToHumanInfoString() is { } info)
|
||||
{
|
||||
ImGui.SetTooltip(info);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndCombo();
|
||||
}
|
||||
private void DrawCommonTab()
|
||||
{
|
||||
using var imTab = ImRaii.TabItem($"Common##CommonTab");
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
|
@ -172,13 +248,18 @@ Works even with the scaling above disabled.
|
|||
Not intended to be used with proper fullscreen.");
|
||||
}
|
||||
|
||||
if (ImGui.BeginCombo("Minimum window size", _configMinSizeMode.ToHumanNameString()))
|
||||
if (!imTab.Success)
|
||||
{
|
||||
foreach (var mode in Enum.GetValues<MinSizeMode>())
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui.BeginCombo("Borderless window workaround", _.DXVKDWMHackMode.ToHumanNameString()))
|
||||
{
|
||||
if (ImGui.Selectable(mode.ToHumanNameString(), _configMinSizeMode == mode, ImGuiSelectableFlags.None))
|
||||
foreach (var mode in Enum.GetValues<DXVKDWMHackMode>())
|
||||
{
|
||||
_configMinSizeMode = mode;
|
||||
if (ImGui.Selectable(mode.ToHumanNameString(), _.DXVKDWMHackMode == mode, ImGuiSelectableFlags.None))
|
||||
{
|
||||
_.DXVKDWMHackMode = mode;
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered() && mode.ToHumanInfoString() is { } info)
|
||||
|
@ -190,14 +271,28 @@ Not intended to be used with proper fullscreen.");
|
|||
ImGui.EndCombo();
|
||||
}
|
||||
|
||||
if (ImGui.Button("Save and apply"))
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
save = true;
|
||||
ImGui.SetTooltip(@"Smaller tweaks and fixes, f.e. for DXVK on Windows,
|
||||
or to allow making the window smaller than 1024x768.");
|
||||
}
|
||||
|
||||
if (save)
|
||||
if (ImGui.BeginCombo("Minimum window size", _.MinSizeMode.ToHumanNameString()))
|
||||
{
|
||||
UpdateToConfig();
|
||||
foreach (var mode in Enum.GetValues<MinSizeMode>())
|
||||
{
|
||||
if (ImGui.Selectable(mode.ToHumanNameString(), _.MinSizeMode == mode, ImGuiSelectableFlags.None))
|
||||
{
|
||||
_.MinSizeMode = mode;
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered() && mode.ToHumanInfoString() is { } info)
|
||||
{
|
||||
ImGui.SetTooltip(info);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndCombo();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue