Expose wrapped default font handle

This commit is contained in:
Soreepeong 2024-01-21 03:41:26 +09:00
parent d70b430e0d
commit 967ae97308
4 changed files with 131 additions and 21 deletions

View file

@ -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<ResizeBuffersDelegate>? 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.<br />
/// <strong>Accessing this static property outside of the main thread is dangerous and not supported.</strong>
/// </summary>
public static ImFontPtr DefaultFont => WhenFontsReady().defaultFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault);
public static ImFontPtr DefaultFont => WhenFontsReady().DefaultFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault);
/// <summary>
/// Gets an included FontAwesome icon font.<br />
/// <strong>Accessing this static property outside of the main thread is dangerous and not supported.</strong>
/// </summary>
public static ImFontPtr IconFont => WhenFontsReady().iconFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault);
public static ImFontPtr IconFont => WhenFontsReady().IconFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault);
/// <summary>
/// Gets an included monospaced font.<br />
/// <strong>Accessing this static property outside of the main thread is dangerous and not supported.</strong>
/// </summary>
public static ImFontPtr MonoFont => WhenFontsReady().monoFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault);
public static ImFontPtr MonoFont => WhenFontsReady().MonoFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault);
/// <summary>
/// Gets the default font handle.
/// </summary>
public IFontHandle.IInternal? DefaultFontHandle { get; private set; }
/// <summary>
/// Gets the icon font handle.
/// </summary>
public IFontHandle.IInternal? IconFontHandle { get; private set; }
/// <summary>
/// Gets the mono font handle.
/// </summary>
public IFontHandle.IInternal? MonoFontHandle { get; private set; }
/// <summary>
/// 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

View file

@ -122,6 +122,10 @@ public interface IFontAtlas : IDisposable
/// Note that <see cref="BuildTask"/> would not necessarily get changed from calling this function.
/// </summary>
/// <exception cref="InvalidOperationException">If <see cref="AutoRebuildMode"/> is <see cref="FontAtlasAutoRebuildMode.Async"/>.</exception>
/// <remarks>
/// Using this method will block the main thread on rebuilding fonts, effectively calling
/// <see cref="BuildFontsImmediately"/> from the main thread. Consider migrating to <see cref="BuildFontsAsync"/>.
/// </remarks>
void BuildFontsOnNextFrame();
/// <summary>

View file

@ -12,7 +12,12 @@ namespace Dalamud.Interface.ManagedFontAtlas;
public interface IFontHandle : IDisposable
{
/// <summary>
/// Called when the built instance of <see cref="ImFontPtr"/> has been changed.
/// Called when the built instance of <see cref="ImFontPtr"/> has been changed.<br />
/// This event will be invoked on the same thread with
/// <see cref="IFontAtlas"/>.<see cref="IFontAtlas.BuildStepChange"/>,
/// when the build step is <see cref="FontAtlasBuildStep.PostPromotion"/>.<br />
/// See <see cref="IFontAtlas.BuildFontsOnNextFrame"/>, <see cref="IFontAtlas.BuildFontsImmediately"/>, and
/// <see cref="IFontAtlas.BuildFontsAsync"/>.
/// </summary>
event Action<IFontHandle> ImFontChanged;

View file

@ -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;
/// <summary>
/// Initializes a new instance of the <see cref="UiBuilder"/> 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.
/// </summary>
[Obsolete($"Use {nameof(this.FontAtlas)} instead.", false)]
/// <remarks>
/// To add your custom font, use <see cref="FontAtlas"/>.<see cref="IFontAtlas.NewDelegateFontHandle"/> or
/// <see cref="IFontAtlas.NewGameFontHandle"/>.<br />
/// To be notified on font changes after fonts are built, use
/// <see cref="DefaultFontHandle"/>.<see cref="IFontHandle.ImFontChanged"/>.<br />
/// For all other purposes, use <see cref="FontAtlas"/>.<see cref="IFontAtlas.BuildStepChange"/>.
/// </remarks>
[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.
/// </summary>
/// <remarks>
/// To add your custom font, use <see cref="FontAtlas"/>.<see cref="IFontAtlas.NewDelegateFontHandle"/> or
/// <see cref="IFontAtlas.NewGameFontHandle"/>.<br />
/// To be notified on font changes after fonts are built, use
/// <see cref="DefaultFontHandle"/>.<see cref="IFontHandle.ImFontChanged"/>.<br />
/// For all other purposes, use <see cref="FontAtlas"/>.<see cref="IFontAtlas.BuildStepChange"/>.
/// </remarks>
[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.<br />
/// <strong>Accessing this static property outside of <see cref="Draw"/> is dangerous and not supported.</strong>
/// </summary>
public static ImFontPtr DefaultFont => InterfaceManager.DefaultFont;
/// <summary>
/// Gets the default Dalamud icon font based on FontAwesome 5 Free solid.<br />
/// <strong>Accessing this static property outside of <see cref="Draw"/> is dangerous and not supported.</strong>
/// </summary>
public static ImFontPtr IconFont => InterfaceManager.IconFont;
/// <summary>
/// Gets the default Dalamud monospaced font based on Inconsolata Regular.<br />
/// <strong>Accessing this static property outside of <see cref="Draw"/> is dangerous and not supported.</strong>
/// </summary>
public static ImFontPtr MonoFont => InterfaceManager.MonoFont;
/// <summary>
/// Gets the handle to the default Dalamud font - supporting all game languages and icons.
/// </summary>
/// <remarks>
/// A font handle corresponding to this font can be obtained with:
/// <code>
@ -151,11 +186,15 @@ public sealed class UiBuilder : IDisposable
/// tk => tk.AddDalamudDefaultFont(UiBuilder.DefaultFontSizePt)));
/// </code>
/// </remarks>
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.")));
/// <summary>
/// Gets the default Dalamud icon font based on FontAwesome 5 Free solid.<br />
/// <strong>Accessing this static property outside of <see cref="Draw"/> is dangerous and not supported.</strong>
/// Gets the default Dalamud icon font based on FontAwesome 5 Free solid.
/// </summary>
/// <remarks>
/// 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 })));
/// </code>
/// </remarks>
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.")));
/// <summary>
/// Gets the default Dalamud monospaced font based on Inconsolata Regular.<br />
/// <strong>Accessing this static property outside of <see cref="Draw"/> is dangerous and not supported.</strong>
/// Gets the default Dalamud monospaced font based on Inconsolata Regular.
/// </summary>
/// <remarks>
/// A font handle corresponding to this font can be obtained with:
@ -181,7 +224,12 @@ public sealed class UiBuilder : IDisposable
/// new() { SizePt = UiBuilder.DefaultFontSizePt })));
/// </code>
/// </remarks>
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.")));
/// <summary>
/// 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<IFontHandle>? 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<IFontHandle> 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);
}
}