diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs
index 1274744c6..35d5261da 100644
--- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs
+++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -34,7 +35,7 @@ internal sealed class DalamudConfiguration : IServiceType, IDisposable
};
[JsonIgnore]
- private string configPath;
+ private string? configPath;
[JsonIgnore]
private bool isSaveQueued;
@@ -48,12 +49,12 @@ internal sealed class DalamudConfiguration : IServiceType, IDisposable
///
/// Event that occurs when dalamud configuration is saved.
///
- public event DalamudConfigurationSavedDelegate DalamudConfigurationSaved;
+ public event DalamudConfigurationSavedDelegate? DalamudConfigurationSaved;
///
/// Gets or sets a list of muted works.
///
- public List BadWords { get; set; }
+ public List? BadWords { get; set; }
///
/// Gets or sets a value indicating whether or not the taskbar should flash once a duty is found.
@@ -68,12 +69,12 @@ internal sealed class DalamudConfiguration : IServiceType, IDisposable
///
/// Gets or sets the language code to load Dalamud localization with.
///
- public string LanguageOverride { get; set; } = null;
+ public string? LanguageOverride { get; set; } = null;
///
/// Gets or sets the last loaded Dalamud version.
///
- public string LastVersion { get; set; } = null;
+ public string? LastVersion { get; set; } = null;
///
/// Gets or sets a value indicating the last seen FTUE version.
@@ -84,7 +85,7 @@ internal sealed class DalamudConfiguration : IServiceType, IDisposable
///
/// Gets or sets the last loaded Dalamud version.
///
- public string LastChangelogMajorMinor { get; set; } = null;
+ public string? LastChangelogMajorMinor { get; set; } = null;
///
/// Gets or sets the chat type used by default for plugin messages.
@@ -229,6 +230,7 @@ internal sealed class DalamudConfiguration : IServiceType, IDisposable
/// Gets or sets a value indicating whether or not plugin user interfaces should trigger sound effects.
/// This setting is effected by the in-game "System Sounds" option and volume.
///
+ [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "ABI")]
public bool EnablePluginUISoundEffects { get; set; }
///
@@ -266,7 +268,7 @@ internal sealed class DalamudConfiguration : IServiceType, IDisposable
///
/// Gets or sets the kind of beta to download when matches the server value.
///
- public string DalamudBetaKind { get; set; }
+ public string? DalamudBetaKind { get; set; }
///
/// Gets or sets a value indicating whether or not any plugin should be loaded when the game is started.
@@ -514,6 +516,8 @@ internal sealed class DalamudConfiguration : IServiceType, IDisposable
private void Save()
{
ThreadSafety.AssertMainThread();
+ if (this.configPath is null)
+ throw new InvalidOperationException("configPath is not set.");
Service.Get().WriteAllText(
this.configPath, JsonConvert.SerializeObject(this, SerializerSettings));
diff --git a/Dalamud/Interface/GameFonts/FdtReader.cs b/Dalamud/Interface/GameFonts/FdtReader.cs
index a68caba94..0e8f3fb59 100644
--- a/Dalamud/Interface/GameFonts/FdtReader.cs
+++ b/Dalamud/Interface/GameFonts/FdtReader.cs
@@ -1,4 +1,3 @@
-using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
@@ -22,7 +21,7 @@ public class FdtReader
for (var i = 0; i < this.FontHeader.FontTableEntryCount; i++)
this.Glyphs.Add(StructureFromByteArray(data, this.FileHeader.FontTableHeaderOffset + Marshal.SizeOf() + (Marshal.SizeOf() * i)));
- for (int i = 0, i_ = Math.Min(this.FontHeader.KerningTableEntryCount, this.KerningHeader.Count); i < i_; i++)
+ for (int i = 0, to = Math.Min(this.FontHeader.KerningTableEntryCount, this.KerningHeader.Count); i < to; i++)
this.Distances.Add(StructureFromByteArray(data, this.FileHeader.KerningTableHeaderOffset + Marshal.SizeOf() + (Marshal.SizeOf() * i)));
}
@@ -51,6 +50,14 @@ public class FdtReader
///
public List Distances { get; init; } = new();
+ ///
+ /// Finds the glyph index for the corresponding codepoint.
+ ///
+ /// Unicode codepoint (UTF-32 value).
+ /// Corresponding index, or a negative number according to .
+ public int FindGlyphIndex(int codepoint) =>
+ this.Glyphs.BinarySearch(new FontTableEntry { CharUtf8 = CodePointToUtf8Int32(codepoint) });
+
///
/// Finds glyph definition for corresponding codepoint.
///
@@ -58,7 +65,7 @@ public class FdtReader
/// Corresponding FontTableEntry, or null if not found.
public FontTableEntry? FindGlyph(int codepoint)
{
- var i = this.Glyphs.BinarySearch(new FontTableEntry { CharUtf8 = CodePointToUtf8Int32(codepoint) });
+ var i = this.FindGlyphIndex(codepoint);
if (i < 0 || i == this.Glyphs.Count)
return null;
return this.Glyphs[i];
@@ -91,17 +98,12 @@ public class FdtReader
return this.Distances[i].RightOffset;
}
- private static unsafe T StructureFromByteArray(byte[] data, int offset)
- {
- var len = Marshal.SizeOf();
- if (offset + len > data.Length)
- throw new Exception("Data too short");
-
- fixed (byte* ptr = data)
- return Marshal.PtrToStructure(new(ptr + offset));
- }
-
- private static int CodePointToUtf8Int32(int codepoint)
+ ///
+ /// Translates a UTF-32 codepoint to a containing a UTF-8 character.
+ ///
+ /// The codepoint.
+ /// The uint.
+ internal static int CodePointToUtf8Int32(int codepoint)
{
if (codepoint <= 0x7F)
{
@@ -131,6 +133,16 @@ public class FdtReader
}
}
+ private static unsafe T StructureFromByteArray(byte[] data, int offset)
+ {
+ var len = Marshal.SizeOf();
+ if (offset + len > data.Length)
+ throw new Exception("Data too short");
+
+ fixed (byte* ptr = data)
+ return Marshal.PtrToStructure(new(ptr + offset));
+ }
+
private static int Utf8Uint32ToCodePoint(int n)
{
if ((n & 0xFFFFFF80) == 0)
@@ -252,7 +264,7 @@ public class FdtReader
/// Glyph table entry.
///
[StructLayout(LayoutKind.Sequential)]
- public unsafe struct FontTableEntry : IComparable
+ public struct FontTableEntry : IComparable
{
///
/// Mapping of texture channel index to byte index.
@@ -367,7 +379,7 @@ public class FdtReader
/// Kerning table entry.
///
[StructLayout(LayoutKind.Sequential)]
- public unsafe struct KerningTableEntry : IComparable
+ public struct KerningTableEntry : IComparable
{
///
/// Integer representation of a Unicode character in UTF-8 in reverse order, read in little endian, for the left character.
diff --git a/Dalamud/Interface/GameFonts/GameFontManager.cs b/Dalamud/Interface/GameFonts/GameFontManager.cs
index a7cd27b83..71661682d 100644
--- a/Dalamud/Interface/GameFonts/GameFontManager.cs
+++ b/Dalamud/Interface/GameFonts/GameFontManager.cs
@@ -257,7 +257,7 @@ internal class GameFontManager : IServiceType
/// Whether to call target.BuildLookupTable().
public void CopyGlyphsAcrossFonts(ImFontPtr? source, GameFontStyle target, bool missingOnly, bool rebuildLookupTable)
{
- ImGuiHelpers.CopyGlyphsAcrossFonts(source, this.fonts[target], missingOnly, rebuildLookupTable);
+ ImGuiHelpers.CopyGlyphsAcrossFonts(source ?? default, this.fonts[target], missingOnly, rebuildLookupTable);
}
///
@@ -269,7 +269,7 @@ internal class GameFontManager : IServiceType
/// Whether to call target.BuildLookupTable().
public void CopyGlyphsAcrossFonts(GameFontStyle source, ImFontPtr? target, bool missingOnly, bool rebuildLookupTable)
{
- ImGuiHelpers.CopyGlyphsAcrossFonts(this.fonts[source], target, missingOnly, rebuildLookupTable);
+ ImGuiHelpers.CopyGlyphsAcrossFonts(this.fonts[source], target ?? default, missingOnly, rebuildLookupTable);
}
///
diff --git a/Dalamud/Interface/GameFonts/GameFontStyle.cs b/Dalamud/Interface/GameFonts/GameFontStyle.cs
index 40b810161..946473df4 100644
--- a/Dalamud/Interface/GameFonts/GameFontStyle.cs
+++ b/Dalamud/Interface/GameFonts/GameFontStyle.cs
@@ -1,5 +1,3 @@
-using System;
-
namespace Dalamud.Interface.GameFonts;
///
@@ -153,7 +151,7 @@ public struct GameFontStyle
GameFontFamilyAndSize.TrumpGothic184 => 18.4f,
GameFontFamilyAndSize.TrumpGothic23 => 23,
GameFontFamilyAndSize.TrumpGothic34 => 34,
- GameFontFamilyAndSize.TrumpGothic68 => 8,
+ GameFontFamilyAndSize.TrumpGothic68 => 68,
_ => throw new InvalidOperationException(),
};
@@ -186,77 +184,77 @@ public struct GameFontStyle
/// Font family.
/// Font size in points.
/// Recommended GameFontFamilyAndSize.
- public static GameFontFamilyAndSize GetRecommendedFamilyAndSize(GameFontFamily family, float size)
- {
- if (size <= 0)
- return GameFontFamilyAndSize.Undefined;
-
- switch (family)
+ public static GameFontFamilyAndSize GetRecommendedFamilyAndSize(GameFontFamily family, float size) =>
+ family switch
{
- case GameFontFamily.Undefined:
- return GameFontFamilyAndSize.Undefined;
+ _ when size <= 0 => GameFontFamilyAndSize.Undefined,
+ GameFontFamily.Undefined => GameFontFamilyAndSize.Undefined,
+ GameFontFamily.Axis => size switch
+ {
+ <= ((int)((9.6f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.Axis96,
+ <= ((int)((12f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.Axis12,
+ <= ((int)((14f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.Axis14,
+ <= ((int)((18f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.Axis18,
+ _ => GameFontFamilyAndSize.Axis36,
+ },
+ GameFontFamily.Jupiter => size switch
+ {
+ <= ((int)((16f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.Jupiter16,
+ <= ((int)((20f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.Jupiter20,
+ <= ((int)((23f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.Jupiter23,
+ _ => GameFontFamilyAndSize.Jupiter46,
+ },
+ GameFontFamily.JupiterNumeric => size switch
+ {
+ <= ((int)((45f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.Jupiter45,
+ _ => GameFontFamilyAndSize.Jupiter90,
+ },
+ GameFontFamily.Meidinger => size switch
+ {
+ <= ((int)((16f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.Meidinger16,
+ <= ((int)((20f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.Meidinger20,
+ _ => GameFontFamilyAndSize.Meidinger40,
+ },
+ GameFontFamily.MiedingerMid => size switch
+ {
+ <= ((int)((10f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.MiedingerMid10,
+ <= ((int)((12f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.MiedingerMid12,
+ <= ((int)((14f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.MiedingerMid14,
+ <= ((int)((18f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.MiedingerMid18,
+ _ => GameFontFamilyAndSize.MiedingerMid36,
+ },
+ GameFontFamily.TrumpGothic => size switch
+ {
+ <= ((int)((18.4f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.TrumpGothic184,
+ <= ((int)((23f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.TrumpGothic23,
+ <= ((int)((34f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.TrumpGothic34,
+ _ => GameFontFamilyAndSize.TrumpGothic68,
+ },
+ _ => GameFontFamilyAndSize.Undefined,
+ };
- case GameFontFamily.Axis:
- if (size <= 9.601)
- return GameFontFamilyAndSize.Axis96;
- else if (size <= 12.001)
- return GameFontFamilyAndSize.Axis12;
- else if (size <= 14.001)
- return GameFontFamilyAndSize.Axis14;
- else if (size <= 18.001)
- return GameFontFamilyAndSize.Axis18;
- else
- return GameFontFamilyAndSize.Axis36;
-
- case GameFontFamily.Jupiter:
- if (size <= 16.001)
- return GameFontFamilyAndSize.Jupiter16;
- else if (size <= 20.001)
- return GameFontFamilyAndSize.Jupiter20;
- else if (size <= 23.001)
- return GameFontFamilyAndSize.Jupiter23;
- else
- return GameFontFamilyAndSize.Jupiter46;
-
- case GameFontFamily.JupiterNumeric:
- if (size <= 45.001)
- return GameFontFamilyAndSize.Jupiter45;
- else
- return GameFontFamilyAndSize.Jupiter90;
-
- case GameFontFamily.Meidinger:
- if (size <= 16.001)
- return GameFontFamilyAndSize.Meidinger16;
- else if (size <= 20.001)
- return GameFontFamilyAndSize.Meidinger20;
- else
- return GameFontFamilyAndSize.Meidinger40;
-
- case GameFontFamily.MiedingerMid:
- if (size <= 10.001)
- return GameFontFamilyAndSize.MiedingerMid10;
- else if (size <= 12.001)
- return GameFontFamilyAndSize.MiedingerMid12;
- else if (size <= 14.001)
- return GameFontFamilyAndSize.MiedingerMid14;
- else if (size <= 18.001)
- return GameFontFamilyAndSize.MiedingerMid18;
- else
- return GameFontFamilyAndSize.MiedingerMid36;
-
- case GameFontFamily.TrumpGothic:
- if (size <= 18.401)
- return GameFontFamilyAndSize.TrumpGothic184;
- else if (size <= 23.001)
- return GameFontFamilyAndSize.TrumpGothic23;
- else if (size <= 34.001)
- return GameFontFamilyAndSize.TrumpGothic34;
- else
- return GameFontFamilyAndSize.TrumpGothic68;
-
- default:
- return GameFontFamilyAndSize.Undefined;
+ ///
+ /// Calculates the adjustment to width resulting fron Weight and SkewStrength.
+ ///
+ /// Font header.
+ /// Glyph.
+ /// Width adjustment in pixel unit.
+ public int CalculateBaseWidthAdjustment(in FdtReader.FontTableHeader header, in FdtReader.FontTableEntry glyph)
+ {
+ var widthDelta = this.Weight;
+ switch (this.BaseSkewStrength)
+ {
+ case > 0:
+ widthDelta += (1f * this.BaseSkewStrength * (header.LineHeight - glyph.CurrentOffsetY))
+ / header.LineHeight;
+ break;
+ case < 0:
+ widthDelta -= (1f * this.BaseSkewStrength * (glyph.CurrentOffsetY + glyph.BoundingHeight))
+ / header.LineHeight;
+ break;
}
+
+ return (int)MathF.Ceiling(widthDelta);
}
///
@@ -265,16 +263,8 @@ public struct GameFontStyle
/// Font information.
/// Glyph.
/// Width adjustment in pixel unit.
- public int CalculateBaseWidthAdjustment(FdtReader reader, FdtReader.FontTableEntry glyph)
- {
- var widthDelta = this.Weight;
- if (this.BaseSkewStrength > 0)
- widthDelta += 1f * this.BaseSkewStrength * (reader.FontHeader.LineHeight - glyph.CurrentOffsetY) / reader.FontHeader.LineHeight;
- else if (this.BaseSkewStrength < 0)
- widthDelta -= 1f * this.BaseSkewStrength * (glyph.CurrentOffsetY + glyph.BoundingHeight) / reader.FontHeader.LineHeight;
-
- return (int)Math.Ceiling(widthDelta);
- }
+ public int CalculateBaseWidthAdjustment(FdtReader reader, FdtReader.FontTableEntry glyph) =>
+ this.CalculateBaseWidthAdjustment(reader.FontHeader, glyph);
///
public override string ToString()
diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs
index d5394fe8d..9de87c6e3 100644
--- a/Dalamud/Interface/Internal/InterfaceManager.cs
+++ b/Dalamud/Interface/Internal/InterfaceManager.cs
@@ -52,8 +52,16 @@ namespace Dalamud.Interface.Internal;
[ServiceManager.BlockingEarlyLoadedService]
internal class InterfaceManager : IDisposable, IServiceType
{
- private const float DefaultFontSizePt = 12.0f;
- private const float DefaultFontSizePx = DefaultFontSizePt * 4.0f / 3.0f;
+ ///
+ /// The default font size, in points.
+ ///
+ public const float DefaultFontSizePt = 12.0f;
+
+ ///
+ /// The default font size, in pixels.
+ ///
+ public const float DefaultFontSizePx = (DefaultFontSizePt * 4.0f) / 3.0f;
+
private const ushort Fallback1Codepoint = 0x3013; // Geta mark; FFXIV uses this to indicate that a glyph is missing.
private const ushort Fallback2Codepoint = '-'; // FFXIV uses dash if Geta mark is unavailable.
diff --git a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs
index d59b50e58..e9d4152a5 100644
--- a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs
@@ -18,39 +18,39 @@ internal class DataWindow : Window
{
private readonly IDataWindowWidget[] modules =
{
- new ServicesWidget(),
- new AddressesWidget(),
- new ObjectTableWidget(),
- new FateTableWidget(),
- new SeFontTestWidget(),
- new FontAwesomeTestWidget(),
- new PartyListWidget(),
- new BuddyListWidget(),
- new PluginIpcWidget(),
- new ConditionWidget(),
- new GaugeWidget(),
- new CommandWidget(),
- new AddonWidget(),
new AddonInspectorWidget(),
+ new AddonLifecycleWidget(),
+ new AddonWidget(),
+ new AddressesWidget(),
+ new AetherytesWidget(),
new AtkArrayDataBrowserWidget(),
+ new BuddyListWidget(),
+ new CommandWidget(),
+ new ConditionWidget(),
+ new ConfigurationWidget(),
+ new DataShareWidget(),
+ new DtrBarWidget(),
+ new FateTableWidget(),
+ new FlyTextWidget(),
+ new FontAwesomeTestWidget(),
+ new GamepadWidget(),
+ new GaugeWidget(),
+ new HookWidget(),
+ new IconBrowserWidget(),
+ new ImGuiWidget(),
+ new KeyStateWidget(),
+ new NetworkMonitorWidget(),
+ new ObjectTableWidget(),
+ new PartyListWidget(),
+ new PluginIpcWidget(),
+ new SeFontTestWidget(),
+ new ServicesWidget(),
new StartInfoWidget(),
new TargetWidget(),
- new ToastWidget(),
- new FlyTextWidget(),
- new ImGuiWidget(),
- new TexWidget(),
- new KeyStateWidget(),
- new GamepadWidget(),
- new ConfigurationWidget(),
new TaskSchedulerWidget(),
- new HookWidget(),
- new AetherytesWidget(),
- new DtrBarWidget(),
+ new TexWidget(),
+ new ToastWidget(),
new UIColorWidget(),
- new DataShareWidget(),
- new NetworkMonitorWidget(),
- new IconBrowserWidget(),
- new AddonLifecycleWidget(),
};
private readonly IOrderedEnumerable orderedModules;
diff --git a/Dalamud/Interface/Internal/Windows/Data/IDataWindowWidget.cs b/Dalamud/Interface/Internal/Windows/Data/IDataWindowWidget.cs
index 0e12e4c51..78df015ed 100644
--- a/Dalamud/Interface/Internal/Windows/Data/IDataWindowWidget.cs
+++ b/Dalamud/Interface/Internal/Windows/Data/IDataWindowWidget.cs
@@ -1,7 +1,6 @@
-using System;
-using System.Linq;
+using System.Linq;
-namespace Dalamud.Interface.Internal.Windows;
+namespace Dalamud.Interface.Internal.Windows.Data;
///
/// Class representing a date window entry.
diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonLifecycleWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonLifecycleWidget.cs
index 7dc8e2f3c..0e654d316 100644
--- a/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonLifecycleWidget.cs
+++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonLifecycleWidget.cs
@@ -28,8 +28,14 @@ public class AddonLifecycleWidget : IDataWindowWidget
///
public void Load()
{
- this.AddonLifecycle = Service.GetNullable();
- if (this.AddonLifecycle is not null) this.Ready = true;
+ Service
+ .GetAsync()
+ .ContinueWith(
+ r =>
+ {
+ this.AddonLifecycle = r.Result;
+ this.Ready = true;
+ });
}
///
diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/FontAwesomeTestWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/FontAwesomeTestWidget.cs
index 26bd2e623..22f615e8a 100644
--- a/Dalamud/Interface/Internal/Windows/Data/Widgets/FontAwesomeTestWidget.cs
+++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/FontAwesomeTestWidget.cs
@@ -38,12 +38,30 @@ internal class FontAwesomeTestWidget : IDataWindowWidget
public void Draw()
{
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
-
- this.iconCategories ??= FontAwesomeHelpers.GetCategories();
+
+ this.iconCategories ??= new[] { "(Show All)", "(Undefined)" }
+ .Concat(FontAwesomeHelpers.GetCategories().Skip(1))
+ .ToArray();
if (this.iconSearchChanged)
{
- this.icons = FontAwesomeHelpers.SearchIcons(this.iconSearchInput, this.iconCategories[this.selectedIconCategory]);
+ if (this.iconSearchInput == string.Empty && this.selectedIconCategory <= 1)
+ {
+ var en = InterfaceManager.IconFont.GlyphsWrapped()
+ .Select(x => (FontAwesomeIcon)x.Codepoint)
+ .Where(x => (ushort)x is >= 0xE000 and < 0xF000);
+ en = this.selectedIconCategory == 0
+ ? en.Concat(FontAwesomeHelpers.SearchIcons(string.Empty, string.Empty))
+ : en.Except(FontAwesomeHelpers.SearchIcons(string.Empty, string.Empty));
+ this.icons = en.Distinct().Order().ToList();
+ }
+ else
+ {
+ this.icons = FontAwesomeHelpers.SearchIcons(
+ this.iconSearchInput,
+ this.selectedIconCategory <= 1 ? string.Empty : this.iconCategories[this.selectedIconCategory]);
+ }
+
this.iconNames = this.icons.Select(icon => Enum.GetName(icon)!).ToList();
this.iconSearchChanged = false;
}
diff --git a/Dalamud/Interface/Internal/Windows/ProfilerWindow.cs b/Dalamud/Interface/Internal/Windows/ProfilerWindow.cs
index 16f253da9..cd653143b 100644
--- a/Dalamud/Interface/Internal/Windows/ProfilerWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/ProfilerWindow.cs
@@ -1,4 +1,3 @@
-using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -46,7 +45,7 @@ public class ProfilerWindow : Window
ImGui.Text("Timings");
- var childHeight = Math.Max(300, 20 * (2 + this.occupied.Count));
+ var childHeight = Math.Max(300, 20 * (2.5f + this.occupied.Count));
if (ImGui.BeginChild("Timings", new Vector2(0, childHeight), true))
{
@@ -115,7 +114,7 @@ public class ProfilerWindow : Window
parentDepthDict[timingHandle.Id] = depth;
startX = Math.Max(startX, 0);
- endX = Math.Max(endX, 0);
+ endX = Math.Max(endX, startX + (ImGuiHelpers.GlobalScale * 16));
Vector4 rectColor;
if (this.occupied[depth].Count % 2 == 0)
@@ -129,11 +128,6 @@ public class ProfilerWindow : Window
if (maxRectDept < depth)
maxRectDept = (uint)depth;
- if (startX == endX)
- {
- continue;
- }
-
var minPos = pos + new Vector2((uint)startX, 20 * depth);
var maxPos = pos + new Vector2((uint)endX, 20 * (depth + 1));
@@ -231,22 +225,22 @@ public class ProfilerWindow : Window
ImGui.EndChild();
var sliderMin = (float)this.min / 1000f;
- if (ImGui.SliderFloat("Start", ref sliderMin, (float)actualMin / 1000f, (float)this.max / 1000f, "%.1fs"))
+ if (ImGui.SliderFloat("Start", ref sliderMin, (float)actualMin / 1000f, (float)this.max / 1000f, "%.2fs"))
{
this.min = sliderMin * 1000f;
}
var sliderMax = (float)this.max / 1000f;
- if (ImGui.SliderFloat("End", ref sliderMax, (float)this.min / 1000f, (float)actualMax / 1000f, "%.1fs"))
+ if (ImGui.SliderFloat("End", ref sliderMax, (float)this.min / 1000f, (float)actualMax / 1000f, "%.2fs"))
{
this.max = sliderMax * 1000f;
}
- var sizeShown = (float)(this.max - this.min);
- var sizeActual = (float)(actualMax - actualMin);
- if (ImGui.SliderFloat("Size", ref sizeShown, sizeActual / 10f, sizeActual, "%.1fs"))
+ var sizeShown = (float)(this.max - this.min) / 1000f;
+ var sizeActual = (float)(actualMax - actualMin) / 1000f;
+ if (ImGui.SliderFloat("Size", ref sizeShown, sizeActual / 10f, sizeActual, "%.2fs"))
{
- this.max = this.min + sizeShown;
+ this.max = this.min + (sizeShown * 1000f);
}
ImGui.Text("Min: " + actualMin.ToString("0.000"));
@@ -257,6 +251,7 @@ public class ProfilerWindow : Window
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Internals")]
private class RectInfo
{
+ // ReSharper disable once NotNullOrRequiredMemberIsNotInitialized <- well you're wrong
internal TimingHandle Timing;
internal Vector2 MinPos;
internal Vector2 MaxPos;
diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs
index 7a6f894c1..02e8ce789 100644
--- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs
+++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs
@@ -1,5 +1,6 @@
-using System;
-using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Numerics;
using CheapLoc;
using Dalamud.Configuration.Internal;
@@ -16,6 +17,16 @@ namespace Dalamud.Interface.Internal.Windows.Settings.Tabs;
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Internals")]
public class SettingsTabLook : SettingsTab
{
+ private static readonly (string, float)[] GlobalUiScalePresets =
+ {
+ ("9.6pt##DalamudSettingsGlobalUiScaleReset96", 9.6f / InterfaceManager.DefaultFontSizePt),
+ ("12pt##DalamudSettingsGlobalUiScaleReset12", 12f / InterfaceManager.DefaultFontSizePt),
+ ("14pt##DalamudSettingsGlobalUiScaleReset14", 14f / InterfaceManager.DefaultFontSizePt),
+ ("18pt##DalamudSettingsGlobalUiScaleReset18", 18f / InterfaceManager.DefaultFontSizePt),
+ ("24pt##DalamudSettingsGlobalUiScaleReset24", 24f / InterfaceManager.DefaultFontSizePt),
+ ("36pt##DalamudSettingsGlobalUiScaleReset36", 36f / InterfaceManager.DefaultFontSizePt),
+ };
+
private float globalUiScale;
private float fontGamma;
@@ -135,55 +146,22 @@ public class SettingsTabLook : SettingsTab
{
var interfaceManager = Service.Get();
- ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3);
+ ImGui.AlignTextToFramePadding();
ImGui.Text(Loc.Localize("DalamudSettingsGlobalUiScale", "Global Font Scale"));
- ImGui.SameLine();
- ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 3);
- if (ImGui.Button("9.6pt##DalamudSettingsGlobalUiScaleReset96"))
- {
- this.globalUiScale = 9.6f / 12.0f;
- ImGui.GetIO().FontGlobalScale = this.globalUiScale;
- interfaceManager.RebuildFonts();
- }
- ImGui.SameLine();
- if (ImGui.Button("12pt##DalamudSettingsGlobalUiScaleReset12"))
+ var buttonSize =
+ GlobalUiScalePresets
+ .Select(x => ImGui.CalcTextSize(x.Item1, 0, x.Item1.IndexOf('#')))
+ .Aggregate(Vector2.Zero, Vector2.Max)
+ + (ImGui.GetStyle().FramePadding * 2);
+ foreach (var (buttonLabel, scale) in GlobalUiScalePresets)
{
- this.globalUiScale = 1.0f;
- ImGui.GetIO().FontGlobalScale = this.globalUiScale;
- interfaceManager.RebuildFonts();
- }
-
- ImGui.SameLine();
- if (ImGui.Button("14pt##DalamudSettingsGlobalUiScaleReset14"))
- {
- this.globalUiScale = 14.0f / 12.0f;
- ImGui.GetIO().FontGlobalScale = this.globalUiScale;
- interfaceManager.RebuildFonts();
- }
-
- ImGui.SameLine();
- if (ImGui.Button("18pt##DalamudSettingsGlobalUiScaleReset18"))
- {
- this.globalUiScale = 18.0f / 12.0f;
- ImGui.GetIO().FontGlobalScale = this.globalUiScale;
- interfaceManager.RebuildFonts();
- }
-
- ImGui.SameLine();
- if (ImGui.Button("24pt##DalamudSettingsGlobalUiScaleReset24"))
- {
- this.globalUiScale = 24.0f / 12.0f;
- ImGui.GetIO().FontGlobalScale = this.globalUiScale;
- interfaceManager.RebuildFonts();
- }
-
- ImGui.SameLine();
- if (ImGui.Button("36pt##DalamudSettingsGlobalUiScaleReset36"))
- {
- this.globalUiScale = 36.0f / 12.0f;
- ImGui.GetIO().FontGlobalScale = this.globalUiScale;
- interfaceManager.RebuildFonts();
+ ImGui.SameLine();
+ if (ImGui.Button(buttonLabel, buttonSize) && Math.Abs(this.globalUiScale - scale) > float.Epsilon)
+ {
+ ImGui.GetIO().FontGlobalScale = this.globalUiScale = scale;
+ interfaceManager.RebuildFonts();
+ }
}
var globalUiScaleInPt = 12f * this.globalUiScale;
@@ -198,10 +176,9 @@ public class SettingsTabLook : SettingsTab
ImGuiHelpers.ScaledDummy(5);
- ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3);
+ ImGui.AlignTextToFramePadding();
ImGui.Text(Loc.Localize("DalamudSettingsFontGamma", "Font Gamma"));
ImGui.SameLine();
- ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 3);
if (ImGui.Button(Loc.Localize("DalamudSettingsIndividualConfigResetToDefaultValue", "Reset") + "##DalamudSettingsFontGammaReset"))
{
this.fontGamma = 1.4f;
diff --git a/Dalamud/Interface/Utility/ImGuiHelpers.cs b/Dalamud/Interface/Utility/ImGuiHelpers.cs
index d11e1ce1c..579d93f86 100644
--- a/Dalamud/Interface/Utility/ImGuiHelpers.cs
+++ b/Dalamud/Interface/Utility/ImGuiHelpers.cs
@@ -1,8 +1,9 @@
using System.Collections.Generic;
-using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
+using System.Runtime.InteropServices;
+using Dalamud.Configuration.Internal;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Interface.Utility.Raii;
using ImGuiNET;
@@ -25,6 +26,20 @@ public static class ImGuiHelpers
///
public static float GlobalScale { get; private set; }
+ ///
+ /// Gets a value indicating whether ImGui is initialized and ready for use.
+ /// This does not necessarily mean you can call drawing functions.
+ ///
+ public static unsafe bool IsImGuiInitialized =>
+ ImGui.GetCurrentContext() is not 0 && ImGui.GetIO().NativePtr is not null;
+
+ ///
+ /// Gets the global Dalamud scale; even available before drawing is ready.
+ /// If you are sure that drawing is ready, at the point of using this, use instead.
+ ///
+ public static float GlobalScaleSafe =>
+ IsImGuiInitialized ? ImGui.GetIO().FontGlobalScale : Service.Get().GlobalUiScale;
+
///
/// Check if the current ImGui window is on the main viewport.
/// Only valid within a window.
@@ -174,6 +189,47 @@ public static class ImGuiHelpers
}
}
+ ///
+ /// Unscales fonts after they have been rendered onto atlas.
+ ///
+ /// Font to scale.
+ /// Scale.
+ /// If a positive number is given, numbers will be rounded to this.
+ public static unsafe void AdjustGlyphMetrics(this ImFontPtr fontPtr, float scale, float round = 0f)
+ {
+ Func rounder = round > 0 ? x => MathF.Round(x * round) / round : x => x;
+
+ var font = fontPtr.NativePtr;
+ font->FontSize = rounder(font->FontSize * scale);
+ font->Ascent = rounder(font->Ascent * scale);
+ font->Descent = font->FontSize - font->Ascent;
+ if (font->ConfigData != null)
+ font->ConfigData->SizePixels = rounder(font->ConfigData->SizePixels * scale);
+
+ foreach (ref var glyphHotDataReal in new Span(
+ (void*)font->IndexedHotData.Data,
+ font->IndexedHotData.Size))
+ {
+ glyphHotDataReal.AdvanceX = rounder(glyphHotDataReal.AdvanceX * scale);
+ glyphHotDataReal.OccupiedWidth = rounder(glyphHotDataReal.OccupiedWidth * scale);
+ }
+
+ foreach (ref var glyphReal in new Span((void*)font->Glyphs.Data, font->Glyphs.Size))
+ {
+ glyphReal.X0 *= scale;
+ glyphReal.X1 *= scale;
+ glyphReal.Y0 *= scale;
+ glyphReal.Y1 *= scale;
+ glyphReal.AdvanceX = rounder(glyphReal.AdvanceX * scale);
+ }
+
+ foreach (ref var kp in new Span((void*)font->KerningPairs.Data, font->KerningPairs.Size))
+ kp.AdvanceXAdjustment = rounder(kp.AdvanceXAdjustment * scale);
+
+ foreach (ref var fkp in new Span((void*)font->FrequentKerningPairs.Data, font->FrequentKerningPairs.Size))
+ fkp = rounder(fkp * scale);
+ }
+
///
/// Fills missing glyphs in target font from source font, if both are not null.
///
@@ -183,71 +239,110 @@ public static class ImGuiHelpers
/// Whether to call target.BuildLookupTable().
/// Low codepoint range to copy.
/// High codepoing range to copy.
- public static void CopyGlyphsAcrossFonts(ImFontPtr? source, ImFontPtr? target, bool missingOnly, bool rebuildLookupTable, int rangeLow = 32, int rangeHigh = 0xFFFE)
+ [Obsolete("Use the non-nullable variant.", true)]
+ public static void CopyGlyphsAcrossFonts(
+ ImFontPtr? source,
+ ImFontPtr? target,
+ bool missingOnly,
+ bool rebuildLookupTable = true,
+ int rangeLow = 32,
+ int rangeHigh = 0xFFFE) =>
+ CopyGlyphsAcrossFonts(
+ source ?? default,
+ target ?? default,
+ missingOnly,
+ rebuildLookupTable,
+ rangeLow,
+ rangeHigh);
+
+ ///
+ /// Fills missing glyphs in target font from source font, if both are not null.
+ ///
+ /// Source font.
+ /// Target font.
+ /// Whether to copy missing glyphs only.
+ /// Whether to call target.BuildLookupTable().
+ /// Low codepoint range to copy.
+ /// High codepoing range to copy.
+ public static unsafe void CopyGlyphsAcrossFonts(
+ ImFontPtr source,
+ ImFontPtr target,
+ bool missingOnly,
+ bool rebuildLookupTable = true,
+ int rangeLow = 32,
+ int rangeHigh = 0xFFFE)
{
- if (!source.HasValue || !target.HasValue)
+ if (!source.IsNotNullAndLoaded() || !target.IsNotNullAndLoaded())
return;
- var scale = target.Value!.FontSize / source.Value!.FontSize;
+ var changed = false;
+ var scale = target.FontSize / source.FontSize;
var addedCodepoints = new HashSet();
- unsafe
+
+ if (source.Glyphs.Size == 0)
+ return;
+
+ var glyphs = (ImFontGlyphReal*)source.Glyphs.Data;
+ if (glyphs is null)
+ throw new InvalidOperationException("Glyphs data is empty but size is >0?");
+
+ for (int j = 0, k = source.Glyphs.Size; j < k; j++)
{
- var glyphs = (ImFontGlyphReal*)source.Value!.Glyphs.Data;
- for (int j = 0, k = source.Value!.Glyphs.Size; j < k; j++)
+ var glyph = &glyphs![j];
+ if (glyph->Codepoint < rangeLow || glyph->Codepoint > rangeHigh)
+ continue;
+
+ var prevGlyphPtr = (ImFontGlyphReal*)target.FindGlyphNoFallback((ushort)glyph->Codepoint).NativePtr;
+ if ((IntPtr)prevGlyphPtr == IntPtr.Zero)
{
- Debug.Assert(glyphs != null, nameof(glyphs) + " != null");
-
- var glyph = &glyphs[j];
- if (glyph->Codepoint < rangeLow || glyph->Codepoint > rangeHigh)
- continue;
-
- var prevGlyphPtr = (ImFontGlyphReal*)target.Value!.FindGlyphNoFallback((ushort)glyph->Codepoint).NativePtr;
- if ((IntPtr)prevGlyphPtr == IntPtr.Zero)
- {
- addedCodepoints.Add(glyph->Codepoint);
- target.Value!.AddGlyph(
- target.Value!.ConfigData,
- (ushort)glyph->Codepoint,
- glyph->TextureIndex,
- glyph->X0 * scale,
- ((glyph->Y0 - source.Value!.Ascent) * scale) + target.Value!.Ascent,
- glyph->X1 * scale,
- ((glyph->Y1 - source.Value!.Ascent) * scale) + target.Value!.Ascent,
- glyph->U0,
- glyph->V0,
- glyph->U1,
- glyph->V1,
- glyph->AdvanceX * scale);
- }
- else if (!missingOnly)
- {
- addedCodepoints.Add(glyph->Codepoint);
- prevGlyphPtr->TextureIndex = glyph->TextureIndex;
- prevGlyphPtr->X0 = glyph->X0 * scale;
- prevGlyphPtr->Y0 = ((glyph->Y0 - source.Value!.Ascent) * scale) + target.Value!.Ascent;
- prevGlyphPtr->X1 = glyph->X1 * scale;
- prevGlyphPtr->Y1 = ((glyph->Y1 - source.Value!.Ascent) * scale) + target.Value!.Ascent;
- prevGlyphPtr->U0 = glyph->U0;
- prevGlyphPtr->V0 = glyph->V0;
- prevGlyphPtr->U1 = glyph->U1;
- prevGlyphPtr->V1 = glyph->V1;
- prevGlyphPtr->AdvanceX = glyph->AdvanceX * scale;
- }
+ addedCodepoints.Add(glyph->Codepoint);
+ target.AddGlyph(
+ target.ConfigData,
+ (ushort)glyph->Codepoint,
+ glyph->TextureIndex,
+ glyph->X0 * scale,
+ ((glyph->Y0 - source.Ascent) * scale) + target.Ascent,
+ glyph->X1 * scale,
+ ((glyph->Y1 - source.Ascent) * scale) + target.Ascent,
+ glyph->U0,
+ glyph->V0,
+ glyph->U1,
+ glyph->V1,
+ glyph->AdvanceX * scale);
+ changed = true;
}
-
- var kernPairs = source.Value!.KerningPairs;
- for (int j = 0, k = kernPairs.Size; j < k; j++)
+ else if (!missingOnly)
{
- if (!addedCodepoints.Contains(kernPairs[j].Left))
- continue;
- if (!addedCodepoints.Contains(kernPairs[j].Right))
- continue;
- target.Value.AddKerningPair(kernPairs[j].Left, kernPairs[j].Right, kernPairs[j].AdvanceXAdjustment);
+ addedCodepoints.Add(glyph->Codepoint);
+ prevGlyphPtr->TextureIndex = glyph->TextureIndex;
+ prevGlyphPtr->X0 = glyph->X0 * scale;
+ prevGlyphPtr->Y0 = ((glyph->Y0 - source.Ascent) * scale) + target.Ascent;
+ prevGlyphPtr->X1 = glyph->X1 * scale;
+ prevGlyphPtr->Y1 = ((glyph->Y1 - source.Ascent) * scale) + target.Ascent;
+ prevGlyphPtr->U0 = glyph->U0;
+ prevGlyphPtr->V0 = glyph->V0;
+ prevGlyphPtr->U1 = glyph->U1;
+ prevGlyphPtr->V1 = glyph->V1;
+ prevGlyphPtr->AdvanceX = glyph->AdvanceX * scale;
}
}
- if (rebuildLookupTable && target.Value!.Glyphs.Size > 0)
- target.Value!.BuildLookupTableNonstandard();
+ if (target.Glyphs.Size == 0)
+ return;
+
+ var kernPairs = source.KerningPairs;
+ for (int j = 0, k = kernPairs.Size; j < k; j++)
+ {
+ if (!addedCodepoints.Contains(kernPairs[j].Left))
+ continue;
+ if (!addedCodepoints.Contains(kernPairs[j].Right))
+ continue;
+ target.AddKerningPair(kernPairs[j].Left, kernPairs[j].Right, kernPairs[j].AdvanceXAdjustment);
+ changed = true;
+ }
+
+ if (changed && rebuildLookupTable)
+ target.BuildLookupTableNonstandard();
}
///
@@ -302,21 +397,35 @@ public static class ImGuiHelpers
/// Center the ImGui cursor for a certain text.
///
/// The text to center for.
- public static void CenterCursorForText(string text)
- {
- var textWidth = ImGui.CalcTextSize(text).X;
- CenterCursorFor((int)textWidth);
- }
+ public static void CenterCursorForText(string text) => CenterCursorFor(ImGui.CalcTextSize(text).X);
///
/// Center the ImGui cursor for an item with a certain width.
///
/// The width to center for.
- public static void CenterCursorFor(int itemWidth)
- {
- var window = (int)ImGui.GetWindowWidth();
- ImGui.SetCursorPosX((window / 2) - (itemWidth / 2));
- }
+ public static void CenterCursorFor(float itemWidth) =>
+ ImGui.SetCursorPosX((int)((ImGui.GetWindowWidth() - itemWidth) / 2));
+
+ ///
+ /// Determines whether is empty.
+ ///
+ /// The pointer.
+ /// Whether it is empty.
+ public static unsafe bool IsNull(this ImFontPtr ptr) => ptr.NativePtr == null;
+
+ ///
+ /// Determines whether is not null and loaded.
+ ///
+ /// The pointer.
+ /// Whether it is empty.
+ public static unsafe bool IsNotNullAndLoaded(this ImFontPtr ptr) => ptr.NativePtr != null && ptr.IsLoaded();
+
+ ///
+ /// Determines whether is empty.
+ ///
+ /// The pointer.
+ /// Whether it is empty.
+ public static unsafe bool IsNull(this ImFontAtlasPtr ptr) => ptr.NativePtr == null;
///
/// Get data needed for each new frame.
@@ -330,19 +439,57 @@ public static class ImGuiHelpers
/// ImFontGlyph the correct version.
///
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "ImGui internals")]
+ [StructLayout(LayoutKind.Explicit, Size = 40)]
public struct ImFontGlyphReal
{
+ [FieldOffset(0)]
public uint ColoredVisibleTextureIndexCodepoint;
+
+ [FieldOffset(4)]
public float AdvanceX;
+
+ [FieldOffset(8)]
public float X0;
+
+ [FieldOffset(12)]
public float Y0;
+
+ [FieldOffset(16)]
public float X1;
+
+ [FieldOffset(20)]
public float Y1;
+
+ [FieldOffset(24)]
public float U0;
+
+ [FieldOffset(28)]
public float V0;
+
+ [FieldOffset(32)]
public float U1;
+
+ [FieldOffset(36)]
public float V1;
+ [FieldOffset(8)]
+ public Vector2 XY0;
+
+ [FieldOffset(16)]
+ public Vector2 XY1;
+
+ [FieldOffset(24)]
+ public Vector2 UV0;
+
+ [FieldOffset(32)]
+ public Vector2 UV1;
+
+ [FieldOffset(8)]
+ public Vector4 XY;
+
+ [FieldOffset(24)]
+ public Vector4 UV;
+
private const uint ColoredMask /*****/ = 0b_00000000_00000000_00000000_00000001u;
private const uint VisibleMask /*****/ = 0b_00000000_00000000_00000000_00000010u;
private const uint TextureMask /*****/ = 0b_00000000_00000000_00000111_11111100u;
@@ -390,7 +537,7 @@ public static class ImGuiHelpers
private const uint UseBisectMask /***/ = 0b_00000000_00000000_00000000_00000001u;
private const uint OffsetMask /******/ = 0b_00000000_00001111_11111111_11111110u;
- private const uint CountMask /*******/ = 0b_11111111_11110000_00000111_11111100u;
+ private const uint CountMask /*******/ = 0b_11111111_11110000_00000000_00000000u;
private const int UseBisectShift = 0;
private const int OffsetShift = 1;
@@ -419,6 +566,7 @@ public static class ImGuiHelpers
/// ImFontAtlasCustomRect the correct version.
///
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "ImGui internals")]
+ [StructLayout(LayoutKind.Sequential)]
public unsafe struct ImFontAtlasCustomRectReal
{
public ushort Width;
@@ -431,10 +579,10 @@ public static class ImGuiHelpers
public ImFont* Font;
private const uint TextureIndexMask /***/ = 0b_00000000_00000000_00000111_11111100u;
- private const uint GlyphIDMask /********/ = 0b_11111111_11111111_11111000_00000000u;
+ private const uint GlyphIdMask /********/ = 0b_11111111_11111111_11111000_00000000u;
private const int TextureIndexShift = 2;
- private const int GlyphIDShift = 11;
+ private const int GlyphIdShift = 11;
public int TextureIndex
{
@@ -444,8 +592,8 @@ public static class ImGuiHelpers
public int GlyphId
{
- get => (int)(this.TextureIndexAndGlyphId & GlyphIDMask) >> GlyphIDShift;
- set => this.TextureIndexAndGlyphId = (this.TextureIndexAndGlyphId & ~GlyphIDMask) | ((uint)value << GlyphIDShift);
+ get => (int)(this.TextureIndexAndGlyphId & GlyphIdMask) >> GlyphIdShift;
+ set => this.TextureIndexAndGlyphId = (this.TextureIndexAndGlyphId & ~GlyphIdMask) | ((uint)value << GlyphIdShift);
}
}
}
diff --git a/Dalamud/Interface/Utility/ImVectorWrapper.cs b/Dalamud/Interface/Utility/ImVectorWrapper.cs
new file mode 100644
index 000000000..67b002179
--- /dev/null
+++ b/Dalamud/Interface/Utility/ImVectorWrapper.cs
@@ -0,0 +1,687 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Numerics;
+
+using ImGuiNET;
+
+using JetBrains.Annotations;
+
+namespace Dalamud.Interface.Utility;
+
+///
+/// Utility methods for .
+///
+public static class ImVectorWrapper
+{
+ ///
+ /// Creates a new instance of the struct, initialized with
+ /// .
+ /// You must call after use.
+ ///
+ /// The item type.
+ /// The initial data.
+ /// The destroyer function to call on item removal.
+ /// The minimum capacity of the new vector.
+ /// The new wrapped vector, that has to be disposed after use.
+ public static ImVectorWrapper CreateFromEnumerable(
+ IEnumerable sourceEnumerable,
+ ImVectorWrapper.ImGuiNativeDestroyDelegate? destroyer = null,
+ int minCapacity = 0)
+ where T : unmanaged
+ {
+ var res = new ImVectorWrapper(0, destroyer);
+ try
+ {
+ switch (sourceEnumerable)
+ {
+ case T[] c:
+ res.SetCapacity(Math.Max(minCapacity, c.Length + 1));
+ res.LengthUnsafe = c.Length;
+ c.AsSpan().CopyTo(res.DataSpan);
+ break;
+ case ICollection c:
+ res.SetCapacity(Math.Max(minCapacity, c.Count + 1));
+ res.AddRange(sourceEnumerable);
+ break;
+ case ICollection c:
+ res.SetCapacity(Math.Max(minCapacity, c.Count + 1));
+ res.AddRange(sourceEnumerable);
+ break;
+ default:
+ res.SetCapacity(minCapacity);
+ res.AddRange(sourceEnumerable);
+ res.EnsureCapacity(res.LengthUnsafe + 1);
+ break;
+ }
+
+ // Null termination
+ Debug.Assert(res.LengthUnsafe < res.CapacityUnsafe, "Capacity must be more than source length + 1");
+ res.StorageSpan[res.LengthUnsafe] = default;
+
+ return res;
+ }
+ catch
+ {
+ res.Dispose();
+ throw;
+ }
+ }
+
+ ///
+ /// Creates a new instance of the struct, initialized with
+ /// .
+ /// You must call after use.
+ ///
+ /// The item type.
+ /// The initial data.
+ /// The destroyer function to call on item removal.
+ /// The minimum capacity of the new vector.
+ /// The new wrapped vector, that has to be disposed after use.
+ public static ImVectorWrapper CreateFromSpan(
+ ReadOnlySpan sourceSpan,
+ ImVectorWrapper.ImGuiNativeDestroyDelegate? destroyer = null,
+ int minCapacity = 0)
+ where T : unmanaged
+ {
+ var res = new ImVectorWrapper(Math.Max(minCapacity, sourceSpan.Length + 1), destroyer);
+ try
+ {
+ res.LengthUnsafe = sourceSpan.Length;
+ sourceSpan.CopyTo(res.DataSpan);
+
+ // Null termination
+ Debug.Assert(res.LengthUnsafe < res.CapacityUnsafe, "Capacity must be more than source length + 1");
+ res.StorageSpan[res.LengthUnsafe] = default;
+ return res;
+ }
+ catch
+ {
+ res.Dispose();
+ throw;
+ }
+ }
+
+ ///
+ /// Wraps into a .
+ /// This does not need to be disposed.
+ ///
+ /// The owner object.
+ /// The wrapped vector.
+ public static unsafe ImVectorWrapper ConfigDataWrapped(this ImFontAtlasPtr obj) =>
+ obj.NativePtr is null
+ ? throw new NullReferenceException()
+ : new(&obj.NativePtr->ConfigData, ImGuiNative.ImFontConfig_destroy);
+
+ ///
+ /// Wraps into a .
+ /// This does not need to be disposed.
+ ///
+ /// The owner object.
+ /// The wrapped vector.
+ public static unsafe ImVectorWrapper FontsWrapped(this ImFontAtlasPtr obj) =>
+ obj.NativePtr is null
+ ? throw new NullReferenceException()
+ : new(&obj.NativePtr->Fonts, x => ImGuiNative.ImFont_destroy(x->NativePtr));
+
+ ///
+ /// Wraps into a .
+ /// This does not need to be disposed.
+ ///
+ /// The owner object.
+ /// The wrapped vector.
+ public static unsafe ImVectorWrapper TexturesWrapped(this ImFontAtlasPtr obj) =>
+ obj.NativePtr is null
+ ? throw new NullReferenceException()
+ : new(&obj.NativePtr->Textures);
+
+ ///
+ /// Wraps into a .
+ /// This does not need to be disposed.
+ ///
+ /// The owner object.
+ /// The wrapped vector.
+ public static unsafe ImVectorWrapper GlyphsWrapped(this ImFontPtr obj) =>
+ obj.NativePtr is null
+ ? throw new NullReferenceException()
+ : new(&obj.NativePtr->Glyphs);
+
+ ///
+ /// Wraps into a .
+ /// This does not need to be disposed.
+ ///
+ /// The owner object.
+ /// The wrapped vector.
+ public static unsafe ImVectorWrapper IndexedHotDataWrapped(this ImFontPtr obj)
+ => obj.NativePtr is null
+ ? throw new NullReferenceException()
+ : new(&obj.NativePtr->IndexedHotData);
+
+ ///
+ /// Wraps into a .
+ /// This does not need to be disposed.
+ ///
+ /// The owner object.
+ /// The wrapped vector.
+ public static unsafe ImVectorWrapper IndexLookupWrapped(this ImFontPtr obj) =>
+ obj.NativePtr is null
+ ? throw new NullReferenceException()
+ : new(&obj.NativePtr->IndexLookup);
+}
+
+///
+/// Wrapper for ImVector.
+///
+/// Contained type.
+public unsafe struct ImVectorWrapper : IList, IList, IReadOnlyList, IDisposable
+ where T : unmanaged
+{
+ private ImVector* vector;
+ private ImGuiNativeDestroyDelegate? destroyer;
+
+ ///
+ /// Initializes a new instance of the struct.
+ /// If is set to true, you must call after use,
+ /// and the underlying memory for must have been allocated using
+ /// . Otherwise, it will crash.
+ ///
+ /// The underlying vector.
+ /// The destroyer function to call on item removal.
+ /// Whether this wrapper owns the vector.
+ public ImVectorWrapper(
+ [NotNull] ImVector* vector,
+ ImGuiNativeDestroyDelegate? destroyer = null,
+ bool ownership = false)
+ {
+ if (vector is null)
+ throw new ArgumentException($"{nameof(vector)} cannot be null.", nameof(this.vector));
+
+ this.vector = vector;
+ this.destroyer = destroyer;
+ this.HasOwnership = ownership;
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ /// You must call after use.
+ ///
+ /// The initial capacity.
+ /// The destroyer function to call on item removal.
+ public ImVectorWrapper(int initialCapacity = 0, ImGuiNativeDestroyDelegate? destroyer = null)
+ {
+ if (initialCapacity < 0)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(initialCapacity),
+ initialCapacity,
+ $"{nameof(initialCapacity)} cannot be a negative number.");
+ }
+
+ this.vector = (ImVector*)ImGuiNative.igMemAlloc((uint)sizeof(ImVector));
+ if (this.vector is null)
+ throw new OutOfMemoryException();
+ *this.vector = default;
+ this.HasOwnership = true;
+ this.destroyer = destroyer;
+
+ try
+ {
+ this.EnsureCapacity(initialCapacity);
+ }
+ catch
+ {
+ ImGuiNative.igMemFree(this.vector);
+ this.vector = null;
+ this.HasOwnership = false;
+ this.destroyer = null;
+ throw;
+ }
+ }
+
+ ///
+ /// Destroy callback for items.
+ ///
+ /// Pointer to self.
+ public delegate void ImGuiNativeDestroyDelegate(T* self);
+
+ ///
+ /// Gets the raw vector.
+ ///
+ public ImVector* RawVector => this.vector;
+
+ ///
+ /// Gets a view of the underlying ImVector{T}, for the range of .
+ ///
+ public Span DataSpan => new(this.DataUnsafe, this.LengthUnsafe);
+
+ ///
+ /// Gets a view of the underlying ImVector{T}, for the range of .
+ ///
+ public Span StorageSpan => new(this.DataUnsafe, this.CapacityUnsafe);
+
+ ///
+ /// Gets a value indicating whether this is disposed.
+ ///
+ public bool IsDisposed => this.vector is null;
+
+ ///
+ /// Gets a value indicating whether this has the ownership of the underlying
+ /// .
+ ///
+ public bool HasOwnership { get; private set; }
+
+ ///
+ /// Gets the underlying .
+ ///
+ public ImVector* Vector =>
+ this.vector is null ? throw new ObjectDisposedException(nameof(ImVectorWrapper)) : this.vector;
+
+ ///
+ /// Gets the number of items contained inside the underlying ImVector{T}.
+ ///
+ public int Length => this.LengthUnsafe;
+
+ ///
+ /// Gets the number of items that can be contained inside the underlying ImVector{T}.
+ ///
+ public int Capacity => this.CapacityUnsafe;
+
+ ///
+ /// Gets the pointer to the first item in the data inside underlying ImVector{T}.
+ ///
+ public T* Data => this.DataUnsafe;
+
+ ///
+ /// Gets the reference to the number of items contained inside the underlying ImVector{T}.
+ ///
+ public ref int LengthUnsafe => ref *&this.Vector->Size;
+
+ ///
+ /// Gets the reference to the number of items that can be contained inside the underlying ImVector{T}.
+ ///
+ public ref int CapacityUnsafe => ref *&this.Vector->Capacity;
+
+ ///
+ /// Gets the reference to the pointer to the first item in the data inside underlying ImVector{T}.
+ ///
+ /// This may be null, if is zero.
+ public ref T* DataUnsafe => ref *(T**)&this.Vector->Data;
+
+ ///
+ public bool IsReadOnly => false;
+
+ ///
+ int ICollection.Count => this.LengthUnsafe;
+
+ ///
+ bool ICollection.IsSynchronized => false;
+
+ ///
+ object ICollection.SyncRoot { get; } = new();
+
+ ///
+ int ICollection.Count => this.LengthUnsafe;
+
+ ///
+ int IReadOnlyCollection.Count => this.LengthUnsafe;
+
+ ///
+ bool IList.IsFixedSize => false;
+
+ ///
+ /// Gets the element at the specified index as a reference.
+ ///
+ /// Index of the item.
+ /// If is out of range.
+ public ref T this[int index] => ref this.DataUnsafe[this.EnsureIndex(index)];
+
+ ///
+ T IReadOnlyList.this[int index] => this[index];
+
+ ///
+ object? IList.this[int index]
+ {
+ get => this[index];
+ set => this[index] = value is null ? default : (T)value;
+ }
+
+ ///
+ T IList.this[int index]
+ {
+ get => this[index];
+ set => this[index] = value;
+ }
+
+ ///
+ public void Dispose()
+ {
+ if (this.HasOwnership)
+ {
+ this.Clear();
+ this.SetCapacity(0);
+ Debug.Assert(this.vector->Data == 0, "SetCapacity(0) did not free the data");
+ ImGuiNative.igMemFree(this.vector);
+ }
+
+ this.vector = null;
+ this.HasOwnership = false;
+ this.destroyer = null;
+ }
+
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ foreach (var i in Enumerable.Range(0, this.LengthUnsafe))
+ yield return this[i];
+ }
+
+ ///
+ public void Add(in T item)
+ {
+ this.EnsureCapacityExponential(this.LengthUnsafe + 1);
+ this.DataUnsafe[this.LengthUnsafe++] = item;
+ }
+
+ ///
+ public void AddRange(IEnumerable items)
+ {
+ if (items is ICollection { Count: var count })
+ this.EnsureCapacityExponential(this.LengthUnsafe + count);
+
+ foreach (var item in items)
+ this.Add(item);
+ }
+
+ ///
+ public void AddRange(Span items)
+ {
+ this.EnsureCapacityExponential(this.LengthUnsafe + items.Length);
+ foreach (var item in items)
+ this.Add(item);
+ }
+
+ ///
+ public void Clear() => this.Clear(false);
+
+ ///
+ /// Clears this vector, optionally skipping destroyer invocation.
+ ///
+ /// Whether to skip destroyer invocation.
+ public void Clear(bool skipDestroyer)
+ {
+ if (this.destroyer != null && !skipDestroyer)
+ {
+ foreach (var i in Enumerable.Range(0, this.LengthUnsafe))
+ this.destroyer(&this.DataUnsafe[i]);
+ }
+
+ this.LengthUnsafe = 0;
+ }
+
+ ///
+ public bool Contains(in T item) => this.IndexOf(in item) != -1;
+
+ ///
+ /// Size down the underlying ImVector{T}.
+ ///
+ /// Capacity to reserve.
+ /// Whether the capacity has been changed.
+ public bool Compact(int reservation) => this.SetCapacity(Math.Max(reservation, this.LengthUnsafe));
+
+ ///
+ public void CopyTo(T[] array, int arrayIndex)
+ {
+ if (arrayIndex < 0)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(arrayIndex),
+ arrayIndex,
+ $"{nameof(arrayIndex)} is less than 0.");
+ }
+
+ if (array.Length - arrayIndex < this.LengthUnsafe)
+ {
+ throw new ArgumentException(
+ "The number of elements in the source ImVectorWrapper is greater than the available space from arrayIndex to the end of the destination array.",
+ nameof(array));
+ }
+
+ fixed (void* p = array)
+ Buffer.MemoryCopy(this.DataUnsafe, p, this.LengthUnsafe * sizeof(T), this.LengthUnsafe * sizeof(T));
+ }
+
+ ///
+ /// Ensures that the capacity of this list is at least the specified .
+ /// On growth, the new capacity exactly matches .
+ ///
+ /// The minimum capacity to ensure.
+ /// Whether the capacity has been changed.
+ public bool EnsureCapacity(int capacity) => this.CapacityUnsafe < capacity && this.SetCapacity(capacity);
+
+ ///
+ /// Ensures that the capacity of this list is at least the specified .
+ /// On growth, the new capacity may exceed .
+ ///
+ /// The minimum capacity to ensure.
+ /// Whether the capacity has been changed.
+ public bool EnsureCapacityExponential(int capacity)
+ => this.EnsureCapacity(1 << ((sizeof(int) * 8) - BitOperations.LeadingZeroCount((uint)this.LengthUnsafe)));
+
+ ///
+ /// Resizes the underlying array and fills with zeroes if grown.
+ ///
+ /// New size.
+ /// New default value.
+ /// Whether to skip calling destroyer function.
+ public void Resize(int size, in T defaultValue = default, bool skipDestroyer = false)
+ {
+ this.EnsureCapacity(size);
+ var old = this.LengthUnsafe;
+ if (old > size && !skipDestroyer && this.destroyer is not null)
+ {
+ foreach (var v in this.DataSpan[size..])
+ this.destroyer(&v);
+ }
+
+ this.LengthUnsafe = size;
+ if (old < size)
+ this.DataSpan[old..].Fill(defaultValue);
+ }
+
+ ///
+ public bool Remove(in T item)
+ {
+ var index = this.IndexOf(item);
+ if (index == -1)
+ return false;
+
+ this.RemoveAt(index);
+ return true;
+ }
+
+ ///
+ public int IndexOf(in T item)
+ {
+ foreach (var i in Enumerable.Range(0, this.LengthUnsafe))
+ {
+ if (Equals(item, this.DataUnsafe[i]))
+ return i;
+ }
+
+ return -1;
+ }
+
+ ///
+ public void Insert(int index, in T item)
+ {
+ // Note: index == this.LengthUnsafe is okay; we're just adding to the end then
+ if (index < 0 || index > this.LengthUnsafe)
+ throw new IndexOutOfRangeException();
+
+ this.EnsureCapacityExponential(this.CapacityUnsafe + 1);
+ var num = this.LengthUnsafe - index;
+ Buffer.MemoryCopy(this.DataUnsafe + index, this.DataUnsafe + index + 1, num * sizeof(T), num * sizeof(T));
+ this.DataUnsafe[index] = item;
+ }
+
+ ///
+ public void InsertRange(int index, IEnumerable items)
+ {
+ if (items is ICollection { Count: var count })
+ {
+ this.EnsureCapacityExponential(this.LengthUnsafe + count);
+ var num = this.LengthUnsafe - index;
+ Buffer.MemoryCopy(this.DataUnsafe + index, this.DataUnsafe + index + count, num * sizeof(T), num * sizeof(T));
+ foreach (var item in items)
+ this.DataUnsafe[index++] = item;
+ }
+ else
+ {
+ foreach (var item in items)
+ this.Insert(index++, item);
+ }
+ }
+
+ ///
+ public void InsertRange(int index, Span items)
+ {
+ this.EnsureCapacityExponential(this.LengthUnsafe + items.Length);
+ var num = this.LengthUnsafe - index;
+ Buffer.MemoryCopy(this.DataUnsafe + index, this.DataUnsafe + index + items.Length, num * sizeof(T), num * sizeof(T));
+ foreach (var item in items)
+ this.DataUnsafe[index++] = item;
+ }
+
+ ///
+ /// Removes the element at the given index.
+ ///
+ /// The index.
+ /// Whether to skip calling the destroyer function.
+ public void RemoveAt(int index, bool skipDestroyer = false)
+ {
+ this.EnsureIndex(index);
+ var num = this.LengthUnsafe - index - 1;
+ if (!skipDestroyer)
+ this.destroyer?.Invoke(&this.DataUnsafe[index]);
+
+ Buffer.MemoryCopy(this.DataUnsafe + index + 1, this.DataUnsafe + index, num * sizeof(T), num * sizeof(T));
+ }
+
+ ///
+ void IList.RemoveAt(int index) => this.RemoveAt(index);
+
+ ///
+ void IList.RemoveAt(int index) => this.RemoveAt(index);
+
+ ///
+ /// Sets the capacity exactly as requested.
+ ///
+ /// New capacity.
+ /// Whether the capacity has been changed.
+ /// If is less than .
+ /// If memory for the requested capacity cannot be allocated.
+ public bool SetCapacity(int capacity)
+ {
+ if (capacity < this.LengthUnsafe)
+ throw new ArgumentOutOfRangeException(nameof(capacity), capacity, null);
+
+ if (capacity == this.LengthUnsafe)
+ {
+ if (capacity == 0 && this.DataUnsafe is not null)
+ {
+ ImGuiNative.igMemFree(this.DataUnsafe);
+ this.DataUnsafe = null;
+ }
+
+ return false;
+ }
+
+ var oldAlloc = this.DataUnsafe;
+ var oldSpan = new Span(oldAlloc, this.CapacityUnsafe);
+
+ var newAlloc = (T*)(capacity == 0
+ ? null
+ : ImGuiNative.igMemAlloc(checked((uint)(capacity * sizeof(T)))));
+
+ if (newAlloc is null && capacity > 0)
+ throw new OutOfMemoryException();
+
+ var newSpan = new Span(newAlloc, capacity);
+
+ if (!oldSpan.IsEmpty && !newSpan.IsEmpty)
+ oldSpan[..this.LengthUnsafe].CopyTo(newSpan);
+// #if DEBUG
+// new Span(newAlloc + this.LengthUnsafe, sizeof(T) * (capacity - this.LengthUnsafe)).Fill(0xCC);
+// #endif
+
+ if (oldAlloc != null)
+ ImGuiNative.igMemFree(oldAlloc);
+
+ this.DataUnsafe = newAlloc;
+ this.CapacityUnsafe = capacity;
+
+ return true;
+ }
+
+ ///
+ void ICollection.Add(T item) => this.Add(in item);
+
+ ///
+ bool ICollection.Contains(T item) => this.Contains(in item);
+
+ ///
+ void ICollection.CopyTo(Array array, int index)
+ {
+ if (index < 0)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(index),
+ index,
+ $"{nameof(index)} is less than 0.");
+ }
+
+ if (array.Length - index < this.LengthUnsafe)
+ {
+ throw new ArgumentException(
+ "The number of elements in the source ImVectorWrapper is greater than the available space from arrayIndex to the end of the destination array.",
+ nameof(array));
+ }
+
+ foreach (var i in Enumerable.Range(0, this.LengthUnsafe))
+ array.SetValue(this.DataUnsafe[i], index);
+ }
+
+ ///
+ bool ICollection.Remove(T item) => this.Remove(in item);
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
+
+ ///
+ int IList.Add(object? value)
+ {
+ this.Add(value is null ? default : (T)value);
+ return this.LengthUnsafe - 1;
+ }
+
+ ///
+ bool IList.Contains(object? value) => this.Contains(value is null ? default : (T)value);
+
+ ///
+ int IList.IndexOf(object? value) => this.IndexOf(value is null ? default : (T)value);
+
+ ///
+ void IList.Insert(int index, object? value) => this.Insert(index, value is null ? default : (T)value);
+
+ ///
+ void IList.Remove(object? value) => this.Remove(value is null ? default : (T)value);
+
+ ///
+ int IList.IndexOf(T item) => this.IndexOf(in item);
+
+ ///
+ void IList.Insert(int index, T item) => this.Insert(index, in item);
+
+ private int EnsureIndex(int i) => i >= 0 && i < this.LengthUnsafe ? i : throw new IndexOutOfRangeException();
+}
diff --git a/Dalamud/Logging/Internal/ModuleLog.cs b/Dalamud/Logging/Internal/ModuleLog.cs
index 2fb735640..5712f419b 100644
--- a/Dalamud/Logging/Internal/ModuleLog.cs
+++ b/Dalamud/Logging/Internal/ModuleLog.cs
@@ -1,6 +1,5 @@
-using System;
-
using Serilog;
+using Serilog.Core;
using Serilog.Events;
namespace Dalamud.Logging.Internal;
@@ -33,6 +32,7 @@ public class ModuleLog
///
/// The message template.
/// Values to log.
+ [MessageTemplateFormatMethod("messageTemplate")]
public void Verbose(string messageTemplate, params object[] values)
=> this.WriteLog(LogEventLevel.Verbose, messageTemplate, null, values);
@@ -42,6 +42,7 @@ public class ModuleLog
/// The exception that caused the error.
/// The message template.
/// Values to log.
+ [MessageTemplateFormatMethod("messageTemplate")]
public void Verbose(Exception exception, string messageTemplate, params object[] values)
=> this.WriteLog(LogEventLevel.Verbose, messageTemplate, exception, values);
@@ -50,6 +51,7 @@ public class ModuleLog
///
/// The message template.
/// Values to log.
+ [MessageTemplateFormatMethod("messageTemplate")]
public void Debug(string messageTemplate, params object[] values)
=> this.WriteLog(LogEventLevel.Debug, messageTemplate, null, values);
@@ -59,6 +61,7 @@ public class ModuleLog
/// The exception that caused the error.
/// The message template.
/// Values to log.
+ [MessageTemplateFormatMethod("messageTemplate")]
public void Debug(Exception exception, string messageTemplate, params object[] values)
=> this.WriteLog(LogEventLevel.Debug, messageTemplate, exception, values);
@@ -67,6 +70,7 @@ public class ModuleLog
///
/// The message template.
/// Values to log.
+ [MessageTemplateFormatMethod("messageTemplate")]
public void Information(string messageTemplate, params object[] values)
=> this.WriteLog(LogEventLevel.Information, messageTemplate, null, values);
@@ -76,6 +80,7 @@ public class ModuleLog
/// The exception that caused the error.
/// The message template.
/// Values to log.
+ [MessageTemplateFormatMethod("messageTemplate")]
public void Information(Exception exception, string messageTemplate, params object[] values)
=> this.WriteLog(LogEventLevel.Information, messageTemplate, exception, values);
@@ -84,6 +89,7 @@ public class ModuleLog
///
/// The message template.
/// Values to log.
+ [MessageTemplateFormatMethod("messageTemplate")]
public void Warning(string messageTemplate, params object[] values)
=> this.WriteLog(LogEventLevel.Warning, messageTemplate, null, values);
@@ -93,6 +99,7 @@ public class ModuleLog
/// The exception that caused the error.
/// The message template.
/// Values to log.
+ [MessageTemplateFormatMethod("messageTemplate")]
public void Warning(Exception exception, string messageTemplate, params object[] values)
=> this.WriteLog(LogEventLevel.Warning, messageTemplate, exception, values);
@@ -101,6 +108,7 @@ public class ModuleLog
///
/// The message template.
/// Values to log.
+ [MessageTemplateFormatMethod("messageTemplate")]
public void Error(string messageTemplate, params object[] values)
=> this.WriteLog(LogEventLevel.Error, messageTemplate, null, values);
@@ -110,6 +118,7 @@ public class ModuleLog
/// The exception that caused the error.
/// The message template.
/// Values to log.
+ [MessageTemplateFormatMethod("messageTemplate")]
public void Error(Exception? exception, string messageTemplate, params object[] values)
=> this.WriteLog(LogEventLevel.Error, messageTemplate, exception, values);
@@ -118,6 +127,7 @@ public class ModuleLog
///
/// The message template.
/// Values to log.
+ [MessageTemplateFormatMethod("messageTemplate")]
public void Fatal(string messageTemplate, params object[] values)
=> this.WriteLog(LogEventLevel.Fatal, messageTemplate, null, values);
@@ -127,9 +137,11 @@ public class ModuleLog
/// The exception that caused the error.
/// The message template.
/// Values to log.
+ [MessageTemplateFormatMethod("messageTemplate")]
public void Fatal(Exception exception, string messageTemplate, params object[] values)
=> this.WriteLog(LogEventLevel.Fatal, messageTemplate, exception, values);
+ [MessageTemplateFormatMethod("messageTemplate")]
private void WriteLog(
LogEventLevel level, string messageTemplate, Exception? exception = null, params object[] values)
{
diff --git a/Dalamud/NativeFunctions.cs b/Dalamud/NativeFunctions.cs
index b77f71d08..92dfe5dd7 100644
--- a/Dalamud/NativeFunctions.cs
+++ b/Dalamud/NativeFunctions.cs
@@ -137,6 +137,7 @@ internal static partial class NativeFunctions
///
/// MB_* from winuser.
///
+ [Flags]
public enum MessageBoxType : uint
{
///