diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index 8915b3e3d..62f9145bf 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -13,7 +13,6 @@ using Dalamud.Game.ClientState.Keys; using Dalamud.Game.Internal.DXGI; using Dalamud.Hooking; using Dalamud.Hooking.WndProcHook; -using Dalamud.Interface.GameFonts; using Dalamud.Interface.Internal.ManagedAsserts; using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ManagedFontAtlas; @@ -87,9 +86,6 @@ internal class InterfaceManager : IDisposable, IServiceType private Hook? resizeBuffersHook; private IFontAtlas? dalamudAtlas; - private IFontHandle.IInternal? defaultFontHandle; - private IFontHandle.IInternal? iconFontHandle; - private IFontHandle.IInternal? monoFontHandle; // can't access imgui IO before first present call private bool lastWantCapture = false; @@ -131,19 +127,34 @@ internal class InterfaceManager : IDisposable, IServiceType /// Gets the default ImGui font.
/// Accessing this static property outside of the main thread is dangerous and not supported. /// - public static ImFontPtr DefaultFont => WhenFontsReady().defaultFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault); + public static ImFontPtr DefaultFont => WhenFontsReady().DefaultFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault); /// /// Gets an included FontAwesome icon font.
/// Accessing this static property outside of the main thread is dangerous and not supported. ///
- public static ImFontPtr IconFont => WhenFontsReady().iconFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault); + public static ImFontPtr IconFont => WhenFontsReady().IconFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault); /// /// Gets an included monospaced font.
/// Accessing this static property outside of the main thread is dangerous and not supported. ///
- public static ImFontPtr MonoFont => WhenFontsReady().monoFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault); + public static ImFontPtr MonoFont => WhenFontsReady().MonoFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault); + + /// + /// Gets the default font handle. + /// + public IFontHandle.IInternal? DefaultFontHandle { get; private set; } + + /// + /// Gets the icon font handle. + /// + public IFontHandle.IInternal? IconFontHandle { get; private set; } + + /// + /// Gets the mono font handle. + /// + public IFontHandle.IInternal? MonoFontHandle { get; private set; } /// /// Gets or sets the pointer to ImGui.IO(), when it was last used. @@ -691,9 +702,9 @@ internal class InterfaceManager : IDisposable, IServiceType .CreateFontAtlas(nameof(InterfaceManager), FontAtlasAutoRebuildMode.Disable); using (this.dalamudAtlas.SuppressAutoRebuild()) { - this.defaultFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle( + this.DefaultFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle( e => e.OnPreBuild(tk => tk.AddDalamudDefaultFont(DefaultFontSizePx))); - this.iconFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle( + this.IconFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle( e => e.OnPreBuild( tk => tk.AddFontAwesomeIconFont( new() @@ -702,7 +713,7 @@ internal class InterfaceManager : IDisposable, IServiceType GlyphMinAdvanceX = DefaultFontSizePx, GlyphMaxAdvanceX = DefaultFontSizePx, }))); - this.monoFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle( + this.MonoFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle( e => e.OnPreBuild( tk => tk.AddDalamudAssetFont( DalamudAsset.InconsolataRegular, @@ -715,12 +726,12 @@ internal class InterfaceManager : IDisposable, IServiceType // Use font handles directly. // Fill missing glyphs in MonoFont from DefaultFont - tk.CopyGlyphsAcrossFonts(this.defaultFontHandle.ImFont, this.monoFontHandle.ImFont, true); + tk.CopyGlyphsAcrossFonts(this.DefaultFontHandle.ImFont, this.MonoFontHandle.ImFont, true); // Update default font unsafe { - ImGui.GetIO().NativePtr->FontDefault = this.defaultFontHandle.ImFont; + ImGui.GetIO().NativePtr->FontDefault = this.DefaultFontHandle.ImFont; } // Broadcast to auto-rebuilding instances diff --git a/Dalamud/Interface/ManagedFontAtlas/IFontAtlas.cs b/Dalamud/Interface/ManagedFontAtlas/IFontAtlas.cs index ec3e66e9a..491292f9d 100644 --- a/Dalamud/Interface/ManagedFontAtlas/IFontAtlas.cs +++ b/Dalamud/Interface/ManagedFontAtlas/IFontAtlas.cs @@ -122,6 +122,10 @@ public interface IFontAtlas : IDisposable /// Note that would not necessarily get changed from calling this function. /// /// If is . + /// + /// Using this method will block the main thread on rebuilding fonts, effectively calling + /// from the main thread. Consider migrating to . + /// void BuildFontsOnNextFrame(); /// diff --git a/Dalamud/Interface/ManagedFontAtlas/IFontHandle.cs b/Dalamud/Interface/ManagedFontAtlas/IFontHandle.cs index 81ce84a63..eb57b815f 100644 --- a/Dalamud/Interface/ManagedFontAtlas/IFontHandle.cs +++ b/Dalamud/Interface/ManagedFontAtlas/IFontHandle.cs @@ -12,7 +12,12 @@ namespace Dalamud.Interface.ManagedFontAtlas; public interface IFontHandle : IDisposable { /// - /// Called when the built instance of has been changed. + /// Called when the built instance of has been changed.
+ /// This event will be invoked on the same thread with + /// ., + /// when the build step is .
+ /// See , , and + /// . ///
event Action ImFontChanged; diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs index 87e3b9032..43912f224 100644 --- a/Dalamud/Interface/UiBuilder.cs +++ b/Dalamud/Interface/UiBuilder.cs @@ -41,6 +41,10 @@ public sealed class UiBuilder : IDisposable private bool hasErrorWindow = false; private bool lastFrameUiHideState = false; + private IFontHandle? defaultFontHandle; + private IFontHandle? iconFontHandle; + private IFontHandle? monoFontHandle; + /// /// Initializes a new instance of the class and registers it. /// You do not have to call this manually. @@ -103,7 +107,14 @@ public sealed class UiBuilder : IDisposable /// (at any time), so you should both reload your custom fonts and restore those /// pointers inside this handler. /// - [Obsolete($"Use {nameof(this.FontAtlas)} instead.", false)] + /// + /// To add your custom font, use . or + /// .
+ /// To be notified on font changes after fonts are built, use + /// ..
+ /// For all other purposes, use .. + ///
+ [Obsolete("See remarks.", false)] [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] public event Action? BuildFonts; @@ -113,6 +124,13 @@ public sealed class UiBuilder : IDisposable /// (at any time), so you should both reload your custom fonts and restore those /// pointers inside this handler. ///
+ /// + /// To add your custom font, use . or + /// .
+ /// To be notified on font changes after fonts are built, use + /// ..
+ /// For all other purposes, use .. + ///
[Obsolete($"Use {nameof(this.FontAtlas)} instead.", false)] [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] public event Action? AfterBuildFonts; @@ -143,6 +161,23 @@ public sealed class UiBuilder : IDisposable /// Gets the default Dalamud font - supporting all game languages and icons.
/// Accessing this static property outside of is dangerous and not supported. /// + public static ImFontPtr DefaultFont => InterfaceManager.DefaultFont; + + /// + /// Gets the default Dalamud icon font based on FontAwesome 5 Free solid.
+ /// Accessing this static property outside of is dangerous and not supported. + ///
+ public static ImFontPtr IconFont => InterfaceManager.IconFont; + + /// + /// Gets the default Dalamud monospaced font based on Inconsolata Regular.
+ /// Accessing this static property outside of is dangerous and not supported. + ///
+ public static ImFontPtr MonoFont => InterfaceManager.MonoFont; + + /// + /// Gets the handle to the default Dalamud font - supporting all game languages and icons. + /// /// /// A font handle corresponding to this font can be obtained with: /// @@ -151,11 +186,15 @@ public sealed class UiBuilder : IDisposable /// tk => tk.AddDalamudDefaultFont(UiBuilder.DefaultFontSizePt))); /// /// - public static ImFontPtr DefaultFont => InterfaceManager.DefaultFont; + public IFontHandle DefaultFontHandle => + this.defaultFontHandle ??= + this.scopedFinalizer.Add( + new FontHandleWrapper( + this.InterfaceManagerWithScene?.DefaultFontHandle + ?? throw new InvalidOperationException("Scene is not yet ready."))); /// - /// Gets the default Dalamud icon font based on FontAwesome 5 Free solid.
- /// Accessing this static property outside of is dangerous and not supported. + /// Gets the default Dalamud icon font based on FontAwesome 5 Free solid. ///
/// /// A font handle corresponding to this font can be obtained with: @@ -165,11 +204,15 @@ public sealed class UiBuilder : IDisposable /// tk => tk.AddFontAwesomeIconFont(new() { SizePt = UiBuilder.DefaultFontSizePt }))); /// /// - public static ImFontPtr IconFont => InterfaceManager.IconFont; + public IFontHandle IconFontHandle => + this.iconFontHandle ??= + this.scopedFinalizer.Add( + new FontHandleWrapper( + this.InterfaceManagerWithScene?.IconFontHandle + ?? throw new InvalidOperationException("Scene is not yet ready."))); /// - /// Gets the default Dalamud monospaced font based on Inconsolata Regular.
- /// Accessing this static property outside of is dangerous and not supported. + /// Gets the default Dalamud monospaced font based on Inconsolata Regular. ///
/// /// A font handle corresponding to this font can be obtained with: @@ -181,7 +224,12 @@ public sealed class UiBuilder : IDisposable /// new() { SizePt = UiBuilder.DefaultFontSizePt }))); /// /// - public static ImFontPtr MonoFont => InterfaceManager.MonoFont; + public IFontHandle MonoFontHandle => + this.monoFontHandle ??= + this.scopedFinalizer.Add( + new FontHandleWrapper( + this.InterfaceManagerWithScene?.MonoFontHandle + ?? throw new InvalidOperationException("Scene is not yet ready."))); /// /// Gets the game's active Direct3D device. @@ -660,4 +708,46 @@ public sealed class UiBuilder : IDisposable { this.ResizeBuffers?.InvokeSafely(); } + + private class FontHandleWrapper : IFontHandle + { + private IFontHandle? wrapped; + + public FontHandleWrapper(IFontHandle wrapped) + { + this.wrapped = wrapped; + this.wrapped.ImFontChanged += this.WrappedOnImFontChanged; + } + + public event Action? ImFontChanged; + + public Exception? LoadException => + this.wrapped!.LoadException ?? new ObjectDisposedException(nameof(FontHandleWrapper)); + + public bool Available => this.wrapped?.Available ?? false; + + public void Dispose() + { + if (this.wrapped is not { } w) + return; + + this.wrapped = null; + w.ImFontChanged -= this.WrappedOnImFontChanged; + // Note: do not dispose w; we do not own it + } + + public IFontHandle.ImFontLocked Lock() => + this.wrapped?.Lock() ?? throw new ObjectDisposedException(nameof(FontHandleWrapper)); + + public IFontHandle.FontPopper Push() => + this.wrapped?.Push() ?? throw new ObjectDisposedException(nameof(FontHandleWrapper)); + + public Task WaitAsync() => + this.wrapped?.WaitAsync().ContinueWith(_ => (IFontHandle)this) ?? + throw new ObjectDisposedException(nameof(FontHandleWrapper)); + + public override string ToString() => $"{nameof(FontHandleWrapper)}({this.wrapped})"; + + private void WrappedOnImFontChanged(IFontHandle obj) => this.ImFontChanged.InvokeSafely(this); + } }