mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 10:17:22 +01:00
Merge pull request #1721 from goatcorp/net8-rollup
[net8] Rollup changes from master
This commit is contained in:
commit
772117ef24
98 changed files with 3002 additions and 778 deletions
|
|
@ -97,8 +97,6 @@ namespace Dalamud.CorePlugin
|
|||
this.Interface.UiBuilder.Draw -= this.OnDraw;
|
||||
|
||||
this.windowSystem.RemoveAllWindows();
|
||||
|
||||
this.Interface.ExplicitDispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ namespace Dalamud.Configuration.Internal;
|
|||
#pragma warning disable SA1015
|
||||
[InherentDependency<ReliableFileStorage>] // We must still have this when unloading
|
||||
#pragma warning restore SA1015
|
||||
internal sealed class DalamudConfiguration : IServiceType, IDisposable
|
||||
internal sealed class DalamudConfiguration : IInternalDisposableService
|
||||
{
|
||||
private static readonly JsonSerializerSettings SerializerSettings = new()
|
||||
{
|
||||
|
|
@ -502,7 +502,7 @@ internal sealed class DalamudConfiguration : IServiceType, IDisposable
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
// Make sure that we save, if a save is queued while we are shutting down
|
||||
this.Update();
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ using System.Threading.Tasks;
|
|||
using Dalamud.Common;
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Plugin.Internal;
|
||||
using Dalamud.Storage;
|
||||
using Dalamud.Utility;
|
||||
|
|
@ -187,27 +186,6 @@ internal sealed class Dalamud : IServiceType
|
|||
this.unloadSignal.WaitOne();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose subsystems related to plugin handling.
|
||||
/// </summary>
|
||||
public void DisposePlugins()
|
||||
{
|
||||
// this must be done before unloading interface manager, in order to do rebuild
|
||||
// the correct cascaded WndProc (IME -> RawDX11Scene -> Game). Otherwise the game
|
||||
// will not receive any windows messages
|
||||
Service<DalamudIme>.GetNullable()?.Dispose();
|
||||
|
||||
// this must be done before unloading plugins, or it can cause a race condition
|
||||
// due to rendering happening on another thread, where a plugin might receive
|
||||
// a render call after it has been disposed, which can crash if it attempts to
|
||||
// use any resources that it freed in its own Dispose method
|
||||
Service<InterfaceManager>.GetNullable()?.Dispose();
|
||||
|
||||
Service<DalamudInterface>.GetNullable()?.Dispose();
|
||||
|
||||
Service<PluginManager>.GetNullable()?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace the current exception handler with the default one.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ namespace Dalamud.Data;
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IDataManager>]
|
||||
#pragma warning restore SA1015
|
||||
internal sealed class DataManager : IDisposable, IServiceType, IDataManager
|
||||
internal sealed class DataManager : IInternalDisposableService, IDataManager
|
||||
{
|
||||
private readonly Thread luminaResourceThread;
|
||||
private readonly CancellationTokenSource luminaCancellationTokenSource;
|
||||
|
|
@ -158,7 +158,7 @@ internal sealed class DataManager : IDisposable, IServiceType, IDataManager
|
|||
#endregion
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IDisposable.Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.luminaCancellationTokenSource.Cancel();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,7 +138,9 @@ public sealed class EntryPoint
|
|||
SerilogEventSink.Instance.LogLine += SerilogOnLogLine;
|
||||
|
||||
// Load configuration first to get some early persistent state, like log level
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var fs = new ReliableFileStorage(Path.GetDirectoryName(info.ConfigurationPath)!);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
var configuration = DalamudConfiguration.Load(info.ConfigurationPath!, fs);
|
||||
|
||||
// Set the appropriate logging level from the configuration
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ namespace Dalamud.Game.Addon.Events;
|
|||
/// </summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal unsafe class AddonEventManager : IDisposable, IServiceType
|
||||
internal unsafe class AddonEventManager : IInternalDisposableService
|
||||
{
|
||||
/// <summary>
|
||||
/// PluginName for Dalamud Internal use.
|
||||
|
|
@ -62,7 +62,7 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType
|
|||
private delegate nint UpdateCursorDelegate(RaptureAtkModule* module);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.onUpdateCursor.Dispose();
|
||||
|
||||
|
|
@ -204,7 +204,7 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IAddonEventManager>]
|
||||
#pragma warning restore SA1015
|
||||
internal class AddonEventManagerPluginScoped : IDisposable, IServiceType, IAddonEventManager
|
||||
internal class AddonEventManagerPluginScoped : IInternalDisposableService, IAddonEventManager
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly AddonEventManager eventManagerService = Service<AddonEventManager>.Get();
|
||||
|
|
@ -225,7 +225,7 @@ internal class AddonEventManagerPluginScoped : IDisposable, IServiceType, IAddon
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
// if multiple plugins force cursors and dispose without un-forcing them then all forces will be cleared.
|
||||
if (this.isForcingCursor)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ namespace Dalamud.Game.Addon.Lifecycle;
|
|||
/// </summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal unsafe class AddonLifecycle : IDisposable, IServiceType
|
||||
internal unsafe class AddonLifecycle : IInternalDisposableService
|
||||
{
|
||||
private static readonly ModuleLog Log = new("AddonLifecycle");
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
|
|||
internal List<AddonLifecycleEventListener> EventListeners { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.onAddonSetupHook.Dispose();
|
||||
this.onAddonSetup2Hook.Dispose();
|
||||
|
|
@ -383,7 +383,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IAddonLifecycle>]
|
||||
#pragma warning restore SA1015
|
||||
internal class AddonLifecyclePluginScoped : IDisposable, IServiceType, IAddonLifecycle
|
||||
internal class AddonLifecyclePluginScoped : IInternalDisposableService, IAddonLifecycle
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly AddonLifecycle addonLifecycleService = Service<AddonLifecycle>.Get();
|
||||
|
|
@ -391,7 +391,7 @@ internal class AddonLifecyclePluginScoped : IDisposable, IServiceType, IAddonLif
|
|||
private readonly List<AddonLifecycleEventListener> eventListeners = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
foreach (var listener in this.eventListeners)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using Dalamud.Game.Gui;
|
|||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Interface.Internal.Windows;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ namespace Dalamud.Game.ClientState;
|
|||
/// </summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
internal sealed class ClientState : IDisposable, IServiceType, IClientState
|
||||
internal sealed class ClientState : IInternalDisposableService, IClientState
|
||||
{
|
||||
private static readonly ModuleLog Log = new("ClientState");
|
||||
|
||||
|
|
@ -115,7 +115,7 @@ internal sealed class ClientState : IDisposable, IServiceType, IClientState
|
|||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
void IDisposable.Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.setupTerritoryTypeHook.Dispose();
|
||||
this.framework.Update -= this.FrameworkOnOnUpdateEvent;
|
||||
|
|
@ -196,7 +196,7 @@ internal sealed class ClientState : IDisposable, IServiceType, IClientState
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IClientState>]
|
||||
#pragma warning restore SA1015
|
||||
internal class ClientStatePluginScoped : IDisposable, IServiceType, IClientState
|
||||
internal class ClientStatePluginScoped : IInternalDisposableService, IClientState
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly ClientState clientStateService = Service<ClientState>.Get();
|
||||
|
|
@ -257,7 +257,7 @@ internal class ClientStatePluginScoped : IDisposable, IServiceType, IClientState
|
|||
public bool IsGPosing => this.clientStateService.IsGPosing;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.clientStateService.TerritoryChanged -= this.TerritoryChangedForward;
|
||||
this.clientStateService.Login -= this.LoginForward;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ namespace Dalamud.Game.ClientState.Conditions;
|
|||
/// </summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
internal sealed partial class Condition : IServiceType, ICondition
|
||||
internal sealed partial class Condition : IInternalDisposableService, ICondition
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has.
|
||||
|
|
@ -22,6 +22,8 @@ internal sealed partial class Condition : IServiceType, ICondition
|
|||
|
||||
private readonly bool[] cache = new bool[MaxConditionEntries];
|
||||
|
||||
private bool isDisposed;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private Condition(ClientState clientState)
|
||||
{
|
||||
|
|
@ -35,6 +37,9 @@ internal sealed partial class Condition : IServiceType, ICondition
|
|||
this.framework.Update += this.FrameworkUpdate;
|
||||
}
|
||||
|
||||
/// <summary>Finalizes an instance of the <see cref="Condition" /> class.</summary>
|
||||
~Condition() => this.Dispose(false);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event ICondition.ConditionChangeDelegate? ConditionChange;
|
||||
|
||||
|
|
@ -60,6 +65,9 @@ internal sealed partial class Condition : IServiceType, ICondition
|
|||
public bool this[ConditionFlag flag]
|
||||
=> this[(int)flag];
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService() => this.Dispose(true);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Any()
|
||||
{
|
||||
|
|
@ -89,6 +97,19 @@ internal sealed partial class Condition : IServiceType, ICondition
|
|||
return false;
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (this.isDisposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
this.framework.Update -= this.FrameworkUpdate;
|
||||
}
|
||||
|
||||
this.isDisposed = true;
|
||||
}
|
||||
|
||||
private void FrameworkUpdate(IFramework unused)
|
||||
{
|
||||
for (var i = 0; i < MaxConditionEntries; i++)
|
||||
|
|
@ -112,44 +133,6 @@ internal sealed partial class Condition : IServiceType, ICondition
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc.
|
||||
/// </summary>
|
||||
internal sealed partial class Condition : IDisposable
|
||||
{
|
||||
private bool isDisposed;
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="Condition" /> class.
|
||||
/// </summary>
|
||||
~Condition()
|
||||
{
|
||||
this.Dispose(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes this instance, alongside its hooks.
|
||||
/// </summary>
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
this.Dispose(true);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (this.isDisposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
this.framework.Update -= this.FrameworkUpdate;
|
||||
}
|
||||
|
||||
this.isDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin-scoped version of a Condition service.
|
||||
/// </summary>
|
||||
|
|
@ -159,7 +142,7 @@ internal sealed partial class Condition : IDisposable
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<ICondition>]
|
||||
#pragma warning restore SA1015
|
||||
internal class ConditionPluginScoped : IDisposable, IServiceType, ICondition
|
||||
internal class ConditionPluginScoped : IInternalDisposableService, ICondition
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly Condition conditionService = Service<Condition>.Get();
|
||||
|
|
@ -185,7 +168,7 @@ internal class ConditionPluginScoped : IDisposable, IServiceType, ICondition
|
|||
public bool this[int flag] => this.conditionService[flag];
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.conditionService.ConditionChange -= this.ConditionChangedForward;
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ namespace Dalamud.Game.ClientState.GamePad;
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IGamepadState>]
|
||||
#pragma warning restore SA1015
|
||||
internal unsafe class GamepadState : IDisposable, IServiceType, IGamepadState
|
||||
internal unsafe class GamepadState : IInternalDisposableService, IGamepadState
|
||||
{
|
||||
private readonly Hook<ControllerPoll>? gamepadPoll;
|
||||
|
||||
|
|
@ -109,7 +109,7 @@ internal unsafe class GamepadState : IDisposable, IServiceType, IGamepadState
|
|||
/// <summary>
|
||||
/// Disposes this instance, alongside its hooks.
|
||||
/// </summary>
|
||||
void IDisposable.Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ namespace Dalamud.Game.Command;
|
|||
/// </summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
internal sealed class CommandManager : IServiceType, IDisposable, ICommandManager
|
||||
internal sealed class CommandManager : IInternalDisposableService, ICommandManager
|
||||
{
|
||||
private static readonly ModuleLog Log = new("Command");
|
||||
|
||||
|
|
@ -130,7 +130,7 @@ internal sealed class CommandManager : IServiceType, IDisposable, ICommandManage
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IDisposable.Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.chatGui.CheckMessageHandled -= this.OnCheckMessageHandled;
|
||||
}
|
||||
|
|
@ -170,7 +170,7 @@ internal sealed class CommandManager : IServiceType, IDisposable, ICommandManage
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<ICommandManager>]
|
||||
#pragma warning restore SA1015
|
||||
internal class CommandManagerPluginScoped : IDisposable, IServiceType, ICommandManager
|
||||
internal class CommandManagerPluginScoped : IInternalDisposableService, ICommandManager
|
||||
{
|
||||
private static readonly ModuleLog Log = new("Command");
|
||||
|
||||
|
|
@ -193,7 +193,7 @@ internal class CommandManagerPluginScoped : IDisposable, IServiceType, ICommandM
|
|||
public ReadOnlyDictionary<string, CommandInfo> Commands => this.commandManagerService.Commands;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
foreach (var command in this.pluginRegisteredCommands)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Dalamud.Game.Config;
|
|||
/// </summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
internal sealed class GameConfig : IServiceType, IGameConfig, IDisposable
|
||||
internal sealed class GameConfig : IInternalDisposableService, IGameConfig
|
||||
{
|
||||
private readonly TaskCompletionSource tcsInitialization = new();
|
||||
private readonly TaskCompletionSource<GameConfigSection> tcsSystem = new();
|
||||
|
|
@ -195,7 +195,7 @@ internal sealed class GameConfig : IServiceType, IGameConfig, IDisposable
|
|||
public void Set(UiControlOption option, string value) => this.UiControl.Set(option.GetName(), value);
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IDisposable.Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
var ode = new ObjectDisposedException(nameof(GameConfig));
|
||||
this.tcsInitialization.SetExceptionIfIncomplete(ode);
|
||||
|
|
@ -248,7 +248,7 @@ internal sealed class GameConfig : IServiceType, IGameConfig, IDisposable
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IGameConfig>]
|
||||
#pragma warning restore SA1015
|
||||
internal class GameConfigPluginScoped : IDisposable, IServiceType, IGameConfig
|
||||
internal class GameConfigPluginScoped : IInternalDisposableService, IGameConfig
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly GameConfig gameConfigService = Service<GameConfig>.Get();
|
||||
|
|
@ -295,7 +295,7 @@ internal class GameConfigPluginScoped : IDisposable, IServiceType, IGameConfig
|
|||
public GameConfigSection UiControl => this.gameConfigService.UiControl;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.gameConfigService.Changed -= this.ConfigChangedForward;
|
||||
this.initializationTask.ContinueWith(
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ namespace Dalamud.Game.DutyState;
|
|||
/// </summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
internal unsafe class DutyState : IDisposable, IServiceType, IDutyState
|
||||
internal unsafe class DutyState : IInternalDisposableService, IDutyState
|
||||
{
|
||||
private readonly DutyStateAddressResolver address;
|
||||
private readonly Hook<SetupContentDirectNetworkMessageDelegate> contentDirectorNetworkMessageHook;
|
||||
|
|
@ -62,7 +62,7 @@ internal unsafe class DutyState : IDisposable, IServiceType, IDutyState
|
|||
private bool CompletedThisTerritory { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.contentDirectorNetworkMessageHook.Dispose();
|
||||
this.framework.Update -= this.FrameworkOnUpdateEvent;
|
||||
|
|
@ -168,7 +168,7 @@ internal unsafe class DutyState : IDisposable, IServiceType, IDutyState
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IDutyState>]
|
||||
#pragma warning restore SA1015
|
||||
internal class DutyStatePluginScoped : IDisposable, IServiceType, IDutyState
|
||||
internal class DutyStatePluginScoped : IInternalDisposableService, IDutyState
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DutyState dutyStateService = Service<DutyState>.Get();
|
||||
|
|
@ -200,7 +200,7 @@ internal class DutyStatePluginScoped : IDisposable, IServiceType, IDutyState
|
|||
public bool IsDutyStarted => this.dutyStateService.IsDutyStarted;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.dutyStateService.DutyStarted -= this.DutyStartedForward;
|
||||
this.dutyStateService.DutyWiped -= this.DutyWipedForward;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ namespace Dalamud.Game;
|
|||
/// </summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
internal sealed class Framework : IDisposable, IServiceType, IFramework
|
||||
internal sealed class Framework : IInternalDisposableService, IFramework
|
||||
{
|
||||
private static readonly ModuleLog Log = new("Framework");
|
||||
|
||||
|
|
@ -279,7 +279,7 @@ internal sealed class Framework : IDisposable, IServiceType, IFramework
|
|||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
void IDisposable.Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.RunOnFrameworkThread(() =>
|
||||
{
|
||||
|
|
@ -476,7 +476,7 @@ internal sealed class Framework : IDisposable, IServiceType, IFramework
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IFramework>]
|
||||
#pragma warning restore SA1015
|
||||
internal class FrameworkPluginScoped : IDisposable, IServiceType, IFramework
|
||||
internal class FrameworkPluginScoped : IInternalDisposableService, IFramework
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly Framework frameworkService = Service<Framework>.Get();
|
||||
|
|
@ -511,7 +511,7 @@ internal class FrameworkPluginScoped : IDisposable, IServiceType, IFramework
|
|||
public bool IsFrameworkUnloading => this.frameworkService.IsFrameworkUnloading;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.frameworkService.Update -= this.OnUpdateForward;
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ namespace Dalamud.Game.Gui;
|
|||
/// </summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
internal sealed unsafe class ChatGui : IDisposable, IServiceType, IChatGui
|
||||
internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
||||
{
|
||||
private static readonly ModuleLog Log = new("ChatGui");
|
||||
|
||||
|
|
@ -109,7 +109,7 @@ internal sealed unsafe class ChatGui : IDisposable, IServiceType, IChatGui
|
|||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
void IDisposable.Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.printMessageHook.Dispose();
|
||||
this.populateItemLinkHook.Dispose();
|
||||
|
|
@ -409,7 +409,7 @@ internal sealed unsafe class ChatGui : IDisposable, IServiceType, IChatGui
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IChatGui>]
|
||||
#pragma warning restore SA1015
|
||||
internal class ChatGuiPluginScoped : IDisposable, IServiceType, IChatGui
|
||||
internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly ChatGui chatGuiService = Service<ChatGui>.Get();
|
||||
|
|
@ -447,7 +447,7 @@ internal class ChatGuiPluginScoped : IDisposable, IServiceType, IChatGui
|
|||
public IReadOnlyDictionary<(string PluginName, uint CommandId), Action<uint, SeString>> RegisteredLinkHandlers => this.chatGuiService.RegisteredLinkHandlers;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.chatGuiService.ChatMessage -= this.OnMessageForward;
|
||||
this.chatGuiService.CheckMessageHandled -= this.OnCheckMessageForward;
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ namespace Dalamud.Game.Gui.ContextMenu;
|
|||
/// </summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal sealed unsafe class ContextMenu : IDisposable, IServiceType, IContextMenu
|
||||
internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextMenu
|
||||
{
|
||||
private static readonly ModuleLog Log = new("ContextMenu");
|
||||
|
||||
|
|
@ -77,7 +77,7 @@ internal sealed unsafe class ContextMenu : IDisposable, IServiceType, IContextMe
|
|||
private IReadOnlyList<MenuItem>? SubmenuItems { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
var manager = RaptureAtkUnitManager.Instance();
|
||||
var menu = manager->GetAddonByName("ContextMenu");
|
||||
|
|
@ -496,7 +496,7 @@ original:
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IContextMenu>]
|
||||
#pragma warning restore SA1015
|
||||
internal class ContextMenuPluginScoped : IDisposable, IServiceType, IContextMenu
|
||||
internal class ContextMenuPluginScoped : IInternalDisposableService, IContextMenu
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly ContextMenu parentService = Service<ContextMenu>.Get();
|
||||
|
|
@ -514,7 +514,7 @@ internal class ContextMenuPluginScoped : IDisposable, IServiceType, IContextMenu
|
|||
private object MenuItemsLock { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.parentService.OnMenuOpened -= this.OnMenuOpenedForward;
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ namespace Dalamud.Game.Gui.Dtr;
|
|||
/// </summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
||||
internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar
|
||||
{
|
||||
private const uint BaseNodeId = 1000;
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IDisposable.Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.addonLifecycle.UnregisterListener(this.dtrPostDrawListener);
|
||||
this.addonLifecycle.UnregisterListener(this.dtrPostRequestedUpdateListener);
|
||||
|
|
@ -493,7 +493,7 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IDtrBar>]
|
||||
#pragma warning restore SA1015
|
||||
internal class DtrBarPluginScoped : IDisposable, IServiceType, IDtrBar
|
||||
internal class DtrBarPluginScoped : IInternalDisposableService, IDtrBar
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DtrBar dtrBarService = Service<DtrBar>.Get();
|
||||
|
|
@ -501,7 +501,7 @@ internal class DtrBarPluginScoped : IDisposable, IServiceType, IDtrBar
|
|||
private readonly Dictionary<string, DtrBarEntry> pluginEntries = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
foreach (var entry in this.pluginEntries)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ namespace Dalamud.Game.Gui.FlyText;
|
|||
/// </summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
internal sealed class FlyTextGui : IDisposable, IServiceType, IFlyTextGui
|
||||
internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui
|
||||
{
|
||||
/// <summary>
|
||||
/// The native function responsible for adding fly text to the UI. See <see cref="FlyTextGuiAddressResolver.AddFlyText"/>.
|
||||
|
|
@ -78,7 +78,7 @@ internal sealed class FlyTextGui : IDisposable, IServiceType, IFlyTextGui
|
|||
/// <summary>
|
||||
/// Disposes of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.createFlyTextHook.Dispose();
|
||||
}
|
||||
|
|
@ -277,7 +277,7 @@ internal sealed class FlyTextGui : IDisposable, IServiceType, IFlyTextGui
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IFlyTextGui>]
|
||||
#pragma warning restore SA1015
|
||||
internal class FlyTextGuiPluginScoped : IDisposable, IServiceType, IFlyTextGui
|
||||
internal class FlyTextGuiPluginScoped : IInternalDisposableService, IFlyTextGui
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly FlyTextGui flyTextGuiService = Service<FlyTextGui>.Get();
|
||||
|
|
@ -294,7 +294,7 @@ internal class FlyTextGuiPluginScoped : IDisposable, IServiceType, IFlyTextGui
|
|||
public event IFlyTextGui.OnFlyTextCreatedDelegate? FlyTextCreated;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.flyTextGuiService.FlyTextCreated -= this.FlyTextCreatedForward;
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ namespace Dalamud.Game.Gui;
|
|||
/// </summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
internal sealed unsafe class GameGui : IDisposable, IServiceType, IGameGui
|
||||
internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
|
||||
{
|
||||
private static readonly ModuleLog Log = new("GameGui");
|
||||
|
||||
|
|
@ -344,7 +344,7 @@ internal sealed unsafe class GameGui : IDisposable, IServiceType, IGameGui
|
|||
/// <summary>
|
||||
/// Disables the hooks and submodules of this module.
|
||||
/// </summary>
|
||||
void IDisposable.Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.setGlobalBgmHook.Dispose();
|
||||
this.handleItemHoverHook.Dispose();
|
||||
|
|
@ -520,7 +520,7 @@ internal sealed unsafe class GameGui : IDisposable, IServiceType, IGameGui
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IGameGui>]
|
||||
#pragma warning restore SA1015
|
||||
internal class GameGuiPluginScoped : IDisposable, IServiceType, IGameGui
|
||||
internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly GameGui gameGuiService = Service<GameGui>.Get();
|
||||
|
|
@ -558,7 +558,7 @@ internal class GameGuiPluginScoped : IDisposable, IServiceType, IGameGui
|
|||
public HoveredAction HoveredAction => this.gameGuiService.HoveredAction;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.gameGuiService.UiHideToggled -= this.UiHideToggledForward;
|
||||
this.gameGuiService.HoveredItemChanged -= this.HoveredItemForward;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Dalamud.Game.Gui.PartyFinder;
|
|||
/// </summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
internal sealed class PartyFinderGui : IDisposable, IServiceType, IPartyFinderGui
|
||||
internal sealed class PartyFinderGui : IInternalDisposableService, IPartyFinderGui
|
||||
{
|
||||
private readonly PartyFinderAddressResolver address;
|
||||
private readonly IntPtr memory;
|
||||
|
|
@ -47,7 +47,7 @@ internal sealed class PartyFinderGui : IDisposable, IServiceType, IPartyFinderGu
|
|||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
void IDisposable.Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.receiveListingHook.Dispose();
|
||||
|
||||
|
|
@ -131,7 +131,7 @@ internal sealed class PartyFinderGui : IDisposable, IServiceType, IPartyFinderGu
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IPartyFinderGui>]
|
||||
#pragma warning restore SA1015
|
||||
internal class PartyFinderGuiPluginScoped : IDisposable, IServiceType, IPartyFinderGui
|
||||
internal class PartyFinderGuiPluginScoped : IInternalDisposableService, IPartyFinderGui
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly PartyFinderGui partyFinderGuiService = Service<PartyFinderGui>.Get();
|
||||
|
|
@ -148,7 +148,7 @@ internal class PartyFinderGuiPluginScoped : IDisposable, IServiceType, IPartyFin
|
|||
public event IPartyFinderGui.PartyFinderListingEventDelegate? ReceiveListing;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.partyFinderGuiService.ReceiveListing -= this.ReceiveListingForward;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ namespace Dalamud.Game.Gui.Toast;
|
|||
/// </summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
internal sealed partial class ToastGui : IDisposable, IServiceType, IToastGui
|
||||
internal sealed partial class ToastGui : IInternalDisposableService, IToastGui
|
||||
{
|
||||
private const uint QuestToastCheckmarkMagic = 60081;
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ internal sealed partial class ToastGui : IDisposable, IServiceType, IToastGui
|
|||
/// <summary>
|
||||
/// Disposes of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
void IDisposable.Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.showNormalToastHook.Dispose();
|
||||
this.showQuestToastHook.Dispose();
|
||||
|
|
@ -383,7 +383,7 @@ internal sealed partial class ToastGui
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IToastGui>]
|
||||
#pragma warning restore SA1015
|
||||
internal class ToastGuiPluginScoped : IDisposable, IServiceType, IToastGui
|
||||
internal class ToastGuiPluginScoped : IInternalDisposableService, IToastGui
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly ToastGui toastGuiService = Service<ToastGui>.Get();
|
||||
|
|
@ -408,7 +408,7 @@ internal class ToastGuiPluginScoped : IDisposable, IServiceType, IToastGui
|
|||
public event IToastGui.OnErrorToastDelegate? ErrorToast;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.toastGuiService.Toast -= this.ToastForward;
|
||||
this.toastGuiService.QuestToast -= this.QuestToastForward;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ namespace Dalamud.Game.Internal;
|
|||
/// This class disables anti-debug functionality in the game client.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal sealed partial class AntiDebug : IServiceType
|
||||
internal sealed class AntiDebug : IInternalDisposableService
|
||||
{
|
||||
private readonly byte[] nop = new byte[] { 0x31, 0xC0, 0x90, 0x90, 0x90, 0x90 };
|
||||
private byte[] original;
|
||||
|
|
@ -43,16 +43,25 @@ internal sealed partial class AntiDebug : IServiceType
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>Finalizes an instance of the <see cref="AntiDebug"/> class.</summary>
|
||||
~AntiDebug() => this.Disable();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the anti-debugging is enabled.
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; private set; } = false;
|
||||
|
||||
/// <inheritdoc />
|
||||
void IInternalDisposableService.DisposeService() => this.Disable();
|
||||
|
||||
/// <summary>
|
||||
/// Enables the anti-debugging by overwriting code in memory.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
if (this.IsEnabled)
|
||||
return;
|
||||
|
||||
this.original = new byte[this.nop.Length];
|
||||
if (this.debugCheckAddress != IntPtr.Zero && !this.IsEnabled)
|
||||
{
|
||||
|
|
@ -73,6 +82,9 @@ internal sealed partial class AntiDebug : IServiceType
|
|||
/// </summary>
|
||||
public void Disable()
|
||||
{
|
||||
if (!this.IsEnabled)
|
||||
return;
|
||||
|
||||
if (this.debugCheckAddress != IntPtr.Zero && this.original != null)
|
||||
{
|
||||
Log.Information($"Reverting debug check at 0x{this.debugCheckAddress.ToInt64():X}");
|
||||
|
|
@ -86,45 +98,3 @@ internal sealed partial class AntiDebug : IServiceType
|
|||
this.IsEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementing IDisposable.
|
||||
/// </summary>
|
||||
internal sealed partial class AntiDebug : IDisposable
|
||||
{
|
||||
private bool disposed = false;
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="AntiDebug"/> class.
|
||||
/// </summary>
|
||||
~AntiDebug() => this.Dispose(false);
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing">If this was disposed through calling Dispose() or from being finalized.</param>
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (this.disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
// If anti-debug is enabled and is being disposed, odds are either the game is exiting, or Dalamud is being reloaded.
|
||||
// If it is the latter, there's half a chance a debugger is currently attached. There's no real need to disable the
|
||||
// check in either situation anyways. However if Dalamud is being reloaded, the sig may fail so may as well undo it.
|
||||
this.Disable();
|
||||
}
|
||||
|
||||
this.disposed = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ namespace Dalamud.Game.Internal;
|
|||
/// This class implements in-game Dalamud options in the in-game System menu.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal sealed unsafe partial class DalamudAtkTweaks : IServiceType
|
||||
internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService
|
||||
{
|
||||
private readonly AtkValueChangeType atkValueChangeType;
|
||||
private readonly AtkValueSetString atkValueSetString;
|
||||
|
|
@ -40,6 +40,8 @@ internal sealed unsafe partial class DalamudAtkTweaks : IServiceType
|
|||
private readonly string locDalamudPlugins;
|
||||
private readonly string locDalamudSettings;
|
||||
|
||||
private bool disposed = false;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private DalamudAtkTweaks(TargetSigScanner sigScanner)
|
||||
{
|
||||
|
|
@ -69,6 +71,9 @@ internal sealed unsafe partial class DalamudAtkTweaks : IServiceType
|
|||
this.hookAtkUnitBaseReceiveGlobalEvent.Enable();
|
||||
}
|
||||
|
||||
/// <summary>Finalizes an instance of the <see cref="DalamudAtkTweaks"/> class.</summary>
|
||||
~DalamudAtkTweaks() => this.Dispose(false);
|
||||
|
||||
private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize);
|
||||
|
||||
private delegate void AtkValueChangeType(AtkValue* thisPtr, ValueType type);
|
||||
|
|
@ -79,6 +84,26 @@ internal sealed unsafe partial class DalamudAtkTweaks : IServiceType
|
|||
|
||||
private delegate IntPtr AtkUnitBaseReceiveGlobalEvent(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* a5);
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService() => this.Dispose(true);
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (this.disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
this.hookAgentHudOpenSystemMenu.Dispose();
|
||||
this.hookUiModuleRequestMainCommand.Dispose();
|
||||
this.hookAtkUnitBaseReceiveGlobalEvent.Dispose();
|
||||
|
||||
// this.contextMenu.ContextMenuOpened -= this.ContextMenuOnContextMenuOpened;
|
||||
}
|
||||
|
||||
this.disposed = true;
|
||||
}
|
||||
|
||||
/*
|
||||
private void ContextMenuOnContextMenuOpened(ContextMenuOpenedArgs args)
|
||||
{
|
||||
|
|
@ -229,45 +254,3 @@ internal sealed unsafe partial class DalamudAtkTweaks : IServiceType
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements IDisposable.
|
||||
/// </summary>
|
||||
internal sealed partial class DalamudAtkTweaks : IDisposable
|
||||
{
|
||||
private bool disposed = false;
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="DalamudAtkTweaks"/> class.
|
||||
/// </summary>
|
||||
~DalamudAtkTweaks() => this.Dispose(false);
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (this.disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
this.hookAgentHudOpenSystemMenu.Dispose();
|
||||
this.hookUiModuleRequestMainCommand.Dispose();
|
||||
this.hookAtkUnitBaseReceiveGlobalEvent.Dispose();
|
||||
|
||||
// this.contextMenu.ContextMenuOpened -= this.ContextMenuOnContextMenuOpened;
|
||||
}
|
||||
|
||||
this.disposed = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ namespace Dalamud.Game.Inventory;
|
|||
/// </summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
internal class GameInventory : IDisposable, IServiceType
|
||||
internal class GameInventory : IInternalDisposableService
|
||||
{
|
||||
private readonly List<GameInventoryPluginScoped> subscribersPendingChange = new();
|
||||
private readonly List<GameInventoryPluginScoped> subscribers = new();
|
||||
|
|
@ -61,7 +61,7 @@ internal class GameInventory : IDisposable, IServiceType
|
|||
private unsafe delegate void RaptureAtkModuleUpdateDelegate(RaptureAtkModule* ram, float f1);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
lock (this.subscribersPendingChange)
|
||||
{
|
||||
|
|
@ -351,7 +351,7 @@ internal class GameInventory : IDisposable, IServiceType
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IGameInventory>]
|
||||
#pragma warning restore SA1015
|
||||
internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInventory
|
||||
internal class GameInventoryPluginScoped : IInternalDisposableService, IGameInventory
|
||||
{
|
||||
private static readonly ModuleLog Log = new(nameof(GameInventoryPluginScoped));
|
||||
|
||||
|
|
@ -406,7 +406,7 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
|
|||
public event IGameInventory.InventoryChangedDelegate<InventoryItemMergedArgs>? ItemMergedExplicit;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.gameInventoryService.Unsubscribe(this);
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Dalamud.Game.Network;
|
|||
/// </summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
internal sealed class GameNetwork : IDisposable, IServiceType, IGameNetwork
|
||||
internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork
|
||||
{
|
||||
private readonly GameNetworkAddressResolver address;
|
||||
private readonly Hook<ProcessZonePacketDownDelegate> processZonePacketDownHook;
|
||||
|
|
@ -59,7 +59,7 @@ internal sealed class GameNetwork : IDisposable, IServiceType, IGameNetwork
|
|||
public event IGameNetwork.OnNetworkMessageDelegate? NetworkMessage;
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IDisposable.Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.processZonePacketDownHook.Dispose();
|
||||
this.processZonePacketUpHook.Dispose();
|
||||
|
|
@ -145,7 +145,7 @@ internal sealed class GameNetwork : IDisposable, IServiceType, IGameNetwork
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IGameNetwork>]
|
||||
#pragma warning restore SA1015
|
||||
internal class GameNetworkPluginScoped : IDisposable, IServiceType, IGameNetwork
|
||||
internal class GameNetworkPluginScoped : IInternalDisposableService, IGameNetwork
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly GameNetwork gameNetworkService = Service<GameNetwork>.Get();
|
||||
|
|
@ -162,7 +162,7 @@ internal class GameNetworkPluginScoped : IDisposable, IServiceType, IGameNetwork
|
|||
public event IGameNetwork.OnNetworkMessageDelegate? NetworkMessage;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.gameNetworkService.NetworkMessage -= this.NetworkMessageForward;
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ namespace Dalamud.Game.Network.Internal;
|
|||
/// This class handles network notifications and uploading market board data.
|
||||
/// </summary>
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
internal unsafe class NetworkHandlers : IDisposable, IServiceType
|
||||
internal unsafe class NetworkHandlers : IInternalDisposableService
|
||||
{
|
||||
private readonly IMarketBoardUploader uploader;
|
||||
|
||||
|
|
@ -213,7 +213,7 @@ internal unsafe class NetworkHandlers : IDisposable, IServiceType
|
|||
/// <summary>
|
||||
/// Disposes of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.disposing = true;
|
||||
this.Dispose(this.disposing);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ namespace Dalamud.Game.Network.Internal;
|
|||
/// This class enables TCP optimizations in the game socket for better performance.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal sealed class WinSockHandlers : IDisposable, IServiceType
|
||||
internal sealed class WinSockHandlers : IInternalDisposableService
|
||||
{
|
||||
private Hook<SocketDelegate> ws2SocketHook;
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ internal sealed class WinSockHandlers : IDisposable, IServiceType
|
|||
/// <summary>
|
||||
/// Disposes of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.ws2SocketHook?.Dispose();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,6 +104,10 @@ public class SigScanner : IDisposable, ISigScanner
|
|||
/// <inheritdoc/>
|
||||
public ProcessModule Module { get; }
|
||||
|
||||
/// <summary>Gets or sets a value indicating whether this instance of <see cref="SigScanner"/> is meant to be a
|
||||
/// Dalamud service.</summary>
|
||||
private protected bool IsService { get; set; }
|
||||
|
||||
private IntPtr TextSectionTop => this.TextSectionBase + this.TextSectionSize;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -309,13 +313,11 @@ public class SigScanner : IDisposable, ISigScanner
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Free the memory of the copied module search area on object disposal, if applicable.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Save();
|
||||
Marshal.FreeHGlobal(this.moduleCopyPtr);
|
||||
if (!this.IsService)
|
||||
this.DisposeCore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -337,6 +339,15 @@ public class SigScanner : IDisposable, ISigScanner
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Free the memory of the copied module search area on object disposal, if applicable.
|
||||
/// </summary>
|
||||
private protected void DisposeCore()
|
||||
{
|
||||
this.Save();
|
||||
Marshal.FreeHGlobal(this.moduleCopyPtr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper for ScanText to get the correct address for IDA sigs that mark the first JMP or CALL location.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Dalamud.Game;
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<ISigScanner>]
|
||||
#pragma warning restore SA1015
|
||||
internal class TargetSigScanner : SigScanner, IServiceType
|
||||
internal class TargetSigScanner : SigScanner, IPublicDisposableService
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TargetSigScanner"/> class.
|
||||
|
|
@ -26,4 +26,14 @@ internal class TargetSigScanner : SigScanner, IServiceType
|
|||
: base(Process.GetCurrentProcess().MainModule!, doCopy, cacheFile)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
if (this.IsService)
|
||||
this.DisposeCore();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IPublicDisposableService.MarkDisposeOnlyFromService() => this.IsService = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -381,7 +381,7 @@ public class SeString
|
|||
{
|
||||
new PartyFinderPayload(listingId, isCrossWorld ? PartyFinderPayload.PartyFinderLinkType.NotSpecified : PartyFinderPayload.PartyFinderLinkType.LimitedToHomeWorld),
|
||||
// ->
|
||||
new TextPayload($"Looking for Party ({recruiterName})"),
|
||||
new TextPayload($"Looking for Party ({recruiterName})" + (isCrossWorld ? " " : string.Empty)),
|
||||
};
|
||||
|
||||
payloads.InsertRange(1, TextArrowPayloads);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ namespace Dalamud.Hooking.Internal;
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IGameInteropProvider>]
|
||||
#pragma warning restore SA1015
|
||||
internal class GameInteropProviderPluginScoped : IGameInteropProvider, IServiceType, IDisposable
|
||||
internal class GameInteropProviderPluginScoped : IGameInteropProvider, IInternalDisposableService
|
||||
{
|
||||
private readonly LocalPlugin plugin;
|
||||
private readonly SigScanner scanner;
|
||||
|
|
@ -83,7 +83,7 @@ internal class GameInteropProviderPluginScoped : IGameInteropProvider, IServiceT
|
|||
=> this.HookFromAddress(this.scanner.ScanText(signature), detour, backend);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
var notDisposed = this.trackedHooks.Where(x => !x.IsDisposed).ToArray();
|
||||
if (notDisposed.Length != 0)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ namespace Dalamud.Hooking.Internal;
|
|||
/// This class manages the final disposition of hooks, cleaning up any that have not reverted their changes.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class HookManager : IDisposable, IServiceType
|
||||
internal class HookManager : IInternalDisposableService
|
||||
{
|
||||
/// <summary>
|
||||
/// Logger shared with <see cref="Unhooker"/>.
|
||||
|
|
@ -74,7 +74,7 @@ internal class HookManager : IDisposable, IServiceType
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
RevertHooks();
|
||||
TrackedHooks.Clear();
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Dalamud.Hooking.WndProcHook;
|
|||
/// Manages WndProc hooks for game main window and extra ImGui viewport windows.
|
||||
/// </summary>
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
internal sealed class WndProcHookManager : IServiceType, IDisposable
|
||||
internal sealed class WndProcHookManager : IInternalDisposableService
|
||||
{
|
||||
private static readonly ModuleLog Log = new(nameof(WndProcHookManager));
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ internal sealed class WndProcHookManager : IServiceType, IDisposable
|
|||
public event WndProcEventDelegate? PostWndProc;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
if (this.dispatchMessageWHook.IsDisposed)
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -6,3 +6,20 @@
|
|||
public interface IServiceType
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary><see cref="IDisposable"/>, but for <see cref="IServiceType"/>.</summary>
|
||||
/// <remarks>Use this to prevent services from accidentally being disposed by plugins or <c>using</c> clauses.</remarks>
|
||||
internal interface IInternalDisposableService : IServiceType
|
||||
{
|
||||
/// <summary>Disposes the service.</summary>
|
||||
void DisposeService();
|
||||
}
|
||||
|
||||
/// <summary>An <see cref="IInternalDisposableService"/> which happens to be public and needs to expose
|
||||
/// <see cref="IDisposable.Dispose"/>.</summary>
|
||||
internal interface IPublicDisposableService : IInternalDisposableService, IDisposable
|
||||
{
|
||||
/// <summary>Marks that only <see cref="IInternalDisposableService.DisposeService"/> should respond,
|
||||
/// while suppressing <see cref="IDisposable.Dispose"/>.</summary>
|
||||
void MarkDisposeOnlyFromService();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ namespace Dalamud.Interface.DragDrop;
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IDragDropManager>]
|
||||
#pragma warning restore SA1015
|
||||
internal partial class DragDropManager : IDisposable, IDragDropManager, IServiceType
|
||||
internal partial class DragDropManager : IInternalDisposableService, IDragDropManager
|
||||
{
|
||||
private nint windowHandlePtr = nint.Zero;
|
||||
|
||||
|
|
@ -56,6 +56,9 @@ internal partial class DragDropManager : IDisposable, IDragDropManager, IService
|
|||
/// <summary> Gets the list of directory paths currently being dragged from an external application over any FFXIV-related viewport or stored from the last drop. </summary>
|
||||
public IReadOnlyList<string> Directories { get; private set; } = Array.Empty<string>();
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService() => this.Disable();
|
||||
|
||||
/// <summary> Enable external drag and drop. </summary>
|
||||
public void Enable()
|
||||
{
|
||||
|
|
@ -99,10 +102,6 @@ internal partial class DragDropManager : IDisposable, IDragDropManager, IService
|
|||
this.ServiceAvailable = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Disable"/>
|
||||
public void Dispose()
|
||||
=> this.Disable();
|
||||
|
||||
/// <inheritdoc cref="IDragDropManager.CreateImGuiSource(string, Func{IDragDropManager, bool}, Func{IDragDropManager, bool})"/>
|
||||
public void CreateImGuiSource(string label, Func<IDragDropManager, bool> validityCheck, Func<IDragDropManager, bool> tooltipBuilder)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ public sealed class SingleFontChooserDialog : IDisposable
|
|||
/// <summary>Initializes a new instance of the <see cref="SingleFontChooserDialog"/> class.</summary>
|
||||
/// <param name="newAsyncAtlas">A new instance of <see cref="IFontAtlas"/> created using
|
||||
/// <see cref="FontAtlasAutoRebuildMode.Async"/> as its auto-rebuild mode.</param>
|
||||
/// <remarks>The passed instance of <see cref="newAsyncAtlas"/> will be disposed after use. If you pass an atlas
|
||||
/// <remarks>The passed instance of <paramref see="newAsyncAtlas"/> will be disposed after use. If you pass an atlas
|
||||
/// that is already being used, then all the font handles under the passed atlas will be invalidated upon disposing
|
||||
/// this font chooser. Consider using <see cref="SingleFontChooserDialog(UiBuilder, bool, string?)"/> for automatic
|
||||
/// handling of font atlas derived from a <see cref="UiBuilder"/>, or even <see cref="CreateAuto"/> for automatic
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
namespace Dalamud.Interface.ImGuiNotification.EventArgs;
|
||||
|
||||
/// <summary>Arguments for use with <see cref="IActiveNotification.Click"/>.</summary>
|
||||
/// <remarks>Not to be implemented by plugins.</remarks>
|
||||
public interface INotificationClickArgs
|
||||
{
|
||||
/// <summary>Gets the notification being clicked.</summary>
|
||||
IActiveNotification Notification { get; }
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
namespace Dalamud.Interface.ImGuiNotification.EventArgs;
|
||||
|
||||
/// <summary>Arguments for use with <see cref="IActiveNotification.Dismiss"/>.</summary>
|
||||
/// <remarks>Not to be implemented by plugins.</remarks>
|
||||
public interface INotificationDismissArgs
|
||||
{
|
||||
/// <summary>Gets the notification being dismissed.</summary>
|
||||
IActiveNotification Notification { get; }
|
||||
|
||||
/// <summary>Gets the dismiss reason.</summary>
|
||||
NotificationDismissReason Reason { get; }
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace Dalamud.Interface.ImGuiNotification.EventArgs;
|
||||
|
||||
/// <summary>Arguments for use with <see cref="IActiveNotification.DrawActions"/>.</summary>
|
||||
/// <remarks>Not to be implemented by plugins.</remarks>
|
||||
public interface INotificationDrawArgs
|
||||
{
|
||||
/// <summary>Gets the notification being drawn.</summary>
|
||||
IActiveNotification Notification { get; }
|
||||
|
||||
/// <summary>Gets the top left coordinates of the area being drawn.</summary>
|
||||
Vector2 MinCoord { get; }
|
||||
|
||||
/// <summary>Gets the bottom right coordinates of the area being drawn.</summary>
|
||||
/// <remarks>Note that <see cref="Vector2.Y"/> can be <see cref="float.MaxValue"/>, in which case there is no
|
||||
/// vertical limits to the drawing region.</remarks>
|
||||
Vector2 MaxCoord { get; }
|
||||
}
|
||||
83
Dalamud/Interface/ImGuiNotification/IActiveNotification.cs
Normal file
83
Dalamud/Interface/ImGuiNotification/IActiveNotification.cs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Interface.ImGuiNotification.EventArgs;
|
||||
using Dalamud.Interface.Internal;
|
||||
|
||||
namespace Dalamud.Interface.ImGuiNotification;
|
||||
|
||||
/// <summary>Represents an active notification.</summary>
|
||||
/// <remarks>Not to be implemented by plugins.</remarks>
|
||||
public interface IActiveNotification : INotification
|
||||
{
|
||||
/// <summary>The counter for <see cref="Id"/> field.</summary>
|
||||
private static long idCounter;
|
||||
|
||||
/// <summary>Invoked upon dismissing the notification.</summary>
|
||||
/// <remarks>The event callback will not be called, if it gets dismissed after plugin unload.</remarks>
|
||||
event Action<INotificationDismissArgs> Dismiss;
|
||||
|
||||
/// <summary>Invoked upon clicking on the notification.</summary>
|
||||
/// <remarks>Note that this function may be called even after <see cref="Dismiss"/> has been invoked.</remarks>
|
||||
event Action<INotificationClickArgs> Click;
|
||||
|
||||
/// <summary>Invoked upon drawing the action bar of the notification.</summary>
|
||||
/// <remarks>Note that this function may be called even after <see cref="Dismiss"/> has been invoked.</remarks>
|
||||
event Action<INotificationDrawArgs> DrawActions;
|
||||
|
||||
/// <summary>Gets the ID of this notification.</summary>
|
||||
/// <remarks>This value does not change.</remarks>
|
||||
long Id { get; }
|
||||
|
||||
/// <summary>Gets the time of creating this notification.</summary>
|
||||
/// <remarks>This value does not change.</remarks>
|
||||
DateTime CreatedAt { get; }
|
||||
|
||||
/// <summary>Gets the effective expiry time.</summary>
|
||||
/// <remarks>Contains <see cref="DateTime.MaxValue"/> if the notification does not expire.</remarks>
|
||||
/// <remarks>This value will change depending on property changes and user interactions.</remarks>
|
||||
DateTime EffectiveExpiry { get; }
|
||||
|
||||
/// <summary>Gets the reason how this notification got dismissed. <c>null</c> if not dismissed.</summary>
|
||||
/// <remarks>This includes when the hide animation is being played.</remarks>
|
||||
NotificationDismissReason? DismissReason { get; }
|
||||
|
||||
/// <summary>Dismisses this notification.</summary>
|
||||
/// <remarks>If the notification has already been dismissed, this function does nothing.</remarks>
|
||||
void DismissNow();
|
||||
|
||||
/// <summary>Extends this notifiation.</summary>
|
||||
/// <param name="extension">The extension time.</param>
|
||||
/// <remarks>This does not override <see cref="INotification.HardExpiry"/>.</remarks>
|
||||
void ExtendBy(TimeSpan extension);
|
||||
|
||||
/// <summary>Sets the icon from <see cref="IDalamudTextureWrap"/>, overriding the icon.</summary>
|
||||
/// <param name="textureWrap">The new texture wrap to use, or null to clear and revert back to the icon specified
|
||||
/// from <see cref="INotification.Icon"/>.</param>
|
||||
/// <remarks>
|
||||
/// <para>The texture passed will be disposed when the notification is dismissed or a new different texture is set
|
||||
/// via another call to this function. You do not have to dispose it yourself.</para>
|
||||
/// <para>If <see cref="DismissReason"/> is not <c>null</c>, then calling this function will simply dispose the
|
||||
/// passed <paramref name="textureWrap"/> without actually updating the icon.</para>
|
||||
/// </remarks>
|
||||
void SetIconTexture(IDalamudTextureWrap? textureWrap);
|
||||
|
||||
/// <summary>Sets the icon from <see cref="IDalamudTextureWrap"/>, overriding the icon, once the given task
|
||||
/// completes.</summary>
|
||||
/// <param name="textureWrapTask">The task that will result in a new texture wrap to use, or null to clear and
|
||||
/// revert back to the icon specified from <see cref="INotification.Icon"/>.</param>
|
||||
/// <remarks>
|
||||
/// <para>The texture resulted from the passed <see cref="Task{TResult}"/> will be disposed when the notification
|
||||
/// is dismissed or a new different texture is set via another call to this function. You do not have to dispose the
|
||||
/// resulted instance of <see cref="IDalamudTextureWrap"/> yourself.</para>
|
||||
/// <para>If the task fails for any reason, the exception will be silently ignored and the icon specified from
|
||||
/// <see cref="INotification.Icon"/> will be used instead.</para>
|
||||
/// <para>If <see cref="DismissReason"/> is not <c>null</c>, then calling this function will simply dispose the
|
||||
/// result of the passed <paramref name="textureWrapTask"/> without actually updating the icon.</para>
|
||||
/// </remarks>
|
||||
void SetIconTexture(Task<IDalamudTextureWrap?>? textureWrapTask);
|
||||
|
||||
/// <summary>Generates a new value to use for <see cref="Id"/>.</summary>
|
||||
/// <returns>The new value.</returns>
|
||||
internal static long CreateNewId() => Interlocked.Increment(ref idCounter);
|
||||
}
|
||||
76
Dalamud/Interface/ImGuiNotification/INotification.cs
Normal file
76
Dalamud/Interface/ImGuiNotification/INotification.cs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace Dalamud.Interface.ImGuiNotification;
|
||||
|
||||
/// <summary>Represents a notification.</summary>
|
||||
/// <remarks>Not to be implemented by plugins.</remarks>
|
||||
public interface INotification
|
||||
{
|
||||
/// <summary>Gets or sets the content body of the notification.</summary>
|
||||
string Content { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the title of the notification.</summary>
|
||||
string? Title { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the text to display when the notification is minimized.</summary>
|
||||
string? MinimizedText { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the type of the notification.</summary>
|
||||
NotificationType Type { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the icon source.</summary>
|
||||
/// <remarks>Use <see cref="IActiveNotification.SetIconTexture(IDalamudTextureWrap?)"/> or
|
||||
/// <see cref="IActiveNotification.SetIconTexture(Task{IDalamudTextureWrap?}?)"/> to use a texture, after calling
|
||||
/// <see cref="INotificationManager.AddNotification"/>. Call either of those functions with <c>null</c> to revert
|
||||
/// the effective icon back to this property.</remarks>
|
||||
INotificationIcon? Icon { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the hard expiry.</summary>
|
||||
/// <remarks>
|
||||
/// Setting this value will override <see cref="InitialDuration"/> and <see cref="ExtensionDurationSinceLastInterest"/>, in that
|
||||
/// the notification will be dismissed when this expiry expires.<br />
|
||||
/// Set to <see cref="DateTime.MaxValue"/> to make only <see cref="InitialDuration"/> take effect.<br />
|
||||
/// If neither <see cref="HardExpiry"/> nor <see cref="InitialDuration"/> is not MaxValue, then the notification
|
||||
/// will not expire after a set time. It must be explicitly dismissed by the user of via calling
|
||||
/// <see cref="IActiveNotification.DismissNow"/>.<br />
|
||||
/// Updating this value will reset the dismiss timer.
|
||||
/// </remarks>
|
||||
DateTime HardExpiry { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the initial duration.</summary>
|
||||
/// <remarks>Set to <see cref="TimeSpan.MaxValue"/> to make only <see cref="HardExpiry"/> take effect.</remarks>
|
||||
/// <remarks>Updating this value will reset the dismiss timer, but the remaining duration will still be calculated
|
||||
/// based on <see cref="IActiveNotification.CreatedAt"/>.</remarks>
|
||||
TimeSpan InitialDuration { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the new duration for this notification once the mouse cursor leaves the window and the
|
||||
/// window is no longer focused.</summary>
|
||||
/// <remarks>
|
||||
/// If set to <see cref="TimeSpan.Zero"/> or less, then this feature is turned off, and hovering the mouse on the
|
||||
/// notification or focusing on it will not make the notification stay.<br />
|
||||
/// Updating this value will reset the dismiss timer.
|
||||
/// </remarks>
|
||||
TimeSpan ExtensionDurationSinceLastInterest { get; set; }
|
||||
|
||||
/// <summary>Gets or sets a value indicating whether to show an indeterminate expiration animation if
|
||||
/// <see cref="HardExpiry"/> is set to <see cref="DateTime.MaxValue"/>.</summary>
|
||||
bool ShowIndeterminateIfNoExpiry { get; set; }
|
||||
|
||||
/// <summary>Gets or sets a value indicating whether to respect the current UI visibility state.</summary>
|
||||
bool RespectUiHidden { get; set; }
|
||||
|
||||
/// <summary>Gets or sets a value indicating whether the notification has been minimized.</summary>
|
||||
bool Minimized { get; set; }
|
||||
|
||||
/// <summary>Gets or sets a value indicating whether the user can dismiss the notification by themselves.</summary>
|
||||
/// <remarks>Consider adding a cancel button to <see cref="IActiveNotification.DrawActions"/>.</remarks>
|
||||
bool UserDismissable { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the progress for the background progress bar of the notification.</summary>
|
||||
/// <remarks>The progress should be in the range between 0 and 1.</remarks>
|
||||
float Progress { get; set; }
|
||||
}
|
||||
54
Dalamud/Interface/ImGuiNotification/INotificationIcon.cs
Normal file
54
Dalamud/Interface/ImGuiNotification/INotificationIcon.cs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Interface.ImGuiNotification.Internal.NotificationIcon;
|
||||
|
||||
namespace Dalamud.Interface.ImGuiNotification;
|
||||
|
||||
/// <summary>Icon source for <see cref="INotification"/>.</summary>
|
||||
/// <remarks>Plugins implementing this interface are left to their own on managing the resources contained by the
|
||||
/// instance of their implementation of <see cref="INotificationIcon"/>. In other words, they should not expect to have
|
||||
/// <see cref="IDisposable.Dispose"/> called if their implementation is an <see cref="IDisposable"/>. Dalamud will not
|
||||
/// call <see cref="IDisposable.Dispose"/> on any instance of <see cref="INotificationIcon"/>. On plugin unloads, the
|
||||
/// icon may be reverted back to the default, if the instance of <see cref="INotificationIcon"/> is not provided by
|
||||
/// Dalamud.</remarks>
|
||||
public interface INotificationIcon
|
||||
{
|
||||
/// <summary>Gets a new instance of <see cref="INotificationIcon"/> that will source the icon from an
|
||||
/// <see cref="SeIconChar"/>.</summary>
|
||||
/// <param name="iconChar">The icon character.</param>
|
||||
/// <returns>A new instance of <see cref="INotificationIcon"/> that should be disposed after use.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static INotificationIcon From(SeIconChar iconChar) => new SeIconCharNotificationIcon(iconChar);
|
||||
|
||||
/// <summary>Gets a new instance of <see cref="INotificationIcon"/> that will source the icon from an
|
||||
/// <see cref="FontAwesomeIcon"/>.</summary>
|
||||
/// <param name="iconChar">The icon character.</param>
|
||||
/// <returns>A new instance of <see cref="INotificationIcon"/> that should be disposed after use.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static INotificationIcon From(FontAwesomeIcon iconChar) => new FontAwesomeIconNotificationIcon(iconChar);
|
||||
|
||||
/// <summary>Gets a new instance of <see cref="INotificationIcon"/> that will source the icon from a texture
|
||||
/// file shipped as a part of the game resources.</summary>
|
||||
/// <param name="gamePath">The path to a texture file in the game virtual file system.</param>
|
||||
/// <returns>A new instance of <see cref="INotificationIcon"/> that should be disposed after use.</returns>
|
||||
/// <remarks>If any errors are thrown, the default icon will be displayed instead.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static INotificationIcon FromGame(string gamePath) => new GamePathNotificationIcon(gamePath);
|
||||
|
||||
/// <summary>Gets a new instance of <see cref="INotificationIcon"/> that will source the icon from an image
|
||||
/// file from the file system.</summary>
|
||||
/// <param name="filePath">The path to an image file in the file system.</param>
|
||||
/// <returns>A new instance of <see cref="INotificationIcon"/> that should be disposed after use.</returns>
|
||||
/// <remarks>If any errors are thrown, the default icon will be displayed instead.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static INotificationIcon FromFile(string filePath) => new FilePathNotificationIcon(filePath);
|
||||
|
||||
/// <summary>Draws the icon.</summary>
|
||||
/// <param name="minCoord">The coordinates of the top left of the icon area.</param>
|
||||
/// <param name="maxCoord">The coordinates of the bottom right of the icon area.</param>
|
||||
/// <param name="color">The foreground color.</param>
|
||||
/// <returns><c>true</c> if anything has been drawn.</returns>
|
||||
bool DrawIcon(Vector2 minCoord, Vector2 maxCoord, Vector4 color);
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
using System.Numerics;
|
||||
|
||||
using Dalamud.Interface.ImGuiNotification.EventArgs;
|
||||
|
||||
namespace Dalamud.Interface.ImGuiNotification.Internal;
|
||||
|
||||
/// <summary>Represents an active notification.</summary>
|
||||
internal sealed partial class ActiveNotification : INotificationDismissArgs
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public event Action<INotificationDismissArgs>? Dismiss;
|
||||
|
||||
/// <inheritdoc/>
|
||||
IActiveNotification INotificationDismissArgs.Notification => this;
|
||||
|
||||
/// <inheritdoc/>
|
||||
NotificationDismissReason INotificationDismissArgs.Reason =>
|
||||
this.DismissReason
|
||||
?? throw new InvalidOperationException("DismissReason must be set before using INotificationDismissArgs");
|
||||
|
||||
private void InvokeDismiss()
|
||||
{
|
||||
try
|
||||
{
|
||||
this.Dismiss?.Invoke(this);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.LogEventInvokeError(e, $"{nameof(this.Dismiss)} error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Represents an active notification.</summary>
|
||||
internal sealed partial class ActiveNotification : INotificationClickArgs
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public event Action<INotificationClickArgs>? Click;
|
||||
|
||||
/// <inheritdoc/>
|
||||
IActiveNotification INotificationClickArgs.Notification => this;
|
||||
|
||||
private void InvokeClick()
|
||||
{
|
||||
try
|
||||
{
|
||||
this.Click?.Invoke(this);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.LogEventInvokeError(e, $"{nameof(this.Click)} error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Represents an active notification.</summary>
|
||||
internal sealed partial class ActiveNotification : INotificationDrawArgs
|
||||
{
|
||||
private Vector2 drawActionArgMinCoord;
|
||||
private Vector2 drawActionArgMaxCoord;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action<INotificationDrawArgs>? DrawActions;
|
||||
|
||||
/// <inheritdoc/>
|
||||
IActiveNotification INotificationDrawArgs.Notification => this;
|
||||
|
||||
/// <inheritdoc/>
|
||||
Vector2 INotificationDrawArgs.MinCoord => this.drawActionArgMinCoord;
|
||||
|
||||
/// <inheritdoc/>
|
||||
Vector2 INotificationDrawArgs.MaxCoord => this.drawActionArgMaxCoord;
|
||||
|
||||
private void InvokeDrawActions(Vector2 minCoord, Vector2 maxCoord)
|
||||
{
|
||||
this.drawActionArgMinCoord = minCoord;
|
||||
this.drawActionArgMaxCoord = maxCoord;
|
||||
try
|
||||
{
|
||||
this.DrawActions?.Invoke(this);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.LogEventInvokeError(e, $"{nameof(this.DrawActions)} error; event registration cancelled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,500 @@
|
|||
using System.Numerics;
|
||||
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.ImGuiNotification.Internal;
|
||||
|
||||
/// <summary>Represents an active notification.</summary>
|
||||
internal sealed partial class ActiveNotification
|
||||
{
|
||||
/// <summary>Draws this notification.</summary>
|
||||
/// <param name="width">The maximum width of the notification window.</param>
|
||||
/// <param name="offsetY">The offset from the bottom.</param>
|
||||
/// <returns>The height of the notification.</returns>
|
||||
public float Draw(float width, float offsetY)
|
||||
{
|
||||
var opacity =
|
||||
Math.Clamp(
|
||||
(float)(this.hideEasing.IsRunning
|
||||
? (this.hideEasing.IsDone ? 0 : 1f - this.hideEasing.Value)
|
||||
: (this.showEasing.IsDone ? 1 : this.showEasing.Value)),
|
||||
0f,
|
||||
1f);
|
||||
if (opacity <= 0)
|
||||
return 0;
|
||||
|
||||
var actionWindowHeight =
|
||||
// Content
|
||||
ImGui.GetTextLineHeight() +
|
||||
// Top and bottom padding
|
||||
(NotificationConstants.ScaledWindowPadding * 2);
|
||||
|
||||
var viewport = ImGuiHelpers.MainViewport;
|
||||
var viewportPos = viewport.WorkPos;
|
||||
var viewportSize = viewport.WorkSize;
|
||||
|
||||
ImGui.PushID(this.Id.GetHashCode());
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, opacity);
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 0f);
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(NotificationConstants.ScaledWindowPadding));
|
||||
unsafe
|
||||
{
|
||||
ImGui.PushStyleColor(
|
||||
ImGuiCol.WindowBg,
|
||||
*ImGui.GetStyleColorVec4(ImGuiCol.WindowBg) * new Vector4(
|
||||
1f,
|
||||
1f,
|
||||
1f,
|
||||
NotificationConstants.BackgroundOpacity));
|
||||
}
|
||||
|
||||
ImGuiHelpers.ForceNextWindowMainViewport();
|
||||
ImGui.SetNextWindowPos(
|
||||
(viewportPos + viewportSize) -
|
||||
new Vector2(NotificationConstants.ScaledViewportEdgeMargin) -
|
||||
new Vector2(0, offsetY),
|
||||
ImGuiCond.Always,
|
||||
Vector2.One);
|
||||
ImGui.SetNextWindowSizeConstraints(
|
||||
new(width, actionWindowHeight),
|
||||
new(
|
||||
width,
|
||||
!this.underlyingNotification.Minimized || this.expandoEasing.IsRunning
|
||||
? float.MaxValue
|
||||
: actionWindowHeight));
|
||||
ImGui.Begin(
|
||||
$"##NotifyMainWindow{this.Id}",
|
||||
ImGuiWindowFlags.AlwaysAutoResize |
|
||||
ImGuiWindowFlags.NoDecoration |
|
||||
ImGuiWindowFlags.NoNav |
|
||||
ImGuiWindowFlags.NoMove |
|
||||
ImGuiWindowFlags.NoFocusOnAppearing |
|
||||
ImGuiWindowFlags.NoDocking);
|
||||
|
||||
var isFocused = ImGui.IsWindowFocused();
|
||||
var isHovered = ImGui.IsWindowHovered(ImGuiHoveredFlags.AllowWhenBlockedByActiveItem);
|
||||
var isTakingKeyboardInput = isFocused && ImGui.GetIO().WantTextInput;
|
||||
var warrantsExtension =
|
||||
this.ExtensionDurationSinceLastInterest > TimeSpan.Zero
|
||||
&& (isHovered || isTakingKeyboardInput);
|
||||
|
||||
this.EffectiveExpiry = this.CalculateEffectiveExpiry(ref warrantsExtension);
|
||||
|
||||
if (!isTakingKeyboardInput && !isHovered && isFocused)
|
||||
{
|
||||
ImGui.SetWindowFocus(null);
|
||||
isFocused = false;
|
||||
}
|
||||
|
||||
if (DateTime.Now > this.EffectiveExpiry)
|
||||
this.DismissNow(NotificationDismissReason.Timeout);
|
||||
|
||||
if (this.ExtensionDurationSinceLastInterest > TimeSpan.Zero && warrantsExtension)
|
||||
this.lastInterestTime = DateTime.Now;
|
||||
|
||||
this.DrawWindowBackgroundProgressBar();
|
||||
this.DrawTopBar(width, actionWindowHeight, isHovered);
|
||||
if (!this.underlyingNotification.Minimized && !this.expandoEasing.IsRunning)
|
||||
{
|
||||
this.DrawContentAndActions(width, actionWindowHeight);
|
||||
}
|
||||
else if (this.expandoEasing.IsRunning)
|
||||
{
|
||||
if (this.underlyingNotification.Minimized)
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, opacity * (1f - (float)this.expandoEasing.Value));
|
||||
else
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, opacity * (float)this.expandoEasing.Value);
|
||||
this.DrawContentAndActions(width, actionWindowHeight);
|
||||
ImGui.PopStyleVar();
|
||||
}
|
||||
|
||||
if (isFocused)
|
||||
this.DrawFocusIndicator();
|
||||
this.DrawExpiryBar(this.EffectiveExpiry, warrantsExtension);
|
||||
|
||||
if (ImGui.IsWindowHovered())
|
||||
{
|
||||
if (this.Click is null)
|
||||
{
|
||||
if (this.UserDismissable && ImGui.IsMouseClicked(ImGuiMouseButton.Left))
|
||||
this.DismissNow(NotificationDismissReason.Manual);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ImGui.IsMouseClicked(ImGuiMouseButton.Left)
|
||||
|| ImGui.IsMouseClicked(ImGuiMouseButton.Right)
|
||||
|| ImGui.IsMouseClicked(ImGuiMouseButton.Middle))
|
||||
this.InvokeClick();
|
||||
}
|
||||
}
|
||||
|
||||
var windowSize = ImGui.GetWindowSize();
|
||||
ImGui.End();
|
||||
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.PopStyleVar(3);
|
||||
ImGui.PopID();
|
||||
|
||||
return windowSize.Y;
|
||||
}
|
||||
|
||||
/// <summary>Calculates the effective expiry, taking ImGui window state into account.</summary>
|
||||
/// <param name="warrantsExtension">Notification will not dismiss while this paramter is <c>true</c>.</param>
|
||||
/// <returns>The calculated effective expiry.</returns>
|
||||
/// <remarks>Expected to be called BETWEEN <see cref="ImGui.Begin(string)"/> and <see cref="ImGui.End"/>.</remarks>
|
||||
private DateTime CalculateEffectiveExpiry(ref bool warrantsExtension)
|
||||
{
|
||||
DateTime expiry;
|
||||
var initialDuration = this.InitialDuration;
|
||||
var expiryInitial =
|
||||
initialDuration == TimeSpan.MaxValue
|
||||
? DateTime.MaxValue
|
||||
: this.CreatedAt + initialDuration;
|
||||
|
||||
var extendDuration = this.ExtensionDurationSinceLastInterest;
|
||||
if (warrantsExtension)
|
||||
{
|
||||
expiry = DateTime.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
var expiryExtend =
|
||||
extendDuration == TimeSpan.MaxValue
|
||||
? DateTime.MaxValue
|
||||
: this.lastInterestTime + extendDuration;
|
||||
|
||||
expiry = expiryInitial > expiryExtend ? expiryInitial : expiryExtend;
|
||||
if (expiry < this.extendedExpiry)
|
||||
expiry = this.extendedExpiry;
|
||||
}
|
||||
|
||||
var he = this.HardExpiry;
|
||||
if (he < expiry)
|
||||
{
|
||||
expiry = he;
|
||||
warrantsExtension = false;
|
||||
}
|
||||
|
||||
return expiry;
|
||||
}
|
||||
|
||||
private void DrawWindowBackgroundProgressBar()
|
||||
{
|
||||
var elapsed = (float)(((DateTime.Now - this.CreatedAt).TotalMilliseconds %
|
||||
NotificationConstants.ProgressWaveLoopDuration) /
|
||||
NotificationConstants.ProgressWaveLoopDuration);
|
||||
elapsed /= NotificationConstants.ProgressWaveIdleTimeRatio;
|
||||
|
||||
var colorElapsed =
|
||||
elapsed < NotificationConstants.ProgressWaveLoopMaxColorTimeRatio
|
||||
? elapsed / NotificationConstants.ProgressWaveLoopMaxColorTimeRatio
|
||||
: ((NotificationConstants.ProgressWaveLoopMaxColorTimeRatio * 2) - elapsed) /
|
||||
NotificationConstants.ProgressWaveLoopMaxColorTimeRatio;
|
||||
|
||||
elapsed = Math.Clamp(elapsed, 0f, 1f);
|
||||
colorElapsed = Math.Clamp(colorElapsed, 0f, 1f);
|
||||
colorElapsed = MathF.Sin(colorElapsed * (MathF.PI / 2f));
|
||||
|
||||
var progress = Math.Clamp(this.ProgressEased, 0f, 1f);
|
||||
if (progress >= 1f)
|
||||
elapsed = colorElapsed = 0f;
|
||||
|
||||
var windowPos = ImGui.GetWindowPos();
|
||||
var windowSize = ImGui.GetWindowSize();
|
||||
var rb = windowPos + windowSize;
|
||||
var midp = windowPos + windowSize with { X = windowSize.X * progress * elapsed };
|
||||
var rp = windowPos + windowSize with { X = windowSize.X * progress };
|
||||
|
||||
ImGui.PushClipRect(windowPos, rb, false);
|
||||
ImGui.GetWindowDrawList().AddRectFilled(
|
||||
windowPos,
|
||||
midp,
|
||||
ImGui.GetColorU32(
|
||||
Vector4.Lerp(
|
||||
NotificationConstants.BackgroundProgressColorMin,
|
||||
NotificationConstants.BackgroundProgressColorMax,
|
||||
colorElapsed)));
|
||||
ImGui.GetWindowDrawList().AddRectFilled(
|
||||
midp with { Y = 0 },
|
||||
rp,
|
||||
ImGui.GetColorU32(NotificationConstants.BackgroundProgressColorMin));
|
||||
ImGui.PopClipRect();
|
||||
}
|
||||
|
||||
private void DrawFocusIndicator()
|
||||
{
|
||||
var windowPos = ImGui.GetWindowPos();
|
||||
var windowSize = ImGui.GetWindowSize();
|
||||
ImGui.PushClipRect(windowPos, windowPos + windowSize, false);
|
||||
ImGui.GetWindowDrawList().AddRect(
|
||||
windowPos,
|
||||
windowPos + windowSize,
|
||||
ImGui.GetColorU32(NotificationConstants.FocusBorderColor * new Vector4(1f, 1f, 1f, ImGui.GetStyle().Alpha)),
|
||||
0f,
|
||||
ImDrawFlags.None,
|
||||
NotificationConstants.FocusIndicatorThickness);
|
||||
ImGui.PopClipRect();
|
||||
}
|
||||
|
||||
private void DrawTopBar(float width, float height, bool drawActionButtons)
|
||||
{
|
||||
var windowPos = ImGui.GetWindowPos();
|
||||
var windowSize = ImGui.GetWindowSize();
|
||||
|
||||
var rtOffset = new Vector2(width, 0);
|
||||
using (Service<InterfaceManager>.Get().IconFontHandle?.Push())
|
||||
{
|
||||
ImGui.PushClipRect(windowPos, windowPos + windowSize with { Y = height }, false);
|
||||
if (this.UserDismissable)
|
||||
{
|
||||
if (this.DrawIconButton(FontAwesomeIcon.Times, rtOffset, height, drawActionButtons))
|
||||
this.DismissNow(NotificationDismissReason.Manual);
|
||||
rtOffset.X -= height;
|
||||
}
|
||||
|
||||
if (this.underlyingNotification.Minimized)
|
||||
{
|
||||
if (this.DrawIconButton(FontAwesomeIcon.ChevronDown, rtOffset, height, drawActionButtons))
|
||||
this.Minimized = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.DrawIconButton(FontAwesomeIcon.ChevronUp, rtOffset, height, drawActionButtons))
|
||||
this.Minimized = true;
|
||||
}
|
||||
|
||||
rtOffset.X -= height;
|
||||
ImGui.PopClipRect();
|
||||
}
|
||||
|
||||
float relativeOpacity;
|
||||
if (this.expandoEasing.IsRunning)
|
||||
{
|
||||
relativeOpacity =
|
||||
this.underlyingNotification.Minimized
|
||||
? 1f - (float)this.expandoEasing.Value
|
||||
: (float)this.expandoEasing.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
relativeOpacity = this.underlyingNotification.Minimized ? 0f : 1f;
|
||||
}
|
||||
|
||||
if (drawActionButtons)
|
||||
ImGui.PushClipRect(windowPos, windowPos + rtOffset with { Y = height }, false);
|
||||
else
|
||||
ImGui.PushClipRect(windowPos, windowPos + windowSize with { Y = height }, false);
|
||||
|
||||
if (relativeOpacity > 0)
|
||||
{
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, ImGui.GetStyle().Alpha * relativeOpacity);
|
||||
ImGui.SetCursorPos(new(NotificationConstants.ScaledWindowPadding));
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, NotificationConstants.WhenTextColor);
|
||||
ImGui.TextUnformatted(
|
||||
ImGui.IsWindowHovered(ImGuiHoveredFlags.AllowWhenBlockedByActiveItem)
|
||||
? this.CreatedAt.LocAbsolute()
|
||||
: this.CreatedAt.LocRelativePastLong());
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.PopStyleVar();
|
||||
}
|
||||
|
||||
if (relativeOpacity < 1)
|
||||
{
|
||||
rtOffset = new(width - NotificationConstants.ScaledWindowPadding, 0);
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, ImGui.GetStyle().Alpha * (1f - relativeOpacity));
|
||||
|
||||
var ltOffset = new Vector2(NotificationConstants.ScaledWindowPadding);
|
||||
this.DrawIcon(ltOffset, new(height - (2 * NotificationConstants.ScaledWindowPadding)));
|
||||
|
||||
ltOffset.X = height;
|
||||
|
||||
var agoText = this.CreatedAt.LocRelativePastShort();
|
||||
var agoSize = ImGui.CalcTextSize(agoText);
|
||||
rtOffset.X -= agoSize.X;
|
||||
ImGui.SetCursorPos(rtOffset with { Y = NotificationConstants.ScaledWindowPadding });
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, NotificationConstants.WhenTextColor);
|
||||
ImGui.TextUnformatted(agoText);
|
||||
ImGui.PopStyleColor();
|
||||
|
||||
rtOffset.X -= NotificationConstants.ScaledWindowPadding;
|
||||
|
||||
ImGui.PushClipRect(
|
||||
windowPos + ltOffset with { Y = 0 },
|
||||
windowPos + rtOffset with { Y = height },
|
||||
true);
|
||||
ImGui.SetCursorPos(ltOffset with { Y = NotificationConstants.ScaledWindowPadding });
|
||||
ImGui.TextUnformatted(this.EffectiveMinimizedText);
|
||||
ImGui.PopClipRect();
|
||||
|
||||
ImGui.PopStyleVar();
|
||||
}
|
||||
|
||||
ImGui.PopClipRect();
|
||||
}
|
||||
|
||||
private bool DrawIconButton(FontAwesomeIcon icon, Vector2 rt, float size, bool drawActionButtons)
|
||||
{
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero);
|
||||
if (!drawActionButtons)
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0f);
|
||||
ImGui.PushStyleColor(ImGuiCol.Button, 0);
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, NotificationConstants.CloseTextColor);
|
||||
|
||||
ImGui.SetCursorPos(rt - new Vector2(size, 0));
|
||||
var r = ImGui.Button(icon.ToIconString(), new(size));
|
||||
|
||||
ImGui.PopStyleColor(2);
|
||||
if (!drawActionButtons)
|
||||
ImGui.PopStyleVar();
|
||||
ImGui.PopStyleVar();
|
||||
return r;
|
||||
}
|
||||
|
||||
private void DrawContentAndActions(float width, float actionWindowHeight)
|
||||
{
|
||||
var textColumnX = (NotificationConstants.ScaledWindowPadding * 2) + NotificationConstants.ScaledIconSize;
|
||||
var textColumnWidth = width - textColumnX - NotificationConstants.ScaledWindowPadding;
|
||||
var textColumnOffset = new Vector2(textColumnX, actionWindowHeight);
|
||||
|
||||
this.DrawIcon(
|
||||
new(NotificationConstants.ScaledWindowPadding, actionWindowHeight),
|
||||
new(NotificationConstants.ScaledIconSize));
|
||||
|
||||
textColumnOffset.Y += this.DrawTitle(textColumnOffset, textColumnWidth);
|
||||
textColumnOffset.Y += NotificationConstants.ScaledComponentGap;
|
||||
|
||||
this.DrawContentBody(textColumnOffset, textColumnWidth);
|
||||
|
||||
if (this.DrawActions is null)
|
||||
return;
|
||||
|
||||
var userActionOffset = new Vector2(
|
||||
NotificationConstants.ScaledWindowPadding,
|
||||
ImGui.GetCursorPosY() + NotificationConstants.ScaledComponentGap);
|
||||
ImGui.SetCursorPos(userActionOffset);
|
||||
this.InvokeDrawActions(
|
||||
userActionOffset,
|
||||
new(width - NotificationConstants.ScaledWindowPadding, float.MaxValue));
|
||||
}
|
||||
|
||||
private void DrawIcon(Vector2 minCoord, Vector2 size)
|
||||
{
|
||||
var maxCoord = minCoord + size;
|
||||
var iconColor = this.Type.ToColor();
|
||||
|
||||
if (NotificationUtilities.DrawIconFrom(minCoord, maxCoord, this.iconTextureWrap))
|
||||
return;
|
||||
|
||||
if (this.Icon?.DrawIcon(minCoord, maxCoord, iconColor) is true)
|
||||
return;
|
||||
|
||||
if (NotificationUtilities.DrawIconFrom(
|
||||
minCoord,
|
||||
maxCoord,
|
||||
this.Type.ToChar(),
|
||||
Service<NotificationManager>.Get().IconFontAwesomeFontHandle,
|
||||
iconColor))
|
||||
return;
|
||||
|
||||
if (NotificationUtilities.DrawIconFrom(minCoord, maxCoord, this.initiatorPlugin))
|
||||
return;
|
||||
|
||||
NotificationUtilities.DrawIconFromDalamudLogo(minCoord, maxCoord);
|
||||
}
|
||||
|
||||
private float DrawTitle(Vector2 minCoord, float width)
|
||||
{
|
||||
ImGui.PushTextWrapPos(minCoord.X + width);
|
||||
|
||||
ImGui.SetCursorPos(minCoord);
|
||||
if ((this.Title ?? this.Type.ToTitle()) is { } title)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, NotificationConstants.TitleTextColor);
|
||||
ImGui.TextUnformatted(title);
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, NotificationConstants.BlameTextColor);
|
||||
ImGui.SetCursorPos(minCoord with { Y = ImGui.GetCursorPosY() });
|
||||
ImGui.TextUnformatted(this.InitiatorString);
|
||||
ImGui.PopStyleColor();
|
||||
|
||||
ImGui.PopTextWrapPos();
|
||||
return ImGui.GetCursorPosY() - minCoord.Y;
|
||||
}
|
||||
|
||||
private void DrawContentBody(Vector2 minCoord, float width)
|
||||
{
|
||||
ImGui.SetCursorPos(minCoord);
|
||||
ImGui.PushTextWrapPos(minCoord.X + width);
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, NotificationConstants.BodyTextColor);
|
||||
ImGui.TextUnformatted(this.Content);
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.PopTextWrapPos();
|
||||
}
|
||||
|
||||
private void DrawExpiryBar(DateTime effectiveExpiry, bool warrantsExtension)
|
||||
{
|
||||
float barL, barR;
|
||||
if (this.DismissReason is not null)
|
||||
{
|
||||
var v = this.hideEasing.IsDone ? 0f : 1f - (float)this.hideEasing.Value;
|
||||
var midpoint = (this.prevProgressL + this.prevProgressR) / 2f;
|
||||
var length = (this.prevProgressR - this.prevProgressL) / 2f;
|
||||
barL = midpoint - (length * v);
|
||||
barR = midpoint + (length * v);
|
||||
}
|
||||
else if (warrantsExtension)
|
||||
{
|
||||
barL = 0f;
|
||||
barR = 1f;
|
||||
this.prevProgressL = barL;
|
||||
this.prevProgressR = barR;
|
||||
}
|
||||
else if (effectiveExpiry == DateTime.MaxValue)
|
||||
{
|
||||
if (this.ShowIndeterminateIfNoExpiry)
|
||||
{
|
||||
var elapsed = (float)(((DateTime.Now - this.CreatedAt).TotalMilliseconds %
|
||||
NotificationConstants.IndeterminateProgressbarLoopDuration) /
|
||||
NotificationConstants.IndeterminateProgressbarLoopDuration);
|
||||
barL = Math.Max(elapsed - (1f / 3), 0f) / (2f / 3);
|
||||
barR = Math.Min(elapsed, 2f / 3) / (2f / 3);
|
||||
barL = MathF.Pow(barL, 3);
|
||||
barR = 1f - MathF.Pow(1f - barR, 3);
|
||||
this.prevProgressL = barL;
|
||||
this.prevProgressR = barR;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.prevProgressL = barL = 0f;
|
||||
this.prevProgressR = barR = 1f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
barL = 1f - (float)((effectiveExpiry - DateTime.Now).TotalMilliseconds /
|
||||
(effectiveExpiry - this.lastInterestTime).TotalMilliseconds);
|
||||
barR = 1f;
|
||||
this.prevProgressL = barL;
|
||||
this.prevProgressR = barR;
|
||||
}
|
||||
|
||||
barR = Math.Clamp(barR, 0f, 1f);
|
||||
|
||||
var windowPos = ImGui.GetWindowPos();
|
||||
var windowSize = ImGui.GetWindowSize();
|
||||
ImGui.PushClipRect(windowPos, windowPos + windowSize, false);
|
||||
ImGui.GetWindowDrawList().AddRectFilled(
|
||||
windowPos + new Vector2(
|
||||
windowSize.X * barL,
|
||||
windowSize.Y - NotificationConstants.ScaledExpiryProgressBarHeight),
|
||||
windowPos + windowSize with { X = windowSize.X * barR },
|
||||
ImGui.GetColorU32(this.Type.ToColor()));
|
||||
ImGui.PopClipRect();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,370 @@
|
|||
using System.Runtime.Loader;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Interface.Animation;
|
||||
using Dalamud.Interface.Animation.EasingFunctions;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Plugin.Internal.Types;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Interface.ImGuiNotification.Internal;
|
||||
|
||||
/// <summary>Represents an active notification.</summary>
|
||||
internal sealed partial class ActiveNotification : IActiveNotification
|
||||
{
|
||||
private readonly Notification underlyingNotification;
|
||||
|
||||
private readonly Easing showEasing;
|
||||
private readonly Easing hideEasing;
|
||||
private readonly Easing progressEasing;
|
||||
private readonly Easing expandoEasing;
|
||||
|
||||
/// <summary>Gets the time of starting to count the timer for the expiration.</summary>
|
||||
private DateTime lastInterestTime;
|
||||
|
||||
/// <summary>Gets the extended expiration time from <see cref="ExtendBy"/>.</summary>
|
||||
private DateTime extendedExpiry;
|
||||
|
||||
/// <summary>The icon texture to use if specified; otherwise, icon will be used from <see cref="Icon"/>.</summary>
|
||||
private Task<IDalamudTextureWrap>? iconTextureWrap;
|
||||
|
||||
/// <summary>The plugin that initiated this notification.</summary>
|
||||
private LocalPlugin? initiatorPlugin;
|
||||
|
||||
/// <summary>Whether <see cref="initiatorPlugin"/> has been unloaded.</summary>
|
||||
private bool isInitiatorUnloaded;
|
||||
|
||||
/// <summary>The progress before for the progress bar animation with <see cref="progressEasing"/>.</summary>
|
||||
private float progressBefore;
|
||||
|
||||
/// <summary>Used for calculating correct dismissal progressbar animation (left edge).</summary>
|
||||
private float prevProgressL;
|
||||
|
||||
/// <summary>Used for calculating correct dismissal progressbar animation (right edge).</summary>
|
||||
private float prevProgressR;
|
||||
|
||||
/// <summary>New progress value to be updated on next call to <see cref="UpdateOrDisposeInternal"/>.</summary>
|
||||
private float? newProgress;
|
||||
|
||||
/// <summary>New minimized value to be updated on next call to <see cref="UpdateOrDisposeInternal"/>.</summary>
|
||||
private bool? newMinimized;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="ActiveNotification"/> class.</summary>
|
||||
/// <param name="underlyingNotification">The underlying notification.</param>
|
||||
/// <param name="initiatorPlugin">The initiator plugin. Use <c>null</c> if originated by Dalamud.</param>
|
||||
public ActiveNotification(Notification underlyingNotification, LocalPlugin? initiatorPlugin)
|
||||
{
|
||||
this.underlyingNotification = underlyingNotification with { };
|
||||
this.initiatorPlugin = initiatorPlugin;
|
||||
this.showEasing = new InCubic(NotificationConstants.ShowAnimationDuration);
|
||||
this.hideEasing = new OutCubic(NotificationConstants.HideAnimationDuration);
|
||||
this.progressEasing = new InOutCubic(NotificationConstants.ProgressChangeAnimationDuration);
|
||||
this.expandoEasing = new InOutCubic(NotificationConstants.ExpandoAnimationDuration);
|
||||
this.CreatedAt = this.lastInterestTime = this.extendedExpiry = DateTime.Now;
|
||||
|
||||
this.showEasing.Start();
|
||||
this.progressEasing.Start();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public long Id { get; } = IActiveNotification.CreateNewId();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DateTime CreatedAt { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Content
|
||||
{
|
||||
get => this.underlyingNotification.Content;
|
||||
set => this.underlyingNotification.Content = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? Title
|
||||
{
|
||||
get => this.underlyingNotification.Title;
|
||||
set => this.underlyingNotification.Title = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool RespectUiHidden
|
||||
{
|
||||
get => this.underlyingNotification.RespectUiHidden;
|
||||
set => this.underlyingNotification.RespectUiHidden = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? MinimizedText
|
||||
{
|
||||
get => this.underlyingNotification.MinimizedText;
|
||||
set => this.underlyingNotification.MinimizedText = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public NotificationType Type
|
||||
{
|
||||
get => this.underlyingNotification.Type;
|
||||
set => this.underlyingNotification.Type = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public INotificationIcon? Icon
|
||||
{
|
||||
get => this.underlyingNotification.Icon;
|
||||
set => this.underlyingNotification.Icon = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DateTime HardExpiry
|
||||
{
|
||||
get => this.underlyingNotification.HardExpiry;
|
||||
set
|
||||
{
|
||||
if (this.underlyingNotification.HardExpiry == value)
|
||||
return;
|
||||
this.underlyingNotification.HardExpiry = value;
|
||||
this.lastInterestTime = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public TimeSpan InitialDuration
|
||||
{
|
||||
get => this.underlyingNotification.InitialDuration;
|
||||
set
|
||||
{
|
||||
this.underlyingNotification.InitialDuration = value;
|
||||
this.lastInterestTime = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public TimeSpan ExtensionDurationSinceLastInterest
|
||||
{
|
||||
get => this.underlyingNotification.ExtensionDurationSinceLastInterest;
|
||||
set
|
||||
{
|
||||
this.underlyingNotification.ExtensionDurationSinceLastInterest = value;
|
||||
this.lastInterestTime = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DateTime EffectiveExpiry { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public NotificationDismissReason? DismissReason { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool ShowIndeterminateIfNoExpiry
|
||||
{
|
||||
get => this.underlyingNotification.ShowIndeterminateIfNoExpiry;
|
||||
set => this.underlyingNotification.ShowIndeterminateIfNoExpiry = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Minimized
|
||||
{
|
||||
get => this.newMinimized ?? this.underlyingNotification.Minimized;
|
||||
set => this.newMinimized = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool UserDismissable
|
||||
{
|
||||
get => this.underlyingNotification.UserDismissable;
|
||||
set => this.underlyingNotification.UserDismissable = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float Progress
|
||||
{
|
||||
get => this.newProgress ?? this.underlyingNotification.Progress;
|
||||
set => this.newProgress = value;
|
||||
}
|
||||
|
||||
/// <summary>Gets the eased progress.</summary>
|
||||
private float ProgressEased
|
||||
{
|
||||
get
|
||||
{
|
||||
var underlyingProgress = this.underlyingNotification.Progress;
|
||||
if (Math.Abs(underlyingProgress - this.progressBefore) < 0.000001f || this.progressEasing.IsDone)
|
||||
return underlyingProgress;
|
||||
|
||||
var state = Math.Clamp((float)this.progressEasing.Value, 0f, 1f);
|
||||
return this.progressBefore + (state * (underlyingProgress - this.progressBefore));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the string for the initiator field.</summary>
|
||||
private string InitiatorString =>
|
||||
this.initiatorPlugin is not { } plugin
|
||||
? NotificationConstants.DefaultInitiator
|
||||
: this.isInitiatorUnloaded
|
||||
? NotificationConstants.UnloadedInitiatorNameFormat.Format(plugin.Name)
|
||||
: plugin.Name;
|
||||
|
||||
/// <summary>Gets the effective text to display when minimized.</summary>
|
||||
private string EffectiveMinimizedText => (this.MinimizedText ?? this.Content).ReplaceLineEndings(" ");
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void DismissNow() => this.DismissNow(NotificationDismissReason.Programmatical);
|
||||
|
||||
/// <summary>Dismisses this notification. Multiple calls will be ignored.</summary>
|
||||
/// <param name="reason">The reason of dismissal.</param>
|
||||
public void DismissNow(NotificationDismissReason reason)
|
||||
{
|
||||
if (this.DismissReason is not null)
|
||||
return;
|
||||
|
||||
this.DismissReason = reason;
|
||||
this.hideEasing.Start();
|
||||
this.InvokeDismiss();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ExtendBy(TimeSpan extension)
|
||||
{
|
||||
var newExpiry = DateTime.Now + extension;
|
||||
if (this.extendedExpiry < newExpiry)
|
||||
this.extendedExpiry = newExpiry;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetIconTexture(IDalamudTextureWrap? textureWrap)
|
||||
{
|
||||
this.SetIconTexture(textureWrap is null ? null : Task.FromResult(textureWrap));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetIconTexture(Task<IDalamudTextureWrap?>? textureWrapTask)
|
||||
{
|
||||
if (this.DismissReason is not null)
|
||||
{
|
||||
textureWrapTask?.ToContentDisposedTask(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// After replacing, if the old texture is not the old texture, then dispose the old texture.
|
||||
if (Interlocked.Exchange(ref this.iconTextureWrap, textureWrapTask) is { } wrapTaskToDispose &&
|
||||
wrapTaskToDispose != textureWrapTask)
|
||||
{
|
||||
wrapTaskToDispose.ToContentDisposedTask(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes non-Dalamud invocation targets from events.</summary>
|
||||
/// <remarks>
|
||||
/// This is done to prevent references of plugins being unloaded from outliving the plugin itself.
|
||||
/// Anything that can contain plugin-provided types and functions count, which effectively means that events and
|
||||
/// interface/object-typed fields need to be scrubbed.
|
||||
/// As a notification can be marked as non-user-dismissable, in which case after removing event handlers there will
|
||||
/// be no way to remove the notification, we force the notification to become user-dismissable, and reset the expiry
|
||||
/// to the default duration on unload.
|
||||
/// </remarks>
|
||||
internal void RemoveNonDalamudInvocations()
|
||||
{
|
||||
var dalamudContext = AssemblyLoadContext.GetLoadContext(typeof(NotificationManager).Assembly);
|
||||
this.Dismiss = RemoveNonDalamudInvocationsCore(this.Dismiss);
|
||||
this.Click = RemoveNonDalamudInvocationsCore(this.Click);
|
||||
this.DrawActions = RemoveNonDalamudInvocationsCore(this.DrawActions);
|
||||
|
||||
if (this.Icon is { } previousIcon && !IsOwnedByDalamud(previousIcon.GetType()))
|
||||
this.Icon = null;
|
||||
|
||||
this.isInitiatorUnloaded = true;
|
||||
this.UserDismissable = true;
|
||||
this.ExtensionDurationSinceLastInterest = NotificationConstants.DefaultDuration;
|
||||
|
||||
var newMaxExpiry = DateTime.Now + NotificationConstants.DefaultDuration;
|
||||
if (this.EffectiveExpiry > newMaxExpiry)
|
||||
this.HardExpiry = newMaxExpiry;
|
||||
|
||||
return;
|
||||
|
||||
bool IsOwnedByDalamud(Type t) => AssemblyLoadContext.GetLoadContext(t.Assembly) == dalamudContext;
|
||||
|
||||
T? RemoveNonDalamudInvocationsCore<T>(T? @delegate) where T : Delegate
|
||||
{
|
||||
if (@delegate is null)
|
||||
return null;
|
||||
|
||||
foreach (var il in @delegate.GetInvocationList())
|
||||
{
|
||||
if (il.Target is { } target && !IsOwnedByDalamud(target.GetType()))
|
||||
@delegate = (T)Delegate.Remove(@delegate, il);
|
||||
}
|
||||
|
||||
return @delegate;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Updates the state of this notification, and release the relevant resource if this notification is no
|
||||
/// longer in use.</summary>
|
||||
/// <returns><c>true</c> if the notification is over and relevant resources are released.</returns>
|
||||
/// <remarks>Intended to be called from the main thread only.</remarks>
|
||||
internal bool UpdateOrDisposeInternal()
|
||||
{
|
||||
this.showEasing.Update();
|
||||
this.hideEasing.Update();
|
||||
this.progressEasing.Update();
|
||||
if (this.expandoEasing.IsRunning)
|
||||
{
|
||||
this.expandoEasing.Update();
|
||||
if (this.expandoEasing.IsDone)
|
||||
this.expandoEasing.Stop();
|
||||
}
|
||||
|
||||
if (this.newProgress is { } newProgressValue)
|
||||
{
|
||||
if (Math.Abs(this.underlyingNotification.Progress - newProgressValue) > float.Epsilon)
|
||||
{
|
||||
this.progressBefore = this.ProgressEased;
|
||||
this.underlyingNotification.Progress = newProgressValue;
|
||||
this.progressEasing.Restart();
|
||||
this.progressEasing.Update();
|
||||
}
|
||||
|
||||
this.newProgress = null;
|
||||
}
|
||||
|
||||
if (this.newMinimized is { } newMinimizedValue)
|
||||
{
|
||||
if (this.underlyingNotification.Minimized != newMinimizedValue)
|
||||
{
|
||||
this.underlyingNotification.Minimized = newMinimizedValue;
|
||||
this.expandoEasing.Restart();
|
||||
this.expandoEasing.Update();
|
||||
}
|
||||
|
||||
this.newMinimized = null;
|
||||
}
|
||||
|
||||
if (!this.hideEasing.IsRunning || !this.hideEasing.IsDone)
|
||||
return false;
|
||||
|
||||
this.DisposeInternal();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Clears the resources associated with this instance of <see cref="ActiveNotification"/>.</summary>
|
||||
internal void DisposeInternal()
|
||||
{
|
||||
if (Interlocked.Exchange(ref this.iconTextureWrap, null) is { } wrapTaskToDispose)
|
||||
wrapTaskToDispose.ToContentDisposedTask(true);
|
||||
this.Dismiss = null;
|
||||
this.Click = null;
|
||||
this.DrawActions = null;
|
||||
this.initiatorPlugin = null;
|
||||
}
|
||||
|
||||
private void LogEventInvokeError(Exception exception, string message) =>
|
||||
Log.Error(
|
||||
exception,
|
||||
$"[{nameof(ActiveNotification)}:{this.initiatorPlugin?.Name ?? NotificationConstants.DefaultInitiator}] {message}");
|
||||
}
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
using System.Numerics;
|
||||
|
||||
using CheapLoc;
|
||||
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Interface.Utility;
|
||||
|
||||
namespace Dalamud.Interface.ImGuiNotification.Internal;
|
||||
|
||||
/// <summary>Constants for drawing notification windows.</summary>
|
||||
internal static class NotificationConstants
|
||||
{
|
||||
// .............................[..]
|
||||
// ..when.......................[XX]
|
||||
// .. ..
|
||||
// ..[i]..title title title title ..
|
||||
// .. by this_plugin ..
|
||||
// .. ..
|
||||
// .. body body body body ..
|
||||
// .. some more wrapped body ..
|
||||
// .. ..
|
||||
// .. action buttons ..
|
||||
// .................................
|
||||
|
||||
/// <summary>The string to measure size of, to decide the width of notification windows.</summary>
|
||||
/// <remarks>Probably not worth localizing.</remarks>
|
||||
public const string NotificationWidthMeasurementString =
|
||||
"The width of this text will decide the width\n" +
|
||||
"of the notification window.";
|
||||
|
||||
/// <summary>The ratio of maximum notification window width w.r.t. main viewport width.</summary>
|
||||
public const float MaxNotificationWindowWidthWrtMainViewportWidth = 2f / 3;
|
||||
|
||||
/// <summary>The size of the icon.</summary>
|
||||
public const float IconSize = 32;
|
||||
|
||||
/// <summary>The background opacity of a notification window.</summary>
|
||||
public const float BackgroundOpacity = 0.82f;
|
||||
|
||||
/// <summary>The duration of indeterminate progress bar loop in milliseconds.</summary>
|
||||
public const float IndeterminateProgressbarLoopDuration = 2000f;
|
||||
|
||||
/// <summary>The duration of the progress wave animation in milliseconds.</summary>
|
||||
public const float ProgressWaveLoopDuration = 2000f;
|
||||
|
||||
/// <summary>The time ratio of a progress wave loop where the animation is idle.</summary>
|
||||
public const float ProgressWaveIdleTimeRatio = 0.5f;
|
||||
|
||||
/// <summary>The time ratio of a non-idle portion of the progress wave loop where the color is the most opaque.
|
||||
/// </summary>
|
||||
public const float ProgressWaveLoopMaxColorTimeRatio = 0.7f;
|
||||
|
||||
/// <summary>Default duration of the notification.</summary>
|
||||
public static readonly TimeSpan DefaultDuration = TimeSpan.FromSeconds(3);
|
||||
|
||||
/// <summary>Duration of show animation.</summary>
|
||||
public static readonly TimeSpan ShowAnimationDuration = TimeSpan.FromMilliseconds(300);
|
||||
|
||||
/// <summary>Duration of hide animation.</summary>
|
||||
public static readonly TimeSpan HideAnimationDuration = TimeSpan.FromMilliseconds(300);
|
||||
|
||||
/// <summary>Duration of progress change animation.</summary>
|
||||
public static readonly TimeSpan ProgressChangeAnimationDuration = TimeSpan.FromMilliseconds(200);
|
||||
|
||||
/// <summary>Duration of expando animation.</summary>
|
||||
public static readonly TimeSpan ExpandoAnimationDuration = TimeSpan.FromMilliseconds(300);
|
||||
|
||||
/// <summary>Text color for the rectangular border when the notification is focused.</summary>
|
||||
public static readonly Vector4 FocusBorderColor = new(0.4f, 0.4f, 0.4f, 1f);
|
||||
|
||||
/// <summary>Text color for the when.</summary>
|
||||
public static readonly Vector4 WhenTextColor = new(0.8f, 0.8f, 0.8f, 1f);
|
||||
|
||||
/// <summary>Text color for the close button [X].</summary>
|
||||
public static readonly Vector4 CloseTextColor = new(0.8f, 0.8f, 0.8f, 1f);
|
||||
|
||||
/// <summary>Text color for the title.</summary>
|
||||
public static readonly Vector4 TitleTextColor = new(1f, 1f, 1f, 1f);
|
||||
|
||||
/// <summary>Text color for the name of the initiator.</summary>
|
||||
public static readonly Vector4 BlameTextColor = new(0.8f, 0.8f, 0.8f, 1f);
|
||||
|
||||
/// <summary>Text color for the body.</summary>
|
||||
public static readonly Vector4 BodyTextColor = new(0.9f, 0.9f, 0.9f, 1f);
|
||||
|
||||
/// <summary>Color for the background progress bar (determinate progress only).</summary>
|
||||
public static readonly Vector4 BackgroundProgressColorMax = new(1f, 1f, 1f, 0.1f);
|
||||
|
||||
/// <summary>Color for the background progress bar (determinate progress only).</summary>
|
||||
public static readonly Vector4 BackgroundProgressColorMin = new(1f, 1f, 1f, 0.05f);
|
||||
|
||||
/// <summary>Gets the scaled padding of the window (dot(.) in the above diagram).</summary>
|
||||
public static float ScaledWindowPadding => MathF.Round(16 * ImGuiHelpers.GlobalScale);
|
||||
|
||||
/// <summary>Gets the distance from the right bottom border of the viewport
|
||||
/// to the right bottom border of a notification window.
|
||||
/// </summary>
|
||||
public static float ScaledViewportEdgeMargin => MathF.Round(20 * ImGuiHelpers.GlobalScale);
|
||||
|
||||
/// <summary>Gets the scaled gap between two notification windows.</summary>
|
||||
public static float ScaledWindowGap => MathF.Round(10 * ImGuiHelpers.GlobalScale);
|
||||
|
||||
/// <summary>Gets the scaled gap between components.</summary>
|
||||
public static float ScaledComponentGap => MathF.Round(5 * ImGuiHelpers.GlobalScale);
|
||||
|
||||
/// <summary>Gets the scaled size of the icon.</summary>
|
||||
public static float ScaledIconSize => MathF.Round(IconSize * ImGuiHelpers.GlobalScale);
|
||||
|
||||
/// <summary>Gets the height of the expiry progress bar.</summary>
|
||||
public static float ScaledExpiryProgressBarHeight => MathF.Round(3 * ImGuiHelpers.GlobalScale);
|
||||
|
||||
/// <summary>Gets the thickness of the focus indicator rectangle.</summary>
|
||||
public static float FocusIndicatorThickness => MathF.Round(3 * ImGuiHelpers.GlobalScale);
|
||||
|
||||
/// <summary>Gets the string to show in place of this_plugin if the notification is shown by Dalamud.</summary>
|
||||
public static string DefaultInitiator => Loc.Localize("NotificationConstants.DefaultInitiator", "Dalamud");
|
||||
|
||||
/// <summary>Gets the string format of the initiator name field, if the initiator is unloaded.</summary>
|
||||
public static string UnloadedInitiatorNameFormat =>
|
||||
Loc.Localize("NotificationConstants.UnloadedInitiatorNameFormat", "{0} (unloaded)");
|
||||
|
||||
/// <summary>Gets the color corresponding to the notification type.</summary>
|
||||
/// <param name="type">The notification type.</param>
|
||||
/// <returns>The corresponding color.</returns>
|
||||
public static Vector4 ToColor(this NotificationType type) => type switch
|
||||
{
|
||||
NotificationType.None => ImGuiColors.DalamudWhite,
|
||||
NotificationType.Success => ImGuiColors.HealerGreen,
|
||||
NotificationType.Warning => ImGuiColors.DalamudOrange,
|
||||
NotificationType.Error => ImGuiColors.DalamudRed,
|
||||
NotificationType.Info => ImGuiColors.TankBlue,
|
||||
_ => ImGuiColors.DalamudWhite,
|
||||
};
|
||||
|
||||
/// <summary>Gets the <see cref="FontAwesomeIcon"/> char value corresponding to the notification type.</summary>
|
||||
/// <param name="type">The notification type.</param>
|
||||
/// <returns>The corresponding char, or null.</returns>
|
||||
public static char ToChar(this NotificationType type) => type switch
|
||||
{
|
||||
NotificationType.None => '\0',
|
||||
NotificationType.Success => FontAwesomeIcon.CheckCircle.ToIconChar(),
|
||||
NotificationType.Warning => FontAwesomeIcon.ExclamationCircle.ToIconChar(),
|
||||
NotificationType.Error => FontAwesomeIcon.TimesCircle.ToIconChar(),
|
||||
NotificationType.Info => FontAwesomeIcon.InfoCircle.ToIconChar(),
|
||||
_ => '\0',
|
||||
};
|
||||
|
||||
/// <summary>Gets the localized title string corresponding to the notification type.</summary>
|
||||
/// <param name="type">The notification type.</param>
|
||||
/// <returns>The corresponding title.</returns>
|
||||
public static string? ToTitle(this NotificationType type) => type switch
|
||||
{
|
||||
NotificationType.None => null,
|
||||
NotificationType.Success => Loc.Localize("NotificationConstants.Title.Success", "Success"),
|
||||
NotificationType.Warning => Loc.Localize("NotificationConstants.Title.Warning", "Warning"),
|
||||
NotificationType.Error => Loc.Localize("NotificationConstants.Title.Error", "Error"),
|
||||
NotificationType.Info => Loc.Localize("NotificationConstants.Title.Info", "Info"),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
using System.IO;
|
||||
using System.Numerics;
|
||||
|
||||
using Dalamud.Interface.Internal;
|
||||
|
||||
namespace Dalamud.Interface.ImGuiNotification.Internal.NotificationIcon;
|
||||
|
||||
/// <summary>Represents the use of a texture from a file as the icon of a notification.</summary>
|
||||
/// <remarks>If there was no texture loaded for any reason, the plugin icon will be displayed instead.</remarks>
|
||||
internal class FilePathNotificationIcon : INotificationIcon
|
||||
{
|
||||
private readonly FileInfo fileInfo;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="FilePathNotificationIcon"/> class.</summary>
|
||||
/// <param name="filePath">The path to a .tex file inside the game resources.</param>
|
||||
public FilePathNotificationIcon(string filePath) => this.fileInfo = new(filePath);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool DrawIcon(Vector2 minCoord, Vector2 maxCoord, Vector4 color) =>
|
||||
NotificationUtilities.DrawIconFrom(
|
||||
minCoord,
|
||||
maxCoord,
|
||||
Service<TextureManager>.Get().GetTextureFromFile(this.fileInfo));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj) =>
|
||||
obj is FilePathNotificationIcon r && r.fileInfo.FullName == this.fileInfo.FullName;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode() => HashCode.Combine(this.GetType().GetHashCode(), this.fileInfo.FullName);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString() => $"{nameof(FilePathNotificationIcon)}({this.fileInfo.FullName})";
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace Dalamud.Interface.ImGuiNotification.Internal.NotificationIcon;
|
||||
|
||||
/// <summary>Represents the use of <see cref="FontAwesomeIcon"/> as the icon of a notification.</summary>
|
||||
internal class FontAwesomeIconNotificationIcon : INotificationIcon
|
||||
{
|
||||
private readonly char iconChar;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="FontAwesomeIconNotificationIcon"/> class.</summary>
|
||||
/// <param name="iconChar">The character.</param>
|
||||
public FontAwesomeIconNotificationIcon(FontAwesomeIcon iconChar) => this.iconChar = (char)iconChar;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool DrawIcon(Vector2 minCoord, Vector2 maxCoord, Vector4 color) =>
|
||||
NotificationUtilities.DrawIconFrom(
|
||||
minCoord,
|
||||
maxCoord,
|
||||
this.iconChar,
|
||||
Service<NotificationManager>.Get().IconFontAwesomeFontHandle,
|
||||
color);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj) => obj is FontAwesomeIconNotificationIcon r && r.iconChar == this.iconChar;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode() => HashCode.Combine(this.GetType().GetHashCode(), this.iconChar);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString() => $"{nameof(FontAwesomeIconNotificationIcon)}({this.iconChar})";
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
using System.Numerics;
|
||||
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace Dalamud.Interface.ImGuiNotification.Internal.NotificationIcon;
|
||||
|
||||
/// <summary>Represents the use of a game-shipped texture as the icon of a notification.</summary>
|
||||
/// <remarks>If there was no texture loaded for any reason, the plugin icon will be displayed instead.</remarks>
|
||||
internal class GamePathNotificationIcon : INotificationIcon
|
||||
{
|
||||
private readonly string gamePath;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="GamePathNotificationIcon"/> class.</summary>
|
||||
/// <param name="gamePath">The path to a .tex file inside the game resources.</param>
|
||||
/// <remarks>Use <see cref="ITextureProvider.GetIconPath"/> to get the game path from icon IDs.</remarks>
|
||||
public GamePathNotificationIcon(string gamePath) => this.gamePath = gamePath;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool DrawIcon(Vector2 minCoord, Vector2 maxCoord, Vector4 color) =>
|
||||
NotificationUtilities.DrawIconFrom(
|
||||
minCoord,
|
||||
maxCoord,
|
||||
Service<TextureManager>.Get().GetTextureFromGame(this.gamePath));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj) => obj is GamePathNotificationIcon r && r.gamePath == this.gamePath;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode() => HashCode.Combine(this.GetType().GetHashCode(), this.gamePath);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString() => $"{nameof(GamePathNotificationIcon)}({this.gamePath})";
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
using System.Numerics;
|
||||
|
||||
using Dalamud.Game.Text;
|
||||
|
||||
namespace Dalamud.Interface.ImGuiNotification.Internal.NotificationIcon;
|
||||
|
||||
/// <summary>Represents the use of <see cref="SeIconChar"/> as the icon of a notification.</summary>
|
||||
internal class SeIconCharNotificationIcon : INotificationIcon
|
||||
{
|
||||
private readonly SeIconChar iconChar;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="SeIconCharNotificationIcon"/> class.</summary>
|
||||
/// <param name="c">The character.</param>
|
||||
public SeIconCharNotificationIcon(SeIconChar c) => this.iconChar = c;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool DrawIcon(Vector2 minCoord, Vector2 maxCoord, Vector4 color) =>
|
||||
NotificationUtilities.DrawIconFrom(
|
||||
minCoord,
|
||||
maxCoord,
|
||||
(char)this.iconChar,
|
||||
Service<NotificationManager>.Get().IconAxisFontHandle,
|
||||
color);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj) => obj is SeIconCharNotificationIcon r && r.iconChar == this.iconChar;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode() => HashCode.Combine(this.GetType().GetHashCode(), this.iconChar);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString() => $"{nameof(SeIconCharNotificationIcon)}({this.iconChar})";
|
||||
}
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Interface.GameFonts;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Internal.Types;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.ImGuiNotification.Internal;
|
||||
|
||||
/// <summary>Class handling notifications/toasts in ImGui.</summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class NotificationManager : INotificationManager, IServiceType, IDisposable
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly GameGui gameGui = Service<GameGui>.Get();
|
||||
|
||||
private readonly List<ActiveNotification> notifications = new();
|
||||
private readonly ConcurrentBag<ActiveNotification> pendingNotifications = new();
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private NotificationManager(FontAtlasFactory fontAtlasFactory)
|
||||
{
|
||||
this.PrivateAtlas = fontAtlasFactory.CreateFontAtlas(
|
||||
nameof(NotificationManager),
|
||||
FontAtlasAutoRebuildMode.Async);
|
||||
this.IconAxisFontHandle =
|
||||
this.PrivateAtlas.NewGameFontHandle(new(GameFontFamily.Axis, NotificationConstants.IconSize));
|
||||
this.IconFontAwesomeFontHandle =
|
||||
this.PrivateAtlas.NewDelegateFontHandle(
|
||||
e => e.OnPreBuild(
|
||||
tk => tk.AddFontAwesomeIconFont(new() { SizePx = NotificationConstants.IconSize })));
|
||||
}
|
||||
|
||||
/// <summary>Gets the handle to AXIS fonts, sized for use as an icon.</summary>
|
||||
public IFontHandle IconAxisFontHandle { get; }
|
||||
|
||||
/// <summary>Gets the handle to FontAwesome fonts, sized for use as an icon.</summary>
|
||||
public IFontHandle IconFontAwesomeFontHandle { get; }
|
||||
|
||||
/// <summary>Gets the private atlas for use with notification windows.</summary>
|
||||
private IFontAtlas PrivateAtlas { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.PrivateAtlas.Dispose();
|
||||
foreach (var n in this.pendingNotifications)
|
||||
n.DisposeInternal();
|
||||
foreach (var n in this.notifications)
|
||||
n.DisposeInternal();
|
||||
this.pendingNotifications.Clear();
|
||||
this.notifications.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IActiveNotification AddNotification(Notification notification)
|
||||
{
|
||||
var an = new ActiveNotification(notification, null);
|
||||
this.pendingNotifications.Add(an);
|
||||
return an;
|
||||
}
|
||||
|
||||
/// <summary>Adds a notification originating from a plugin.</summary>
|
||||
/// <param name="notification">The notification.</param>
|
||||
/// <param name="plugin">The source plugin.</param>
|
||||
/// <returns>The added notification.</returns>
|
||||
public IActiveNotification AddNotification(Notification notification, LocalPlugin plugin)
|
||||
{
|
||||
var an = new ActiveNotification(notification, plugin);
|
||||
this.pendingNotifications.Add(an);
|
||||
return an;
|
||||
}
|
||||
|
||||
/// <summary>Add a notification to the notification queue.</summary>
|
||||
/// <param name="content">The content of the notification.</param>
|
||||
/// <param name="title">The title of the notification.</param>
|
||||
/// <param name="type">The type of the notification.</param>
|
||||
public void AddNotification(
|
||||
string content,
|
||||
string? title = null,
|
||||
NotificationType type = NotificationType.None) =>
|
||||
this.AddNotification(
|
||||
new()
|
||||
{
|
||||
Content = content,
|
||||
Title = title,
|
||||
Type = type,
|
||||
});
|
||||
|
||||
/// <summary>Draw all currently queued notifications.</summary>
|
||||
public void Draw()
|
||||
{
|
||||
var viewportSize = ImGuiHelpers.MainViewport.WorkSize;
|
||||
var height = 0f;
|
||||
var uiHidden = this.gameGui.GameUiHidden;
|
||||
|
||||
while (this.pendingNotifications.TryTake(out var newNotification))
|
||||
this.notifications.Add(newNotification);
|
||||
|
||||
var width = ImGui.CalcTextSize(NotificationConstants.NotificationWidthMeasurementString).X;
|
||||
width += NotificationConstants.ScaledWindowPadding * 3;
|
||||
width += NotificationConstants.ScaledIconSize;
|
||||
width = Math.Min(width, viewportSize.X * NotificationConstants.MaxNotificationWindowWidthWrtMainViewportWidth);
|
||||
|
||||
this.notifications.RemoveAll(static x => x.UpdateOrDisposeInternal());
|
||||
foreach (var tn in this.notifications)
|
||||
{
|
||||
if (uiHidden && tn.RespectUiHidden)
|
||||
continue;
|
||||
height += tn.Draw(width, height) + NotificationConstants.ScaledWindowGap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Plugin-scoped version of a <see cref="NotificationManager"/> service.</summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<INotificationManager>]
|
||||
#pragma warning restore SA1015
|
||||
internal class NotificationManagerPluginScoped : INotificationManager, IServiceType, IDisposable
|
||||
{
|
||||
private readonly LocalPlugin localPlugin;
|
||||
private readonly ConcurrentDictionary<IActiveNotification, int> notifications = new();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly NotificationManager notificationManagerService = Service<NotificationManager>.Get();
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private NotificationManagerPluginScoped(LocalPlugin localPlugin) =>
|
||||
this.localPlugin = localPlugin;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IActiveNotification AddNotification(Notification notification)
|
||||
{
|
||||
var an = this.notificationManagerService.AddNotification(notification, this.localPlugin);
|
||||
_ = this.notifications.TryAdd(an, 0);
|
||||
an.Dismiss += a => this.notifications.TryRemove(a.Notification, out _);
|
||||
return an;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
while (!this.notifications.IsEmpty)
|
||||
{
|
||||
foreach (var n in this.notifications.Keys)
|
||||
{
|
||||
this.notifications.TryRemove(n, out _);
|
||||
((ActiveNotification)n).RemoveNonDalamudInvocations();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Dalamud/Interface/ImGuiNotification/Notification.cs
Normal file
52
Dalamud/Interface/ImGuiNotification/Notification.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
|
||||
namespace Dalamud.Interface.ImGuiNotification;
|
||||
|
||||
/// <summary>Represents a blueprint for a notification.</summary>
|
||||
public sealed record Notification : INotification
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the default value for <see cref="InitialDuration"/> and <see cref="ExtensionDurationSinceLastInterest"/>.
|
||||
/// </summary>
|
||||
public static TimeSpan DefaultDuration => NotificationConstants.DefaultDuration;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Content { get; set; } = string.Empty;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? Title { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? MinimizedText { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public NotificationType Type { get; set; } = NotificationType.None;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public INotificationIcon? Icon { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DateTime HardExpiry { get; set; } = DateTime.MaxValue;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public TimeSpan InitialDuration { get; set; } = DefaultDuration;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public TimeSpan ExtensionDurationSinceLastInterest { get; set; } = DefaultDuration;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool ShowIndeterminateIfNoExpiry { get; set; } = true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool RespectUiHidden { get; set; } = true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Minimized { get; set; } = true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool UserDismissable { get; set; } = true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float Progress { get; set; } = 1f;
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
namespace Dalamud.Interface.ImGuiNotification;
|
||||
|
||||
/// <summary>Specifies the reason of dismissal for a notification.</summary>
|
||||
public enum NotificationDismissReason
|
||||
{
|
||||
/// <summary>The notification is dismissed because the expiry specified from <see cref="INotification.HardExpiry"/> is
|
||||
/// met.</summary>
|
||||
Timeout = 1,
|
||||
|
||||
/// <summary>The notification is dismissed because the user clicked on the close button on a notification window.
|
||||
/// </summary>
|
||||
Manual = 2,
|
||||
|
||||
/// <summary>The notification is dismissed from calling <see cref="IActiveNotification.DismissNow"/>.</summary>
|
||||
Programmatical = 3,
|
||||
}
|
||||
149
Dalamud/Interface/ImGuiNotification/NotificationUtilities.cs
Normal file
149
Dalamud/Interface/ImGuiNotification/NotificationUtilities.cs
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Internal.Windows;
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Plugin.Internal.Types;
|
||||
using Dalamud.Storage.Assets;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.ImGuiNotification;
|
||||
|
||||
/// <summary>Utilities for implementing stuff under <see cref="ImGuiNotification"/>.</summary>
|
||||
public static class NotificationUtilities
|
||||
{
|
||||
/// <inheritdoc cref="INotificationIcon.From(SeIconChar)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static INotificationIcon ToNotificationIcon(this SeIconChar iconChar) =>
|
||||
INotificationIcon.From(iconChar);
|
||||
|
||||
/// <inheritdoc cref="INotificationIcon.From(FontAwesomeIcon)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static INotificationIcon ToNotificationIcon(this FontAwesomeIcon iconChar) =>
|
||||
INotificationIcon.From(iconChar);
|
||||
|
||||
/// <inheritdoc cref="INotificationIcon.FromFile(string)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static INotificationIcon ToNotificationIcon(this FileInfo fileInfo) =>
|
||||
INotificationIcon.FromFile(fileInfo.FullName);
|
||||
|
||||
/// <summary>Draws an icon from an <see cref="IFontHandle"/> and a <see cref="char"/>.</summary>
|
||||
/// <param name="minCoord">The coordinates of the top left of the icon area.</param>
|
||||
/// <param name="maxCoord">The coordinates of the bottom right of the icon area.</param>
|
||||
/// <param name="c">The icon character.</param>
|
||||
/// <param name="fontHandle">The font handle to use.</param>
|
||||
/// <param name="color">The foreground color.</param>
|
||||
/// <returns><c>true</c> if anything has been drawn.</returns>
|
||||
internal static unsafe bool DrawIconFrom(
|
||||
Vector2 minCoord,
|
||||
Vector2 maxCoord,
|
||||
char c,
|
||||
IFontHandle fontHandle,
|
||||
Vector4 color)
|
||||
{
|
||||
if (c is '\0' or char.MaxValue)
|
||||
return false;
|
||||
|
||||
var smallerDim = Math.Max(maxCoord.Y - minCoord.Y, maxCoord.X - minCoord.X);
|
||||
using (fontHandle.Push())
|
||||
{
|
||||
var font = ImGui.GetFont();
|
||||
var glyphPtr = (ImGuiHelpers.ImFontGlyphReal*)font.FindGlyphNoFallback(c).NativePtr;
|
||||
if (glyphPtr is null)
|
||||
return false;
|
||||
|
||||
ref readonly var glyph = ref *glyphPtr;
|
||||
var size = glyph.XY1 - glyph.XY0;
|
||||
var smallerSizeDim = Math.Min(size.X, size.Y);
|
||||
var scale = smallerSizeDim > smallerDim ? smallerDim / smallerSizeDim : 1f;
|
||||
size *= scale;
|
||||
var pos = ((minCoord + maxCoord) - size) / 2;
|
||||
pos += ImGui.GetWindowPos();
|
||||
ImGui.GetWindowDrawList().AddImage(
|
||||
font.ContainerAtlas.Textures[glyph.TextureIndex].TexID,
|
||||
pos,
|
||||
pos + size,
|
||||
glyph.UV0,
|
||||
glyph.UV1,
|
||||
ImGui.GetColorU32(color with { W = color.W * ImGui.GetStyle().Alpha }));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Draws an icon from an instance of <see cref="IDalamudTextureWrap"/>.</summary>
|
||||
/// <param name="minCoord">The coordinates of the top left of the icon area.</param>
|
||||
/// <param name="maxCoord">The coordinates of the bottom right of the icon area.</param>
|
||||
/// <param name="texture">The texture.</param>
|
||||
/// <returns><c>true</c> if anything has been drawn.</returns>
|
||||
internal static bool DrawIconFrom(Vector2 minCoord, Vector2 maxCoord, IDalamudTextureWrap? texture)
|
||||
{
|
||||
if (texture is null)
|
||||
return false;
|
||||
try
|
||||
{
|
||||
var handle = texture.ImGuiHandle;
|
||||
var size = texture.Size;
|
||||
if (size.X > maxCoord.X - minCoord.X)
|
||||
size *= (maxCoord.X - minCoord.X) / size.X;
|
||||
if (size.Y > maxCoord.Y - minCoord.Y)
|
||||
size *= (maxCoord.Y - minCoord.Y) / size.Y;
|
||||
ImGui.SetCursorPos(((minCoord + maxCoord) - size) / 2);
|
||||
ImGui.Image(handle, size);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Draws an icon from an instance of <see cref="Task{TResult}"/> that results in an
|
||||
/// <see cref="IDalamudTextureWrap"/>.</summary>
|
||||
/// <param name="minCoord">The coordinates of the top left of the icon area.</param>
|
||||
/// <param name="maxCoord">The coordinates of the bottom right of the icon area.</param>
|
||||
/// <param name="textureTask">The task that results in a texture.</param>
|
||||
/// <returns><c>true</c> if anything has been drawn.</returns>
|
||||
/// <remarks>Exceptions from the task will be treated as if no texture is provided.</remarks>
|
||||
internal static bool DrawIconFrom(Vector2 minCoord, Vector2 maxCoord, Task<IDalamudTextureWrap?>? textureTask) =>
|
||||
textureTask?.IsCompletedSuccessfully is true && DrawIconFrom(minCoord, maxCoord, textureTask.Result);
|
||||
|
||||
/// <summary>Draws an icon from an instance of <see cref="LocalPlugin"/>.</summary>
|
||||
/// <param name="minCoord">The coordinates of the top left of the icon area.</param>
|
||||
/// <param name="maxCoord">The coordinates of the bottom right of the icon area.</param>
|
||||
/// <param name="plugin">The plugin. Dalamud icon will be drawn if <c>null</c> is given.</param>
|
||||
/// <returns><c>true</c> if anything has been drawn.</returns>
|
||||
internal static bool DrawIconFrom(Vector2 minCoord, Vector2 maxCoord, LocalPlugin? plugin)
|
||||
{
|
||||
var dam = Service<DalamudAssetManager>.Get();
|
||||
if (plugin is null)
|
||||
return false;
|
||||
|
||||
if (!Service<PluginImageCache>.Get().TryGetIcon(
|
||||
plugin,
|
||||
plugin.Manifest,
|
||||
plugin.IsThirdParty,
|
||||
out var texture) || texture is null)
|
||||
{
|
||||
texture = dam.GetDalamudTextureWrap(DalamudAsset.DefaultIcon);
|
||||
}
|
||||
|
||||
return DrawIconFrom(minCoord, maxCoord, texture);
|
||||
}
|
||||
|
||||
/// <summary>Draws the Dalamud logo as an icon.</summary>
|
||||
/// <param name="minCoord">The coordinates of the top left of the icon area.</param>
|
||||
/// <param name="maxCoord">The coordinates of the bottom right of the icon area.</param>
|
||||
internal static void DrawIconFromDalamudLogo(Vector2 minCoord, Vector2 maxCoord)
|
||||
{
|
||||
var dam = Service<DalamudAssetManager>.Get();
|
||||
var texture = dam.GetDalamudTextureWrap(DalamudAsset.LogoSmall);
|
||||
DrawIconFrom(minCoord, maxCoord, texture);
|
||||
}
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ namespace Dalamud.Interface.Internal;
|
|||
/// This class handles CJK IME.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal sealed unsafe class DalamudIme : IDisposable, IServiceType
|
||||
internal sealed unsafe class DalamudIme : IInternalDisposableService
|
||||
{
|
||||
private const int CImGuiStbTextCreateUndoOffset = 0xB57A0;
|
||||
private const int CImGuiStbTextUndoOffset = 0xB59C0;
|
||||
|
|
@ -200,7 +200,7 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
|
|||
this.candidateStrings.Count != 0 || this.ShowPartialConversion || this.inputModeIcon != default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.interfaceManager.Draw -= this.Draw;
|
||||
this.ReleaseUnmanagedResources();
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ namespace Dalamud.Interface.Internal;
|
|||
/// This plugin implements all of the Dalamud interface separately, to allow for reloading of the interface and rapid prototyping.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class DalamudInterface : IDisposable, IServiceType
|
||||
internal class DalamudInterface : IInternalDisposableService
|
||||
{
|
||||
private const float CreditsDarkeningMaxAlpha = 0.8f;
|
||||
|
||||
|
|
@ -209,7 +209,7 @@ internal class DalamudInterface : IDisposable, IServiceType
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.interfaceManager.Draw -= this.OnDraw;
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ namespace Dalamud.Interface.Internal;
|
|||
/// </para>
|
||||
/// </remarks>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal sealed unsafe class ImGuiClipboardFunctionProvider : IServiceType, IDisposable
|
||||
internal sealed unsafe class ImGuiClipboardFunctionProvider : IInternalDisposableService
|
||||
{
|
||||
private static readonly ModuleLog Log = new(nameof(ImGuiClipboardFunctionProvider));
|
||||
private readonly nint clipboardUserDataOriginal;
|
||||
|
|
@ -75,7 +75,7 @@ internal sealed unsafe class ImGuiClipboardFunctionProvider : IServiceType, IDis
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
if (!this.clipboardUserData.IsAllocated)
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ namespace Dalamud.Interface.Internal;
|
|||
/// Change push_texture_id to only have one condition.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal sealed unsafe class ImGuiDrawListFixProvider : IServiceType, IDisposable
|
||||
internal sealed unsafe class ImGuiDrawListFixProvider : IInternalDisposableService
|
||||
{
|
||||
private const int CImGuiImDrawListAddPolyLineOffset = 0x589B0;
|
||||
private const int CImGuiImDrawListAddRectFilled = 0x59FD0;
|
||||
|
|
@ -69,7 +69,7 @@ internal sealed unsafe class ImGuiDrawListFixProvider : IServiceType, IDisposabl
|
|||
ImDrawFlags flags);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.hookImDrawListAddPolyline.Dispose();
|
||||
this.hookImDrawListAddRectFilled.Dispose();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
|
|
@ -14,6 +15,7 @@ using Dalamud.Game.ClientState.Keys;
|
|||
using Dalamud.Game.Internal.DXGI;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Hooking.WndProcHook;
|
||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||
using Dalamud.Interface.Internal.ManagedAsserts;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
|
|
@ -50,7 +52,7 @@ namespace Dalamud.Interface.Internal;
|
|||
/// This class manages interaction with the ImGui interface.
|
||||
/// </summary>
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
internal class InterfaceManager : IDisposable, IServiceType
|
||||
internal class InterfaceManager : IInternalDisposableService
|
||||
{
|
||||
/// <summary>
|
||||
/// The default font size, in points.
|
||||
|
|
@ -68,10 +70,13 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
[ServiceManager.ServiceDependency]
|
||||
private readonly WndProcHookManager wndProcHookManager = Service<WndProcHookManager>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly Framework framework = Service<Framework>.Get();
|
||||
|
||||
private readonly SwapChainVtableResolver address = new();
|
||||
private readonly Hook<SetCursorDelegate> setCursorHook;
|
||||
private RawDX11Scene? scene;
|
||||
|
||||
private Hook<SetCursorDelegate>? setCursorHook;
|
||||
private Hook<PresentDelegate>? presentHook;
|
||||
private Hook<ResizeBuffersDelegate>? resizeBuffersHook;
|
||||
|
||||
|
|
@ -86,8 +91,6 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
[ServiceManager.ServiceConstructor]
|
||||
private InterfaceManager()
|
||||
{
|
||||
this.setCursorHook = Hook<SetCursorDelegate>.FromImport(
|
||||
null, "user32.dll", "SetCursor", 0, this.SetCursorDetour);
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
|
|
@ -232,25 +235,45 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
if (Service<Framework>.GetNullable() is { } framework)
|
||||
framework.RunOnFrameworkThread(Disposer).Wait();
|
||||
else
|
||||
Disposer();
|
||||
// Unload hooks from the framework thread if possible.
|
||||
// We're currently off the framework thread, as this function can only be called from
|
||||
// ServiceManager.UnloadAllServices, which is called from EntryPoint.RunThread.
|
||||
// The functions being unhooked are mostly called from the main thread, so unhooking from the main thread when
|
||||
// possible would avoid any chance of unhooking a function that currently is being called.
|
||||
// If unloading is initiated from "Unload Dalamud" /xldev menu, then the framework would still be running, as
|
||||
// Framework.Destroy has never been called and thus Framework.IsFrameworkUnloading cannot be true, and this
|
||||
// function will actually run the destroy from the framework thread.
|
||||
// Otherwise, as Framework.IsFrameworkUnloading should have been set, this code should run immediately.
|
||||
this.framework.RunOnFrameworkThread(ClearHooks).Wait();
|
||||
|
||||
// Below this point, hooks are guaranteed to be no longer called.
|
||||
|
||||
// A font resource lock outlives the parent handle and the owner atlas. It should be disposed.
|
||||
Interlocked.Exchange(ref this.defaultFontResourceLock, null)?.Dispose();
|
||||
|
||||
// Font handles become invalid after disposing the atlas, but just to be safe.
|
||||
this.DefaultFontHandle?.Dispose();
|
||||
this.DefaultFontHandle = null;
|
||||
|
||||
this.MonoFontHandle?.Dispose();
|
||||
this.MonoFontHandle = null;
|
||||
|
||||
this.IconFontHandle?.Dispose();
|
||||
this.IconFontHandle = null;
|
||||
|
||||
Interlocked.Exchange(ref this.dalamudAtlas, null)?.Dispose();
|
||||
Interlocked.Exchange(ref this.scene, null)?.Dispose();
|
||||
|
||||
this.wndProcHookManager.PreWndProc -= this.WndProcHookManagerOnPreWndProc;
|
||||
this.defaultFontResourceLock?.Dispose(); // lock outlives handle and atlas
|
||||
this.defaultFontResourceLock = null;
|
||||
this.dalamudAtlas?.Dispose();
|
||||
this.scene?.Dispose();
|
||||
return;
|
||||
|
||||
void Disposer()
|
||||
void ClearHooks()
|
||||
{
|
||||
this.setCursorHook.Dispose();
|
||||
this.presentHook?.Dispose();
|
||||
this.resizeBuffersHook?.Dispose();
|
||||
this.wndProcHookManager.PreWndProc -= this.WndProcHookManagerOnPreWndProc;
|
||||
Interlocked.Exchange(ref this.setCursorHook, null)?.Dispose();
|
||||
Interlocked.Exchange(ref this.presentHook, null)?.Dispose();
|
||||
Interlocked.Exchange(ref this.resizeBuffersHook, null)?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -692,7 +715,6 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
"InterfaceManager accepts event registration and stuff even when the game window is not ready.")]
|
||||
private void ContinueConstruction(
|
||||
TargetSigScanner sigScanner,
|
||||
Framework framework,
|
||||
FontAtlasFactory fontAtlasFactory)
|
||||
{
|
||||
this.dalamudAtlas = fontAtlasFactory
|
||||
|
|
@ -730,7 +752,7 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
this.DefaultFontHandle.ImFontChanged += (_, font) =>
|
||||
{
|
||||
var fontLocked = font.NewRef();
|
||||
Service<Framework>.Get().RunOnFrameworkThread(
|
||||
this.framework.RunOnFrameworkThread(
|
||||
() =>
|
||||
{
|
||||
// Update the ImGui default font.
|
||||
|
|
@ -764,6 +786,7 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
Log.Error(ex, "Could not enable immersive mode");
|
||||
}
|
||||
|
||||
this.setCursorHook = Hook<SetCursorDelegate>.FromImport(null, "user32.dll", "SetCursor", 0, this.SetCursorDetour);
|
||||
this.presentHook = Hook<PresentDelegate>.FromAddress(this.address.Present, this.PresentDetour);
|
||||
this.resizeBuffersHook = Hook<ResizeBuffersDelegate>.FromAddress(this.address.ResizeBuffers, this.ResizeBuffersDetour);
|
||||
|
||||
|
|
@ -807,7 +830,7 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
if (this.lastWantCapture && (!this.scene?.IsImGuiCursor(hCursor) ?? false) && this.OverrideGameCursor)
|
||||
return IntPtr.Zero;
|
||||
|
||||
return this.setCursorHook.IsDisposed
|
||||
return this.setCursorHook?.IsDisposed is not false
|
||||
? User32.SetCursor(new(hCursor, false)).DangerousGetHandle()
|
||||
: this.setCursorHook.Original(hCursor);
|
||||
}
|
||||
|
|
@ -917,7 +940,7 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
if (this.IsDispatchingEvents)
|
||||
{
|
||||
this.Draw?.Invoke();
|
||||
Service<NotificationManager>.Get().Draw();
|
||||
Service<NotificationManager>.GetNullable()?.Draw();
|
||||
}
|
||||
|
||||
ImGuiManagedAsserts.ReportProblems("Dalamud Core", snap);
|
||||
|
|
|
|||
|
|
@ -1,318 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Utility;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Notifications;
|
||||
|
||||
/// <summary>
|
||||
/// Class handling notifications/toasts in ImGui.
|
||||
/// Ported from https://github.com/patrickcjk/imgui-notify.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class NotificationManager : IServiceType
|
||||
{
|
||||
/// <summary>
|
||||
/// Value indicating the bottom-left X padding.
|
||||
/// </summary>
|
||||
internal const float NotifyPaddingX = 20.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Value indicating the bottom-left Y padding.
|
||||
/// </summary>
|
||||
internal const float NotifyPaddingY = 20.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Value indicating the Y padding between each message.
|
||||
/// </summary>
|
||||
internal const float NotifyPaddingMessageY = 10.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Value indicating the fade-in and out duration.
|
||||
/// </summary>
|
||||
internal const int NotifyFadeInOutTime = 500;
|
||||
|
||||
/// <summary>
|
||||
/// Value indicating the default time until the notification is dismissed.
|
||||
/// </summary>
|
||||
internal const int NotifyDefaultDismiss = 3000;
|
||||
|
||||
/// <summary>
|
||||
/// Value indicating the maximum opacity.
|
||||
/// </summary>
|
||||
internal const float NotifyOpacity = 0.82f;
|
||||
|
||||
/// <summary>
|
||||
/// Value indicating default window flags for the notifications.
|
||||
/// </summary>
|
||||
internal const ImGuiWindowFlags NotifyToastFlags =
|
||||
ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoInputs |
|
||||
ImGuiWindowFlags.NoNav | ImGuiWindowFlags.NoBringToFrontOnFocus | ImGuiWindowFlags.NoFocusOnAppearing;
|
||||
|
||||
private readonly List<Notification> notifications = new();
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private NotificationManager()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a notification to the notification queue.
|
||||
/// </summary>
|
||||
/// <param name="content">The content of the notification.</param>
|
||||
/// <param name="title">The title of the notification.</param>
|
||||
/// <param name="type">The type of the notification.</param>
|
||||
/// <param name="msDelay">The time the notification should be displayed for.</param>
|
||||
public void AddNotification(string content, string? title = null, NotificationType type = NotificationType.None, uint msDelay = NotifyDefaultDismiss)
|
||||
{
|
||||
this.notifications.Add(new Notification
|
||||
{
|
||||
Content = content,
|
||||
Title = title,
|
||||
NotificationType = type,
|
||||
DurationMs = msDelay,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw all currently queued notifications.
|
||||
/// </summary>
|
||||
public void Draw()
|
||||
{
|
||||
var viewportSize = ImGuiHelpers.MainViewport.Size;
|
||||
var height = 0f;
|
||||
|
||||
for (var i = 0; i < this.notifications.Count; i++)
|
||||
{
|
||||
var tn = this.notifications.ElementAt(i);
|
||||
|
||||
if (tn.GetPhase() == Notification.Phase.Expired)
|
||||
{
|
||||
this.notifications.RemoveAt(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
var opacity = tn.GetFadePercent();
|
||||
|
||||
var iconColor = tn.Color;
|
||||
iconColor.W = opacity;
|
||||
|
||||
var windowName = $"##NOTIFY{i}";
|
||||
|
||||
ImGuiHelpers.ForceNextWindowMainViewport();
|
||||
ImGui.SetNextWindowBgAlpha(opacity);
|
||||
ImGui.SetNextWindowPos(ImGuiHelpers.MainViewport.Pos + new Vector2(viewportSize.X - NotifyPaddingX, viewportSize.Y - NotifyPaddingY - height), ImGuiCond.Always, Vector2.One);
|
||||
ImGui.Begin(windowName, NotifyToastFlags);
|
||||
|
||||
ImGui.PushTextWrapPos(viewportSize.X / 3.0f);
|
||||
|
||||
var wasTitleRendered = false;
|
||||
|
||||
if (!tn.Icon.IsNullOrEmpty())
|
||||
{
|
||||
wasTitleRendered = true;
|
||||
ImGui.PushFont(InterfaceManager.IconFont);
|
||||
ImGui.TextColored(iconColor, tn.Icon);
|
||||
ImGui.PopFont();
|
||||
}
|
||||
|
||||
var textColor = ImGuiColors.DalamudWhite;
|
||||
textColor.W = opacity;
|
||||
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, textColor);
|
||||
|
||||
if (!tn.Title.IsNullOrEmpty())
|
||||
{
|
||||
if (!tn.Icon.IsNullOrEmpty())
|
||||
{
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
ImGui.TextUnformatted(tn.Title);
|
||||
wasTitleRendered = true;
|
||||
}
|
||||
else if (!tn.DefaultTitle.IsNullOrEmpty())
|
||||
{
|
||||
if (!tn.Icon.IsNullOrEmpty())
|
||||
{
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
ImGui.TextUnformatted(tn.DefaultTitle);
|
||||
wasTitleRendered = true;
|
||||
}
|
||||
|
||||
if (wasTitleRendered && !tn.Content.IsNullOrEmpty())
|
||||
{
|
||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 5.0f);
|
||||
}
|
||||
|
||||
if (!tn.Content.IsNullOrEmpty())
|
||||
{
|
||||
if (wasTitleRendered)
|
||||
{
|
||||
ImGui.Separator();
|
||||
}
|
||||
|
||||
ImGui.TextUnformatted(tn.Content);
|
||||
}
|
||||
|
||||
ImGui.PopStyleColor();
|
||||
|
||||
ImGui.PopTextWrapPos();
|
||||
|
||||
height += ImGui.GetWindowHeight() + NotifyPaddingMessageY;
|
||||
|
||||
ImGui.End();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Container class for notifications.
|
||||
/// </summary>
|
||||
internal class Notification
|
||||
{
|
||||
/// <summary>
|
||||
/// Possible notification phases.
|
||||
/// </summary>
|
||||
internal enum Phase
|
||||
{
|
||||
/// <summary>
|
||||
/// Phase indicating fade-in.
|
||||
/// </summary>
|
||||
FadeIn,
|
||||
|
||||
/// <summary>
|
||||
/// Phase indicating waiting until fade-out.
|
||||
/// </summary>
|
||||
Wait,
|
||||
|
||||
/// <summary>
|
||||
/// Phase indicating fade-out.
|
||||
/// </summary>
|
||||
FadeOut,
|
||||
|
||||
/// <summary>
|
||||
/// Phase indicating that the notification has expired.
|
||||
/// </summary>
|
||||
Expired,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the notification.
|
||||
/// </summary>
|
||||
internal NotificationType NotificationType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the title of the notification.
|
||||
/// </summary>
|
||||
internal string? Title { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content of the notification.
|
||||
/// </summary>
|
||||
internal string Content { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the duration of the notification in milliseconds.
|
||||
/// </summary>
|
||||
internal uint DurationMs { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the creation time of the notification.
|
||||
/// </summary>
|
||||
internal DateTime CreationTime { get; init; } = DateTime.Now;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default color of the notification.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <see cref="NotificationType"/> is set to an out-of-range value.</exception>
|
||||
internal Vector4 Color => this.NotificationType switch
|
||||
{
|
||||
NotificationType.None => ImGuiColors.DalamudWhite,
|
||||
NotificationType.Success => ImGuiColors.HealerGreen,
|
||||
NotificationType.Warning => ImGuiColors.DalamudOrange,
|
||||
NotificationType.Error => ImGuiColors.DalamudRed,
|
||||
NotificationType.Info => ImGuiColors.TankBlue,
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the icon of the notification.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <see cref="NotificationType"/> is set to an out-of-range value.</exception>
|
||||
internal string? Icon => this.NotificationType switch
|
||||
{
|
||||
NotificationType.None => null,
|
||||
NotificationType.Success => FontAwesomeIcon.CheckCircle.ToIconString(),
|
||||
NotificationType.Warning => FontAwesomeIcon.ExclamationCircle.ToIconString(),
|
||||
NotificationType.Error => FontAwesomeIcon.TimesCircle.ToIconString(),
|
||||
NotificationType.Info => FontAwesomeIcon.InfoCircle.ToIconString(),
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default title of the notification.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <see cref="NotificationType"/> is set to an out-of-range value.</exception>
|
||||
internal string? DefaultTitle => this.NotificationType switch
|
||||
{
|
||||
NotificationType.None => null,
|
||||
NotificationType.Success => NotificationType.Success.ToString(),
|
||||
NotificationType.Warning => NotificationType.Warning.ToString(),
|
||||
NotificationType.Error => NotificationType.Error.ToString(),
|
||||
NotificationType.Info => NotificationType.Info.ToString(),
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the elapsed time since creating the notification.
|
||||
/// </summary>
|
||||
internal TimeSpan ElapsedTime => DateTime.Now - this.CreationTime;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the phase of the notification.
|
||||
/// </summary>
|
||||
/// <returns>The phase of the notification.</returns>
|
||||
internal Phase GetPhase()
|
||||
{
|
||||
var elapsed = (int)this.ElapsedTime.TotalMilliseconds;
|
||||
|
||||
if (elapsed > NotifyFadeInOutTime + this.DurationMs + NotifyFadeInOutTime)
|
||||
return Phase.Expired;
|
||||
else if (elapsed > NotifyFadeInOutTime + this.DurationMs)
|
||||
return Phase.FadeOut;
|
||||
else if (elapsed > NotifyFadeInOutTime)
|
||||
return Phase.Wait;
|
||||
else
|
||||
return Phase.FadeIn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the opacity of the notification.
|
||||
/// </summary>
|
||||
/// <returns>The opacity, in a range from 0 to 1.</returns>
|
||||
internal float GetFadePercent()
|
||||
{
|
||||
var phase = this.GetPhase();
|
||||
var elapsed = this.ElapsedTime.TotalMilliseconds;
|
||||
|
||||
if (phase == Phase.FadeIn)
|
||||
{
|
||||
return (float)elapsed / NotifyFadeInOutTime * NotifyOpacity;
|
||||
}
|
||||
else if (phase == Phase.FadeOut)
|
||||
{
|
||||
return (1.0f - (((float)elapsed - NotifyFadeInOutTime - this.DurationMs) /
|
||||
NotifyFadeInOutTime)) * NotifyOpacity;
|
||||
}
|
||||
|
||||
return 1.0f * NotifyOpacity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +1,23 @@
|
|||
namespace Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Utility;
|
||||
|
||||
/// <summary>
|
||||
/// Possible notification types.
|
||||
/// </summary>
|
||||
namespace Dalamud.Interface.Internal.Notifications;
|
||||
|
||||
/// <summary>Possible notification types.</summary>
|
||||
[Api10ToDo(Api10ToDoAttribute.MoveNamespace, nameof(ImGuiNotification.Internal))]
|
||||
public enum NotificationType
|
||||
{
|
||||
/// <summary>
|
||||
/// No special type.
|
||||
/// </summary>
|
||||
/// <summary>No special type.</summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Type indicating success.
|
||||
/// </summary>
|
||||
/// <summary>Type indicating success.</summary>
|
||||
Success,
|
||||
|
||||
/// <summary>
|
||||
/// Type indicating a warning.
|
||||
/// </summary>
|
||||
/// <summary>Type indicating a warning.</summary>
|
||||
Warning,
|
||||
|
||||
/// <summary>
|
||||
/// Type indicating an error.
|
||||
/// </summary>
|
||||
/// <summary>Type indicating an error.</summary>
|
||||
Error,
|
||||
|
||||
/// <summary>
|
||||
/// Type indicating generic information.
|
||||
/// </summary>
|
||||
/// <summary>Type indicating generic information.</summary>
|
||||
Info,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ namespace Dalamud.Interface.Internal;
|
|||
[ResolveVia<ITextureProvider>]
|
||||
[ResolveVia<ITextureSubstitutionProvider>]
|
||||
#pragma warning restore SA1015
|
||||
internal class TextureManager : IDisposable, IServiceType, ITextureProvider, ITextureSubstitutionProvider
|
||||
internal class TextureManager : IInternalDisposableService, ITextureProvider, ITextureSubstitutionProvider
|
||||
{
|
||||
private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex";
|
||||
private const string HighResolutionIconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}_hr1.tex";
|
||||
|
|
@ -268,7 +268,7 @@ internal class TextureManager : IDisposable, IServiceType, ITextureProvider, ITe
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.fallbackTextureWrap?.Dispose();
|
||||
this.framework.Update -= this.FrameworkOnUpdate;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ using Dalamud.Game;
|
|||
using Dalamud.Game.Command;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Components;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
|
|
@ -76,6 +78,8 @@ internal class ConsoleWindow : Window, IDisposable
|
|||
private int historyPos;
|
||||
private int copyStart = -1;
|
||||
|
||||
private IActiveNotification? prevCopyNotification;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="ConsoleWindow"/> class.</summary>
|
||||
/// <param name="configuration">An instance of <see cref="DalamudConfiguration"/>.</param>
|
||||
public ConsoleWindow(DalamudConfiguration configuration)
|
||||
|
|
@ -436,10 +440,14 @@ internal class ConsoleWindow : Window, IDisposable
|
|||
return;
|
||||
|
||||
ImGui.SetClipboardText(sb.ToString());
|
||||
Service<NotificationManager>.Get().AddNotification(
|
||||
$"{n:n0} line(s) copied.",
|
||||
this.WindowName,
|
||||
NotificationType.Success);
|
||||
this.prevCopyNotification?.DismissNow();
|
||||
this.prevCopyNotification = Service<NotificationManager>.Get().AddNotification(
|
||||
new()
|
||||
{
|
||||
Title = this.WindowName,
|
||||
Content = $"{n:n0} line(s) copied.",
|
||||
Type = NotificationType.Success,
|
||||
});
|
||||
}
|
||||
|
||||
private void DrawOptionsToolbar()
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ internal class GameInventoryTestWidget : IDataWindowWidget
|
|||
this.standardEnabled = false;
|
||||
if (!this.rawEnabled)
|
||||
{
|
||||
this.scoped.Dispose();
|
||||
((IInternalDisposableService)this.scoped).DisposeService();
|
||||
this.scoped = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -105,7 +105,7 @@ internal class GameInventoryTestWidget : IDataWindowWidget
|
|||
this.rawEnabled = false;
|
||||
if (!this.standardEnabled)
|
||||
{
|
||||
this.scoped.Dispose();
|
||||
((IInternalDisposableService)this.scoped).DisposeService();
|
||||
this.scoped = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -135,7 +135,7 @@ internal class GameInventoryTestWidget : IDataWindowWidget
|
|||
{
|
||||
if (ImGui.Button("Disable##all-disable"))
|
||||
{
|
||||
this.scoped?.Dispose();
|
||||
((IInternalDisposableService)this.scoped)?.DisposeService();
|
||||
this.scoped = null;
|
||||
this.standardEnabled = this.rawEnabled = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ public class AddonLifecycleWidget : IDataWindowWidget
|
|||
ImGui.Text(listener.AddonName is "" ? "GLOBAL" : listener.AddonName);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text($"{listener.FunctionDelegate.Target}::{listener.FunctionDelegate.Method.Name}");
|
||||
ImGui.Text($"{listener.FunctionDelegate.Method.DeclaringType.FullName}::{listener.FunctionDelegate.Method.Name}");
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System.Numerics;
|
|||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,14 @@
|
|||
using Dalamud.Interface.Internal.Notifications;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Storage.Assets;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||
|
|
@ -9,11 +18,13 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
|||
/// </summary>
|
||||
internal class ImGuiWidget : IDataWindowWidget
|
||||
{
|
||||
private NotificationTemplate notificationTemplate;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string[]? CommandShortcuts { get; init; } = { "imgui" };
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string DisplayName { get; init; } = "ImGui";
|
||||
public string DisplayName { get; init; } = "ImGui";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
|
@ -22,6 +33,7 @@ internal class ImGuiWidget : IDataWindowWidget
|
|||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
this.notificationTemplate.Reset();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -38,38 +50,374 @@ internal class ImGuiWidget : IDataWindowWidget
|
|||
|
||||
ImGui.Separator();
|
||||
|
||||
ImGui.TextUnformatted($"WindowSystem.TimeSinceLastAnyFocus: {WindowSystem.TimeSinceLastAnyFocus.TotalMilliseconds:0}ms");
|
||||
ImGui.TextUnformatted(
|
||||
$"WindowSystem.TimeSinceLastAnyFocus: {WindowSystem.TimeSinceLastAnyFocus.TotalMilliseconds:0}ms");
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
if (ImGui.Button("Add random notification"))
|
||||
ImGui.Checkbox("##manualContent", ref this.notificationTemplate.ManualContent);
|
||||
ImGui.SameLine();
|
||||
ImGui.InputText("Content##content", ref this.notificationTemplate.Content, 255);
|
||||
|
||||
ImGui.Checkbox("##manualTitle", ref this.notificationTemplate.ManualTitle);
|
||||
ImGui.SameLine();
|
||||
ImGui.InputText("Title##title", ref this.notificationTemplate.Title, 255);
|
||||
|
||||
ImGui.Checkbox("##manualMinimizedText", ref this.notificationTemplate.ManualMinimizedText);
|
||||
ImGui.SameLine();
|
||||
ImGui.InputText("MinimizedText##minimizedText", ref this.notificationTemplate.MinimizedText, 255);
|
||||
|
||||
ImGui.Checkbox("##manualType", ref this.notificationTemplate.ManualType);
|
||||
ImGui.SameLine();
|
||||
ImGui.Combo(
|
||||
"Type##type",
|
||||
ref this.notificationTemplate.TypeInt,
|
||||
NotificationTemplate.TypeTitles,
|
||||
NotificationTemplate.TypeTitles.Length);
|
||||
|
||||
ImGui.Combo(
|
||||
"Icon##iconCombo",
|
||||
ref this.notificationTemplate.IconInt,
|
||||
NotificationTemplate.IconTitles,
|
||||
NotificationTemplate.IconTitles.Length);
|
||||
switch (this.notificationTemplate.IconInt)
|
||||
{
|
||||
var rand = new Random();
|
||||
case 1 or 2:
|
||||
ImGui.InputText(
|
||||
"Icon Text##iconText",
|
||||
ref this.notificationTemplate.IconText,
|
||||
255);
|
||||
break;
|
||||
case 5 or 6:
|
||||
ImGui.Combo(
|
||||
"Asset##iconAssetCombo",
|
||||
ref this.notificationTemplate.IconAssetInt,
|
||||
NotificationTemplate.AssetSources,
|
||||
NotificationTemplate.AssetSources.Length);
|
||||
break;
|
||||
case 3 or 7:
|
||||
ImGui.InputText(
|
||||
"Game Path##iconText",
|
||||
ref this.notificationTemplate.IconText,
|
||||
255);
|
||||
break;
|
||||
case 4 or 8:
|
||||
ImGui.InputText(
|
||||
"File Path##iconText",
|
||||
ref this.notificationTemplate.IconText,
|
||||
255);
|
||||
break;
|
||||
}
|
||||
|
||||
var title = rand.Next(0, 5) switch
|
||||
ImGui.Combo(
|
||||
"Initial Duration",
|
||||
ref this.notificationTemplate.InitialDurationInt,
|
||||
NotificationTemplate.InitialDurationTitles,
|
||||
NotificationTemplate.InitialDurationTitles.Length);
|
||||
|
||||
ImGui.Combo(
|
||||
"Extension Duration",
|
||||
ref this.notificationTemplate.HoverExtendDurationInt,
|
||||
NotificationTemplate.HoverExtendDurationTitles,
|
||||
NotificationTemplate.HoverExtendDurationTitles.Length);
|
||||
|
||||
ImGui.Combo(
|
||||
"Progress",
|
||||
ref this.notificationTemplate.ProgressMode,
|
||||
NotificationTemplate.ProgressModeTitles,
|
||||
NotificationTemplate.ProgressModeTitles.Length);
|
||||
|
||||
ImGui.Checkbox("Respect UI Hidden", ref this.notificationTemplate.RespectUiHidden);
|
||||
|
||||
ImGui.Checkbox("Minimized", ref this.notificationTemplate.Minimized);
|
||||
|
||||
ImGui.Checkbox("Show Indeterminate If No Expiry", ref this.notificationTemplate.ShowIndeterminateIfNoExpiry);
|
||||
|
||||
ImGui.Checkbox("User Dismissable", ref this.notificationTemplate.UserDismissable);
|
||||
|
||||
ImGui.Checkbox(
|
||||
"Action Bar (always on if not user dismissable for the example)",
|
||||
ref this.notificationTemplate.ActionBar);
|
||||
|
||||
if (ImGui.Button("Add notification"))
|
||||
{
|
||||
var text =
|
||||
"Bla bla bla bla bla bla bla bla bla bla bla.\nBla bla bla bla bla bla bla bla bla bla bla bla bla bla.";
|
||||
|
||||
NewRandom(out var title, out var type, out var progress);
|
||||
if (this.notificationTemplate.ManualTitle)
|
||||
title = this.notificationTemplate.Title;
|
||||
if (this.notificationTemplate.ManualContent)
|
||||
text = this.notificationTemplate.Content;
|
||||
if (this.notificationTemplate.ManualType)
|
||||
type = (NotificationType)this.notificationTemplate.TypeInt;
|
||||
|
||||
var n = notifications.AddNotification(
|
||||
new()
|
||||
{
|
||||
Content = text,
|
||||
Title = title,
|
||||
MinimizedText = this.notificationTemplate.ManualMinimizedText
|
||||
? this.notificationTemplate.MinimizedText
|
||||
: null,
|
||||
Type = type,
|
||||
ShowIndeterminateIfNoExpiry = this.notificationTemplate.ShowIndeterminateIfNoExpiry,
|
||||
RespectUiHidden = this.notificationTemplate.RespectUiHidden,
|
||||
Minimized = this.notificationTemplate.Minimized,
|
||||
UserDismissable = this.notificationTemplate.UserDismissable,
|
||||
InitialDuration =
|
||||
this.notificationTemplate.InitialDurationInt == 0
|
||||
? TimeSpan.MaxValue
|
||||
: NotificationTemplate.Durations[this.notificationTemplate.InitialDurationInt],
|
||||
ExtensionDurationSinceLastInterest =
|
||||
this.notificationTemplate.HoverExtendDurationInt == 0
|
||||
? TimeSpan.Zero
|
||||
: NotificationTemplate.Durations[this.notificationTemplate.HoverExtendDurationInt],
|
||||
Progress = this.notificationTemplate.ProgressMode switch
|
||||
{
|
||||
0 => 1f,
|
||||
1 => progress,
|
||||
2 => 0f,
|
||||
3 => 0f,
|
||||
4 => -1f,
|
||||
_ => 0.5f,
|
||||
},
|
||||
Icon = this.notificationTemplate.IconInt switch
|
||||
{
|
||||
1 => INotificationIcon.From(
|
||||
(SeIconChar)(this.notificationTemplate.IconText.Length == 0
|
||||
? 0
|
||||
: this.notificationTemplate.IconText[0])),
|
||||
2 => INotificationIcon.From(
|
||||
(FontAwesomeIcon)(this.notificationTemplate.IconText.Length == 0
|
||||
? 0
|
||||
: this.notificationTemplate.IconText[0])),
|
||||
3 => INotificationIcon.FromGame(this.notificationTemplate.IconText),
|
||||
4 => INotificationIcon.FromFile(this.notificationTemplate.IconText),
|
||||
_ => null,
|
||||
},
|
||||
});
|
||||
|
||||
var dam = Service<DalamudAssetManager>.Get();
|
||||
var tm = Service<TextureManager>.Get();
|
||||
switch (this.notificationTemplate.IconInt)
|
||||
{
|
||||
0 => "This is a toast",
|
||||
1 => "Truly, a toast",
|
||||
2 => "I am testing this toast",
|
||||
3 => "I hope this looks right",
|
||||
4 => "Good stuff",
|
||||
5 => "Nice",
|
||||
_ => null,
|
||||
};
|
||||
case 5:
|
||||
n.SetIconTexture(
|
||||
dam.GetDalamudTextureWrap(
|
||||
Enum.Parse<DalamudAsset>(
|
||||
NotificationTemplate.AssetSources[this.notificationTemplate.IconAssetInt])));
|
||||
break;
|
||||
case 6:
|
||||
n.SetIconTexture(
|
||||
dam.GetDalamudTextureWrapAsync(
|
||||
Enum.Parse<DalamudAsset>(
|
||||
NotificationTemplate.AssetSources[this.notificationTemplate.IconAssetInt])));
|
||||
break;
|
||||
case 7:
|
||||
n.SetIconTexture(tm.GetTextureFromGame(this.notificationTemplate.IconText));
|
||||
break;
|
||||
case 8:
|
||||
n.SetIconTexture(tm.GetTextureFromFile(new(this.notificationTemplate.IconText)));
|
||||
break;
|
||||
}
|
||||
|
||||
var type = rand.Next(0, 4) switch
|
||||
switch (this.notificationTemplate.ProgressMode)
|
||||
{
|
||||
0 => NotificationType.Error,
|
||||
1 => NotificationType.Warning,
|
||||
2 => NotificationType.Info,
|
||||
3 => NotificationType.Success,
|
||||
4 => NotificationType.None,
|
||||
_ => NotificationType.None,
|
||||
};
|
||||
case 2:
|
||||
Task.Run(
|
||||
async () =>
|
||||
{
|
||||
for (var i = 0; i <= 10 && !n.DismissReason.HasValue; i++)
|
||||
{
|
||||
await Task.Delay(500);
|
||||
n.Progress = i / 10f;
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 3:
|
||||
Task.Run(
|
||||
async () =>
|
||||
{
|
||||
for (var i = 0; i <= 10 && !n.DismissReason.HasValue; i++)
|
||||
{
|
||||
await Task.Delay(500);
|
||||
n.Progress = i / 10f;
|
||||
}
|
||||
|
||||
const string text = "Bla bla bla bla bla bla bla bla bla bla bla.\nBla bla bla bla bla bla bla bla bla bla bla bla bla bla.";
|
||||
n.ExtendBy(NotificationConstants.DefaultDuration);
|
||||
n.InitialDuration = NotificationConstants.DefaultDuration;
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
notifications.AddNotification(text, title, type);
|
||||
if (this.notificationTemplate.ActionBar || !this.notificationTemplate.UserDismissable)
|
||||
{
|
||||
var nclick = 0;
|
||||
var testString = "input";
|
||||
|
||||
n.Click += _ => nclick++;
|
||||
n.DrawActions += an =>
|
||||
{
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted($"{nclick}");
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Update"))
|
||||
{
|
||||
NewRandom(out title, out type, out progress);
|
||||
an.Notification.Title = title;
|
||||
an.Notification.Type = type;
|
||||
an.Notification.Progress = progress;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Dismiss"))
|
||||
an.Notification.DismissNow();
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.SetNextItemWidth(an.MaxCoord.X - ImGui.GetCursorPosX());
|
||||
ImGui.InputText("##input", ref testString, 255);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void NewRandom(out string? title, out NotificationType type, out float progress)
|
||||
{
|
||||
var rand = new Random();
|
||||
|
||||
title = rand.Next(0, 7) switch
|
||||
{
|
||||
0 => "This is a toast",
|
||||
1 => "Truly, a toast",
|
||||
2 => "I am testing this toast",
|
||||
3 => "I hope this looks right",
|
||||
4 => "Good stuff",
|
||||
5 => "Nice",
|
||||
_ => null,
|
||||
};
|
||||
|
||||
type = rand.Next(0, 5) switch
|
||||
{
|
||||
0 => NotificationType.Error,
|
||||
1 => NotificationType.Warning,
|
||||
2 => NotificationType.Info,
|
||||
3 => NotificationType.Success,
|
||||
4 => NotificationType.None,
|
||||
_ => NotificationType.None,
|
||||
};
|
||||
|
||||
if (rand.Next() % 2 == 0)
|
||||
progress = -1;
|
||||
else
|
||||
progress = rand.NextSingle();
|
||||
}
|
||||
|
||||
private struct NotificationTemplate
|
||||
{
|
||||
public static readonly string[] IconTitles =
|
||||
{
|
||||
"None (use Type)",
|
||||
"SeIconChar",
|
||||
"FontAwesomeIcon",
|
||||
"GamePath",
|
||||
"FilePath",
|
||||
"TextureWrap from DalamudAssets",
|
||||
"TextureWrap from DalamudAssets(Async)",
|
||||
"TextureWrap from GamePath",
|
||||
"TextureWrap from FilePath",
|
||||
};
|
||||
|
||||
public static readonly string[] AssetSources =
|
||||
Enum.GetValues<DalamudAsset>()
|
||||
.Where(x => x.GetAttribute<DalamudAssetAttribute>()?.Purpose is DalamudAssetPurpose.TextureFromPng)
|
||||
.Select(Enum.GetName)
|
||||
.ToArray();
|
||||
|
||||
public static readonly string[] ProgressModeTitles =
|
||||
{
|
||||
"Default",
|
||||
"Random",
|
||||
"Increasing",
|
||||
"Increasing & Auto Dismiss",
|
||||
"Indeterminate",
|
||||
};
|
||||
|
||||
public static readonly string[] TypeTitles =
|
||||
{
|
||||
nameof(NotificationType.None),
|
||||
nameof(NotificationType.Success),
|
||||
nameof(NotificationType.Warning),
|
||||
nameof(NotificationType.Error),
|
||||
nameof(NotificationType.Info),
|
||||
};
|
||||
|
||||
public static readonly string[] InitialDurationTitles =
|
||||
{
|
||||
"Infinite",
|
||||
"1 seconds",
|
||||
"3 seconds (default)",
|
||||
"10 seconds",
|
||||
};
|
||||
|
||||
public static readonly string[] HoverExtendDurationTitles =
|
||||
{
|
||||
"Disable",
|
||||
"1 seconds",
|
||||
"3 seconds (default)",
|
||||
"10 seconds",
|
||||
};
|
||||
|
||||
public static readonly TimeSpan[] Durations =
|
||||
{
|
||||
TimeSpan.Zero,
|
||||
TimeSpan.FromSeconds(1),
|
||||
NotificationConstants.DefaultDuration,
|
||||
TimeSpan.FromSeconds(10),
|
||||
};
|
||||
|
||||
public bool ManualContent;
|
||||
public string Content;
|
||||
public bool ManualTitle;
|
||||
public string Title;
|
||||
public bool ManualMinimizedText;
|
||||
public string MinimizedText;
|
||||
public int IconInt;
|
||||
public string IconText;
|
||||
public int IconAssetInt;
|
||||
public bool ManualType;
|
||||
public int TypeInt;
|
||||
public int InitialDurationInt;
|
||||
public int HoverExtendDurationInt;
|
||||
public bool ShowIndeterminateIfNoExpiry;
|
||||
public bool RespectUiHidden;
|
||||
public bool Minimized;
|
||||
public bool UserDismissable;
|
||||
public bool ActionBar;
|
||||
public int ProgressMode;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
this.ManualContent = false;
|
||||
this.Content = string.Empty;
|
||||
this.ManualTitle = false;
|
||||
this.Title = string.Empty;
|
||||
this.ManualMinimizedText = false;
|
||||
this.MinimizedText = string.Empty;
|
||||
this.IconInt = 0;
|
||||
this.IconText = "ui/icon/000000/000004_hr1.tex";
|
||||
this.IconAssetInt = 0;
|
||||
this.ManualType = false;
|
||||
this.TypeInt = (int)NotificationType.None;
|
||||
this.InitialDurationInt = 2;
|
||||
this.HoverExtendDurationInt = 2;
|
||||
this.ShowIndeterminateIfNoExpiry = true;
|
||||
this.Minimized = true;
|
||||
this.UserDismissable = true;
|
||||
this.ActionBar = true;
|
||||
this.ProgressMode = 0;
|
||||
this.RespectUiHidden = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ namespace Dalamud.Interface.Internal.Windows;
|
|||
/// A cache for plugin icons and images.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class PluginImageCache : IDisposable, IServiceType
|
||||
internal class PluginImageCache : IInternalDisposableService
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum plugin image width.
|
||||
|
|
@ -136,7 +136,7 @@ internal class PluginImageCache : IDisposable, IServiceType
|
|||
this.dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall, this.EmptyTexture);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.cancelToken.Cancel();
|
||||
this.downloadQueue.CompleteAdding();
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ using Dalamud.Game.Command;
|
|||
using Dalamud.Interface.Animation.EasingFunctions;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Components;
|
||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using CheapLoc;
|
|||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Components;
|
||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Reflection;
|
|||
using Dalamud.Game;
|
||||
using Dalamud.Hooking.Internal;
|
||||
using Dalamud.Interface.Components;
|
||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Plugin.Internal;
|
||||
|
|
|
|||
|
|
@ -85,6 +85,10 @@ public interface IFontAtlas : IDisposable
|
|||
/// <summary>Creates a new <see cref="IFontHandle"/> from game's built-in fonts.</summary>
|
||||
/// <param name="style">Font to use.</param>
|
||||
/// <returns>Handle to a font that may or may not be ready yet.</returns>
|
||||
/// <exception cref="InvalidOperationException">When called during <see cref="BuildStepChange"/>,
|
||||
/// <see cref="UiBuilder.BuildFonts"/>, <see cref="UiBuilder.AfterBuildFonts"/>, and alike. Move the font handle
|
||||
/// creating code outside those handlers, and only initialize them once. Call <see cref="IDisposable.Dispose"/>
|
||||
/// on a previous font handle if you're replacing one.</exception>
|
||||
/// <remarks>This function does not throw. <see cref="IFontHandle.LoadException"/> will be populated instead, if
|
||||
/// the build procedure has failed. <see cref="IFontHandle.Push"/> can be used regardless of the state of the font
|
||||
/// handle.</remarks>
|
||||
|
|
@ -93,6 +97,13 @@ public interface IFontAtlas : IDisposable
|
|||
/// <summary>Creates a new IFontHandle using your own callbacks.</summary>
|
||||
/// <param name="buildStepDelegate">Callback for <see cref="IFontAtlas.BuildStepChange"/>.</param>
|
||||
/// <returns>Handle to a font that may or may not be ready yet.</returns>
|
||||
/// <exception cref="InvalidOperationException">When called during <see cref="BuildStepChange"/>,
|
||||
/// <see cref="UiBuilder.BuildFonts"/>, <see cref="UiBuilder.AfterBuildFonts"/>, and alike. Move the font handle
|
||||
/// creating code outside those handlers, and only initialize them once. Call <see cref="IDisposable.Dispose"/>
|
||||
/// on a previous font handle if you're replacing one.</exception>
|
||||
/// <remarks>Consider calling <see cref="IFontAtlasBuildToolkitPreBuild.AttachExtraGlyphsForDalamudLanguage"/> to
|
||||
/// support glyphs that are not supplied by the game by default; this mostly affects Chinese and Korean language
|
||||
/// users.</remarks>
|
||||
/// <remarks>
|
||||
/// <para>Consider calling <see cref="IFontAtlasBuildToolkitPreBuild.AttachExtraGlyphsForDalamudLanguage"/> to
|
||||
/// support glyphs that are not supplied by the game by default; this mostly affects Chinese and Korean language
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ internal sealed partial class FontAtlasFactory
|
|||
/// </summary>
|
||||
public const string EllipsisCodepoints = "\u2026\u0085";
|
||||
|
||||
/// <summary>Marker for tasks on whether it's being called inside a font build cycle.</summary>
|
||||
public static readonly AsyncLocal<bool> IsBuildInProgressForTask = new();
|
||||
|
||||
/// <summary>
|
||||
/// If set, disables concurrent font build operation.
|
||||
/// </summary>
|
||||
|
|
@ -204,12 +207,12 @@ internal sealed partial class FontAtlasFactory
|
|||
{
|
||||
while (this.IsBuildInProgress)
|
||||
await Task.Delay(100);
|
||||
this.Garbage.Dispose();
|
||||
this.Clear();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Garbage.Dispose();
|
||||
this.Clear();
|
||||
}
|
||||
|
||||
return newRefCount;
|
||||
|
|
@ -227,6 +230,20 @@ internal sealed partial class FontAtlasFactory
|
|||
var axisSubstance = this.Substances.OfType<GamePrebakedFontHandle.HandleSubstance>().Single();
|
||||
return new(factory, this, axisSubstance, isAsync) { BuildStep = FontAtlasBuildStep.PreBuild };
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
try
|
||||
{
|
||||
this.Garbage.Dispose();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(
|
||||
e,
|
||||
$"Disposing {nameof(FontAtlasBuiltData)} of {this.Owner?.Name ?? "???"}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DalamudFontAtlas : IFontAtlas, DisposeSafety.IDisposeCallback
|
||||
|
|
@ -413,11 +430,28 @@ internal sealed partial class FontAtlasFactory
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IFontHandle NewGameFontHandle(GameFontStyle style) => this.gameFontHandleManager.NewFontHandle(style);
|
||||
public IFontHandle NewGameFontHandle(GameFontStyle style)
|
||||
{
|
||||
if (IsBuildInProgressForTask.Value)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"{nameof(this.NewGameFontHandle)} may not be called during {nameof(this.BuildStepChange)}, the callback of {nameof(this.NewDelegateFontHandle)}, {nameof(UiBuilder.BuildFonts)} or {nameof(UiBuilder.AfterBuildFonts)}.");
|
||||
}
|
||||
|
||||
return this.gameFontHandleManager.NewFontHandle(style);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IFontHandle NewDelegateFontHandle(FontAtlasBuildStepDelegate buildStepDelegate) =>
|
||||
this.delegateFontHandleManager.NewFontHandle(buildStepDelegate);
|
||||
public IFontHandle NewDelegateFontHandle(FontAtlasBuildStepDelegate buildStepDelegate)
|
||||
{
|
||||
if (IsBuildInProgressForTask.Value)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"{nameof(this.NewDelegateFontHandle)} may not be called during {nameof(this.BuildStepChange)} or the callback of {nameof(this.NewDelegateFontHandle)}, {nameof(UiBuilder.BuildFonts)} or {nameof(UiBuilder.AfterBuildFonts)}.");
|
||||
}
|
||||
|
||||
return this.delegateFontHandleManager.NewFontHandle(buildStepDelegate);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void BuildFontsOnNextFrame()
|
||||
|
|
@ -547,13 +581,13 @@ internal sealed partial class FontAtlasFactory
|
|||
{
|
||||
if (this.buildIndex != rebuildIndex)
|
||||
{
|
||||
data.ExplicitDisposeIgnoreExceptions();
|
||||
data.Release();
|
||||
return;
|
||||
}
|
||||
|
||||
var prevBuiltData = this.builtData;
|
||||
this.builtData = data;
|
||||
prevBuiltData.ExplicitDisposeIgnoreExceptions();
|
||||
prevBuiltData?.Release();
|
||||
|
||||
this.buildTask = EmptyTask;
|
||||
fontsAndLocks.EnsureCapacity(data.Substances.Sum(x => x.RelevantHandles.Count));
|
||||
|
|
@ -616,6 +650,8 @@ internal sealed partial class FontAtlasFactory
|
|||
FontAtlasBuiltData? res = null;
|
||||
nint atlasPtr = 0;
|
||||
BuildToolkit? toolkit = null;
|
||||
|
||||
IsBuildInProgressForTask.Value = true;
|
||||
try
|
||||
{
|
||||
res = new(this, scale);
|
||||
|
|
@ -740,6 +776,7 @@ internal sealed partial class FontAtlasFactory
|
|||
// ReSharper disable once ConstantConditionalAccessQualifier
|
||||
toolkit?.Dispose();
|
||||
this.buildQueued = false;
|
||||
IsBuildInProgressForTask.Value = false;
|
||||
}
|
||||
|
||||
unsafe bool ValidateMergeFontReferences(ImFontPtr replacementDstFont)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ namespace Dalamud.Interface.ManagedFontAtlas.Internals;
|
|||
/// </summary>
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
internal sealed partial class FontAtlasFactory
|
||||
: IServiceType, GamePrebakedFontHandle.IGameFontTextureProvider, IDisposable
|
||||
: IInternalDisposableService, GamePrebakedFontHandle.IGameFontTextureProvider
|
||||
{
|
||||
private readonly DisposeSafety.ScopedFinalizer scopedFinalizer = new();
|
||||
private readonly CancellationTokenSource cancellationTokenSource = new();
|
||||
|
|
@ -161,7 +161,7 @@ internal sealed partial class FontAtlasFactory
|
|||
this.dalamudAssetManager.IsStreamImmediatelyAvailable(DalamudAsset.LodestoneGameSymbol);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.cancellationTokenSource.Cancel();
|
||||
this.scopedFinalizer.Dispose();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Interface.Internal;
|
||||
|
|
@ -24,7 +25,6 @@ internal abstract class FontHandle : IFontHandle
|
|||
private static readonly ConditionalWeakTable<LocalPlugin, object> NonMainThreadFontAccessWarning = new();
|
||||
private static long nextNonMainThreadFontAccessWarningCheck;
|
||||
|
||||
private readonly InterfaceManager interfaceManager;
|
||||
private readonly List<IDisposable> pushedFonts = new(8);
|
||||
|
||||
private IFontHandleManager? manager;
|
||||
|
|
@ -36,7 +36,6 @@ internal abstract class FontHandle : IFontHandle
|
|||
/// <param name="manager">An instance of <see cref="IFontHandleManager"/>.</param>
|
||||
protected FontHandle(IFontHandleManager manager)
|
||||
{
|
||||
this.interfaceManager = Service<InterfaceManager>.Get();
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
|
|
@ -58,7 +57,11 @@ internal abstract class FontHandle : IFontHandle
|
|||
/// Gets the associated <see cref="IFontHandleManager"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="ObjectDisposedException">When the object has already been disposed.</exception>
|
||||
protected IFontHandleManager Manager => this.manager ?? throw new ObjectDisposedException(this.GetType().Name);
|
||||
protected IFontHandleManager Manager =>
|
||||
this.manager
|
||||
?? throw new ObjectDisposedException(
|
||||
this.GetType().Name,
|
||||
"Did you write `using (fontHandle)` instead of `using (fontHandle.Push())`?");
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
|
|
@ -122,7 +125,7 @@ internal abstract class FontHandle : IFontHandle
|
|||
}
|
||||
}
|
||||
|
||||
this.interfaceManager.EnqueueDeferredDispose(locked);
|
||||
Service<InterfaceManager>.Get().EnqueueDeferredDispose(locked);
|
||||
return locked.ImFont;
|
||||
}
|
||||
|
||||
|
|
@ -201,7 +204,7 @@ internal abstract class FontHandle : IFontHandle
|
|||
ThreadSafety.AssertMainThread();
|
||||
|
||||
// Warn if the client is not properly managing the pushed font stack.
|
||||
var cumulativePresentCalls = this.interfaceManager.CumulativePresentCalls;
|
||||
var cumulativePresentCalls = Service<InterfaceManager>.Get().CumulativePresentCalls;
|
||||
if (this.lastCumulativePresentCalls != cumulativePresentCalls)
|
||||
{
|
||||
this.lastCumulativePresentCalls = cumulativePresentCalls;
|
||||
|
|
@ -218,7 +221,7 @@ internal abstract class FontHandle : IFontHandle
|
|||
if (this.TryLock(out _) is { } locked)
|
||||
{
|
||||
font = locked.ImFont;
|
||||
this.interfaceManager.EnqueueDeferredDispose(locked);
|
||||
Service<InterfaceManager>.Get().EnqueueDeferredDispose(locked);
|
||||
}
|
||||
|
||||
var rented = SimplePushedFont.Rent(this.pushedFonts, font);
|
||||
|
|
@ -289,11 +292,15 @@ internal abstract class FontHandle : IFontHandle
|
|||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (Interlocked.Exchange(ref this.manager, null) is not { } managerToDisassociate)
|
||||
return;
|
||||
|
||||
if (this.pushedFonts.Count > 0)
|
||||
Log.Warning($"{nameof(IFontHandle)}.{nameof(IDisposable.Dispose)}: fonts were still in a stack.");
|
||||
this.Manager.FreeFontHandle(this);
|
||||
this.manager = null;
|
||||
|
||||
managerToDisassociate.FreeFontHandle(this);
|
||||
this.Disposed?.InvokeSafely();
|
||||
this.Disposed = null;
|
||||
this.ImFontChanged = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<ITitleScreenMenu>]
|
||||
#pragma warning restore SA1015
|
||||
internal class TitleScreenMenuPluginScoped : IDisposable, IServiceType, ITitleScreenMenu
|
||||
internal class TitleScreenMenuPluginScoped : IInternalDisposableService, ITitleScreenMenu
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly TitleScreenMenu titleScreenMenuService = Service<TitleScreenMenu>.Get();
|
||||
|
|
@ -204,7 +204,7 @@ internal class TitleScreenMenuPluginScoped : IDisposable, IServiceType, ITitleSc
|
|||
public IReadOnlyList<TitleScreenMenuEntry>? Entries => this.titleScreenMenuService.Entries;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
foreach (var entry in this.pluginEntries)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -9,12 +10,16 @@ using Dalamud.Game.ClientState.Conditions;
|
|||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Interface.FontIdentifier;
|
||||
using Dalamud.Interface.GameFonts;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Internal.ManagedAsserts;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Internal.Types;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
using ImGuiNET;
|
||||
using ImGuiScene;
|
||||
|
|
@ -29,11 +34,13 @@ namespace Dalamud.Interface;
|
|||
/// </summary>
|
||||
public sealed class UiBuilder : IDisposable
|
||||
{
|
||||
private readonly LocalPlugin localPlugin;
|
||||
private readonly Stopwatch stopwatch;
|
||||
private readonly HitchDetector hitchDetector;
|
||||
private readonly string namespaceName;
|
||||
private readonly InterfaceManager interfaceManager = Service<InterfaceManager>.Get();
|
||||
private readonly Framework framework = Service<Framework>.Get();
|
||||
private readonly ConcurrentDictionary<IActiveNotification, int> notifications = new();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||
|
|
@ -52,8 +59,10 @@ public sealed class UiBuilder : IDisposable
|
|||
/// You do not have to call this manually.
|
||||
/// </summary>
|
||||
/// <param name="namespaceName">The plugin namespace.</param>
|
||||
internal UiBuilder(string namespaceName)
|
||||
/// <param name="localPlugin">The relevant local plugin.</param>
|
||||
internal UiBuilder(string namespaceName, LocalPlugin localPlugin)
|
||||
{
|
||||
this.localPlugin = localPlugin;
|
||||
try
|
||||
{
|
||||
this.stopwatch = new Stopwatch();
|
||||
|
|
@ -507,9 +516,16 @@ public sealed class UiBuilder : IDisposable
|
|||
/// <returns>Handle to the game font which may or may not be available for use yet.</returns>
|
||||
[Obsolete($"Use {nameof(this.FontAtlas)}.{nameof(IFontAtlas.NewGameFontHandle)} instead.", false)]
|
||||
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
|
||||
public GameFontHandle GetGameFontHandle(GameFontStyle style) => new(
|
||||
(GamePrebakedFontHandle)this.FontAtlas.NewGameFontHandle(style),
|
||||
Service<FontAtlasFactory>.Get());
|
||||
public GameFontHandle GetGameFontHandle(GameFontStyle style)
|
||||
{
|
||||
var prevValue = FontAtlasFactory.IsBuildInProgressForTask.Value;
|
||||
FontAtlasFactory.IsBuildInProgressForTask.Value = false;
|
||||
var v = new GameFontHandle(
|
||||
(GamePrebakedFontHandle)this.FontAtlas.NewGameFontHandle(style),
|
||||
Service<FontAtlasFactory>.Get());
|
||||
FontAtlasFactory.IsBuildInProgressForTask.Value = prevValue;
|
||||
return v;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call this to queue a rebuild of the font atlas.<br/>
|
||||
|
|
@ -556,22 +572,50 @@ public sealed class UiBuilder : IDisposable
|
|||
/// <param name="title">The title of the notification.</param>
|
||||
/// <param name="type">The type of the notification.</param>
|
||||
/// <param name="msDelay">The time the notification should be displayed for.</param>
|
||||
public void AddNotification(
|
||||
string content, string? title = null, NotificationType type = NotificationType.None, uint msDelay = 3000)
|
||||
[Obsolete($"Use {nameof(INotificationManager)}.", false)]
|
||||
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
|
||||
public async void AddNotification(
|
||||
string content,
|
||||
string? title = null,
|
||||
NotificationType type = NotificationType.None,
|
||||
uint msDelay = 3000)
|
||||
{
|
||||
Service<NotificationManager>
|
||||
.GetAsync()
|
||||
.ContinueWith(task =>
|
||||
var nm = await Service<NotificationManager>.GetAsync();
|
||||
var an = nm.AddNotification(
|
||||
new()
|
||||
{
|
||||
if (task.IsCompletedSuccessfully)
|
||||
task.Result.AddNotification(content, title, type, msDelay);
|
||||
});
|
||||
Content = content,
|
||||
Title = title,
|
||||
Type = type,
|
||||
InitialDuration = TimeSpan.FromMilliseconds(msDelay),
|
||||
},
|
||||
this.localPlugin);
|
||||
_ = this.notifications.TryAdd(an, 0);
|
||||
an.Dismiss += a => this.notifications.TryRemove(a.Notification, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregister the UiBuilder. Do not call this in plugin code.
|
||||
/// </summary>
|
||||
void IDisposable.Dispose() => this.scopedFinalizer.Dispose();
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
this.scopedFinalizer.Dispose();
|
||||
|
||||
// Taken from NotificationManagerPluginScoped.
|
||||
// TODO: remove on API 10.
|
||||
while (!this.notifications.IsEmpty)
|
||||
{
|
||||
foreach (var n in this.notifications.Keys)
|
||||
{
|
||||
this.notifications.TryRemove(n, out _);
|
||||
((ActiveNotification)n).RemoveNonDalamudInvocations();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Clean up resources allocated by this instance of <see cref="UiBuilder"/>.</summary>
|
||||
/// <remarks>Dalamud internal use only.</remarks>
|
||||
internal void DisposeInternal() => this.scopedFinalizer.Dispose();
|
||||
|
||||
/// <summary>
|
||||
/// Open the registered configuration UI, if it exists.
|
||||
|
|
|
|||
|
|
@ -493,12 +493,13 @@ public static class ImGuiHelpers
|
|||
/// <returns>The range array that can be used for <see cref="SafeFontConfig.GlyphRanges"/>.</returns>
|
||||
public static ushort[] CreateImGuiRangesFrom(IEnumerable<UnicodeRange> ranges) =>
|
||||
ranges
|
||||
.Where(x => x.FirstCodePoint <= ushort.MaxValue)
|
||||
.Select(x => (First: Math.Max(x.FirstCodePoint, 1), Last: x.FirstCodePoint + x.Length))
|
||||
.Where(x => x.First <= ushort.MaxValue && x.First <= x.Last)
|
||||
.SelectMany(
|
||||
x => new[]
|
||||
{
|
||||
(ushort)Math.Min(x.FirstCodePoint, ushort.MaxValue),
|
||||
(ushort)Math.Min(x.FirstCodePoint + x.Length, ushort.MaxValue),
|
||||
(ushort)Math.Min(x.First, ushort.MaxValue),
|
||||
(ushort)Math.Min(x.Last, ushort.MaxValue),
|
||||
})
|
||||
.Append((ushort)0)
|
||||
.ToArray();
|
||||
|
|
|
|||
|
|
@ -96,6 +96,17 @@ internal class ServiceScopeImpl : IServiceScope
|
|||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var createdObject in this.scopeCreatedObjects.OfType<IDisposable>()) createdObject.Dispose();
|
||||
foreach (var createdObject in this.scopeCreatedObjects)
|
||||
{
|
||||
switch (createdObject)
|
||||
{
|
||||
case IInternalDisposableService d:
|
||||
d.DisposeService();
|
||||
break;
|
||||
case IDisposable d:
|
||||
d.Dispose();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ public class Localization : IServiceType
|
|||
/// <param name="useEmbedded">Use embedded loc resource files.</param>
|
||||
public Localization(string locResourceDirectory, string locResourcePrefix = "", bool useEmbedded = false)
|
||||
{
|
||||
this.DalamudLanguageCultureInfo = CultureInfo.InvariantCulture;
|
||||
this.locResourceDirectory = locResourceDirectory;
|
||||
this.locResourcePrefix = locResourcePrefix;
|
||||
this.useEmbedded = useEmbedded;
|
||||
|
|
@ -61,7 +62,24 @@ public class Localization : IServiceType
|
|||
/// <summary>
|
||||
/// Event that occurs when the language is changed.
|
||||
/// </summary>
|
||||
public event LocalizationChangedDelegate LocalizationChanged;
|
||||
public event LocalizationChangedDelegate? LocalizationChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance of <see cref="CultureInfo"/> that corresponds to the language configured from Dalamud Settings.
|
||||
/// </summary>
|
||||
public CultureInfo DalamudLanguageCultureInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance of <see cref="CultureInfo"/> that corresponds to <paramref name="langCode"/>.
|
||||
/// </summary>
|
||||
/// <param name="langCode">The language code which should be in <see cref="ApplicableLangCodes"/>.</param>
|
||||
/// <returns>The corresponding instance of <see cref="CultureInfo"/>.</returns>
|
||||
public static CultureInfo GetCultureInfoFromLangCode(string langCode) =>
|
||||
CultureInfo.GetCultureInfo(langCode switch
|
||||
{
|
||||
"tw" => "zh-tw",
|
||||
_ => langCode,
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Search the set-up localization data for the provided assembly for the given string key and return it.
|
||||
|
|
@ -108,6 +126,7 @@ public class Localization : IServiceType
|
|||
/// </summary>
|
||||
public void SetupWithFallbacks()
|
||||
{
|
||||
this.DalamudLanguageCultureInfo = CultureInfo.InvariantCulture;
|
||||
this.LocalizationChanged?.Invoke(FallbackLangCode);
|
||||
Loc.SetupWithFallbacks(this.assembly);
|
||||
}
|
||||
|
|
@ -124,6 +143,7 @@ public class Localization : IServiceType
|
|||
return;
|
||||
}
|
||||
|
||||
this.DalamudLanguageCultureInfo = GetCultureInfoFromLangCode(langCode);
|
||||
this.LocalizationChanged?.Invoke(langCode);
|
||||
|
||||
try
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ namespace Dalamud.Logging.Internal;
|
|||
/// Class responsible for tracking asynchronous tasks.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class TaskTracker : IDisposable, IServiceType
|
||||
internal class TaskTracker : IInternalDisposableService
|
||||
{
|
||||
private static readonly ModuleLog Log = new("TT");
|
||||
private static readonly List<TaskInfo> TrackedTasksInternal = new();
|
||||
|
|
@ -120,7 +120,7 @@ internal class TaskTracker : IDisposable, IServiceType
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
// NET8 CHORE
|
||||
// this.scheduleAndStartHook?.Dispose();
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ namespace Dalamud.Logging;
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IPluginLog>]
|
||||
#pragma warning restore SA1015
|
||||
internal class ScopedPluginLogService : IServiceType, IPluginLog, IDisposable
|
||||
internal class ScopedPluginLogService : IServiceType, IPluginLog
|
||||
{
|
||||
private readonly LocalPlugin localPlugin;
|
||||
|
||||
|
|
@ -53,12 +53,6 @@ internal class ScopedPluginLogService : IServiceType, IPluginLog, IDisposable
|
|||
/// </summary>
|
||||
public ILogger Logger { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Fatal(string messageTemplate, params object[] values) =>
|
||||
this.Write(LogEventLevel.Fatal, null, messageTemplate, values);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ namespace Dalamud.Networking.Http;
|
|||
/// awareness.
|
||||
/// </summary>
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
internal class HappyHttpClient : IDisposable, IServiceType
|
||||
internal class HappyHttpClient : IInternalDisposableService
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HappyHttpClient"/> class.
|
||||
|
|
@ -58,7 +58,7 @@ internal class HappyHttpClient : IDisposable, IServiceType
|
|||
public HappyEyeballsCallback SharedHappyEyeballsCallback { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IDisposable.Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.SharedHttpClient.Dispose();
|
||||
this.SharedHappyEyeballsCallback.Dispose();
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ public sealed class DalamudPluginInterface : IDisposable
|
|||
var dataManager = Service<DataManager>.Get();
|
||||
var localization = Service<Localization>.Get();
|
||||
|
||||
this.UiBuilder = new UiBuilder(plugin.Name);
|
||||
this.UiBuilder = new UiBuilder(plugin.Name, plugin);
|
||||
|
||||
this.configs = Service<PluginManager>.Get().PluginConfigs;
|
||||
this.Reason = reason;
|
||||
|
|
@ -452,26 +452,28 @@ public sealed class DalamudPluginInterface : IDisposable
|
|||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Unregister your plugin and dispose all references.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="Dispose"/>
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
this.UiBuilder.ExplicitDispose();
|
||||
Service<ChatGui>.Get().RemoveChatLinkHandler(this.plugin.InternalName);
|
||||
Service<Localization>.Get().LocalizationChanged -= this.OnLocalizationChanged;
|
||||
Service<DalamudConfiguration>.Get().DalamudConfigurationSaved -= this.OnDalamudConfigurationSaved;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete implicit dispose implementation. Should not be used.
|
||||
/// </summary>
|
||||
[Obsolete("Do not dispose \"DalamudPluginInterface\".", true)]
|
||||
/// <summary>This function will do nothing. Dalamud will dispose this object on plugin unload.</summary>
|
||||
[Obsolete("This function will do nothing. Dalamud will dispose this object on plugin unload.", true)]
|
||||
public void Dispose()
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
/// <summary>Unregister the plugin and dispose all references.</summary>
|
||||
/// <remarks>Dalamud internal use only.</remarks>
|
||||
internal void DisposeInternal()
|
||||
{
|
||||
Service<ChatGui>.Get().RemoveChatLinkHandler(this.plugin.InternalName);
|
||||
Service<Localization>.Get().LocalizationChanged -= this.OnLocalizationChanged;
|
||||
Service<DalamudConfiguration>.Get().DalamudConfigurationSaved -= this.OnDalamudConfigurationSaved;
|
||||
this.UiBuilder.DisposeInternal();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispatch the active plugins changed event.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ namespace Dalamud.Plugin.Internal;
|
|||
[InherentDependency<DataShare>]
|
||||
|
||||
#pragma warning restore SA1015
|
||||
internal partial class PluginManager : IDisposable, IServiceType
|
||||
internal partial class PluginManager : IInternalDisposableService
|
||||
{
|
||||
/// <summary>
|
||||
/// Default time to wait between plugin unload and plugin assembly unload.
|
||||
|
|
@ -371,7 +371,7 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
var disposablePlugins =
|
||||
this.installedPluginsList.Where(plugin => plugin.State is PluginState.Loaded or PluginState.LoadError).ToArray();
|
||||
|
|
@ -411,7 +411,16 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
// Now that we've waited enough, dispose the whole plugin.
|
||||
// Since plugins should have been unloaded above, this should be done quickly.
|
||||
foreach (var plugin in disposablePlugins)
|
||||
plugin.ExplicitDisposeIgnoreExceptions($"Error disposing {plugin.Name}", Log);
|
||||
{
|
||||
try
|
||||
{
|
||||
plugin.Dispose();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"Error disposing {plugin.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NET8 CHORE
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ namespace Dalamud.Plugin.Internal.Profiles;
|
|||
/// Service responsible for profile-related chat commands.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class ProfileCommandHandler : IServiceType, IDisposable
|
||||
internal class ProfileCommandHandler : IInternalDisposableService
|
||||
{
|
||||
private readonly CommandManager cmd;
|
||||
private readonly ProfileManager profileManager;
|
||||
|
|
@ -69,7 +69,7 @@ internal class ProfileCommandHandler : IServiceType, IDisposable
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.cmd.RemoveHandler("/xlenablecollection");
|
||||
this.cmd.RemoveHandler("/xldisablecollection");
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Internal.Types.Manifest;
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@ internal class LocalPlugin : IDisposable
|
|||
this.instance = null;
|
||||
}
|
||||
|
||||
this.DalamudInterface?.ExplicitDispose();
|
||||
this.DalamudInterface?.DisposeInternal();
|
||||
this.DalamudInterface = null;
|
||||
|
||||
this.ServiceScope?.Dispose();
|
||||
|
|
@ -427,7 +427,7 @@ internal class LocalPlugin : IDisposable
|
|||
if (this.instance == null)
|
||||
{
|
||||
this.State = PluginState.LoadError;
|
||||
this.DalamudInterface.ExplicitDispose();
|
||||
this.DalamudInterface.DisposeInternal();
|
||||
Log.Error(
|
||||
$"Error while loading {this.Name}, failed to bind and call the plugin constructor");
|
||||
return;
|
||||
|
|
@ -500,7 +500,7 @@ internal class LocalPlugin : IDisposable
|
|||
|
||||
this.instance = null;
|
||||
|
||||
this.DalamudInterface?.ExplicitDispose();
|
||||
this.DalamudInterface?.DisposeInternal();
|
||||
this.DalamudInterface = null;
|
||||
|
||||
this.ServiceScope?.Dispose();
|
||||
|
|
|
|||
12
Dalamud/Plugin/Services/INotificationManager.cs
Normal file
12
Dalamud/Plugin/Services/INotificationManager.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
using Dalamud.Interface.ImGuiNotification;
|
||||
|
||||
namespace Dalamud.Plugin.Services;
|
||||
|
||||
/// <summary>Manager for notifications provided by Dalamud using ImGui.</summary>
|
||||
public interface INotificationManager
|
||||
{
|
||||
/// <summary>Adds a notification.</summary>
|
||||
/// <param name="notification">The new notification.</param>
|
||||
/// <returns>The added notification.</returns>
|
||||
IActiveNotification AddNotification(Notification notification);
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ using System.Diagnostics;
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
|
@ -175,7 +176,8 @@ internal static class ServiceManager
|
|||
foreach (var serviceType in GetConcreteServiceTypes())
|
||||
{
|
||||
var serviceKind = serviceType.GetServiceKind();
|
||||
Debug.Assert(serviceKind != ServiceKind.None, $"Service<{serviceType.FullName}> did not specify a kind");
|
||||
|
||||
CheckServiceTypeContracts(serviceType);
|
||||
|
||||
// Let IoC know about the interfaces this service implements
|
||||
serviceContainer.RegisterInterfaces(serviceType);
|
||||
|
|
@ -514,6 +516,44 @@ internal static class ServiceManager
|
|||
return ServiceKind.ProvidedService;
|
||||
}
|
||||
|
||||
/// <summary>Validate service type contracts, and throws exceptions accordingly.</summary>
|
||||
/// <param name="serviceType">An instance of <see cref="Type"/> that is supposed to be a service type.</param>
|
||||
/// <remarks>Does nothing on non-debug builds.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void CheckServiceTypeContracts(Type serviceType)
|
||||
{
|
||||
#if DEBUG
|
||||
try
|
||||
{
|
||||
if (!serviceType.IsAssignableTo(typeof(IServiceType)))
|
||||
throw new InvalidOperationException($"Non-{nameof(IServiceType)} passed.");
|
||||
if (serviceType.GetServiceKind() == ServiceKind.None)
|
||||
throw new InvalidOperationException("Service type is not specified.");
|
||||
|
||||
var isServiceDisposable =
|
||||
serviceType.IsAssignableTo(typeof(IInternalDisposableService));
|
||||
var isAnyDisposable =
|
||||
isServiceDisposable
|
||||
|| serviceType.IsAssignableTo(typeof(IDisposable))
|
||||
|| serviceType.IsAssignableTo(typeof(IAsyncDisposable));
|
||||
if (isAnyDisposable && !isServiceDisposable)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"A service must be an {nameof(IInternalDisposableService)} without specifying " +
|
||||
$"{nameof(IDisposable)} nor {nameof(IAsyncDisposable)} if it is purely meant to be a service, " +
|
||||
$"or an {nameof(IPublicDisposableService)} if it also is allowed to be constructed not as a " +
|
||||
$"service to be used elsewhere and has to offer {nameof(IDisposable)} or " +
|
||||
$"{nameof(IAsyncDisposable)}. See {nameof(ReliableFileStorage)} for an example of " +
|
||||
$"{nameof(IPublicDisposableService)}.");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidOperationException($"{serviceType.Name}: {e.Message}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that this constructor will be called for early initialization.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -65,6 +65,12 @@ internal static class Service<T> where T : IServiceType
|
|||
None,
|
||||
}
|
||||
|
||||
/// <summary>Does nothing.</summary>
|
||||
/// <remarks>Used to invoke the static ctor.</remarks>
|
||||
public static void Nop()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the type in the service locator to the given object.
|
||||
/// </summary>
|
||||
|
|
@ -72,6 +78,8 @@ internal static class Service<T> where T : IServiceType
|
|||
public static void Provide(T obj)
|
||||
{
|
||||
ServiceManager.Log.Debug("Service<{0}>: Provided", typeof(T).Name);
|
||||
if (obj is IPublicDisposableService pds)
|
||||
pds.MarkDisposeOnlyFromService();
|
||||
instanceTcs.SetResult(obj);
|
||||
}
|
||||
|
||||
|
|
@ -297,23 +305,26 @@ internal static class Service<T> where T : IServiceType
|
|||
if (!instanceTcs.Task.IsCompletedSuccessfully)
|
||||
return;
|
||||
|
||||
var instance = instanceTcs.Task.Result;
|
||||
if (instance is IDisposable disposable)
|
||||
switch (instanceTcs.Task.Result)
|
||||
{
|
||||
ServiceManager.Log.Debug("Service<{0}>: Disposing", typeof(T).Name);
|
||||
try
|
||||
{
|
||||
disposable.Dispose();
|
||||
ServiceManager.Log.Debug("Service<{0}>: Disposed", typeof(T).Name);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ServiceManager.Log.Warning(e, "Service<{0}>: Dispose failure", typeof(T).Name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ServiceManager.Log.Debug("Service<{0}>: Unset", typeof(T).Name);
|
||||
case IInternalDisposableService d:
|
||||
ServiceManager.Log.Debug("Service<{0}>: Disposing", typeof(T).Name);
|
||||
try
|
||||
{
|
||||
d.DisposeService();
|
||||
ServiceManager.Log.Debug("Service<{0}>: Disposed", typeof(T).Name);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ServiceManager.Log.Warning(e, "Service<{0}>: Dispose failure", typeof(T).Name);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
ServiceManager.CheckServiceTypeContracts(typeof(T));
|
||||
ServiceManager.Log.Debug("Service<{0}>: Unset", typeof(T).Name);
|
||||
break;
|
||||
}
|
||||
|
||||
instanceTcs = new TaskCompletionSource<T>();
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ namespace Dalamud.Storage.Assets;
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IDalamudAssetManager>]
|
||||
#pragma warning restore SA1015
|
||||
internal sealed class DalamudAssetManager : IServiceType, IDisposable, IDalamudAssetManager
|
||||
internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamudAssetManager
|
||||
{
|
||||
private const int DownloadAttemptCount = 10;
|
||||
private const int RenameAttemptCount = 10;
|
||||
|
|
@ -67,7 +67,13 @@ internal sealed class DalamudAssetManager : IServiceType, IDisposable, IDalamudA
|
|||
.Where(x => x.GetAttribute<DalamudAssetAttribute>()?.Required is true)
|
||||
.Select(this.CreateStreamAsync)
|
||||
.Select(x => x.ToContentDisposedTask()))
|
||||
.ContinueWith(_ => loadTimings.Dispose()),
|
||||
.ContinueWith(
|
||||
r =>
|
||||
{
|
||||
loadTimings.Dispose();
|
||||
return r;
|
||||
})
|
||||
.Unwrap(),
|
||||
"Prevent Dalamud from loading more stuff, until we've ensured that all required assets are available.");
|
||||
|
||||
Task.WhenAll(
|
||||
|
|
@ -83,7 +89,7 @@ internal sealed class DalamudAssetManager : IServiceType, IDisposable, IDalamudA
|
|||
public IDalamudTextureWrap Empty4X4 => this.GetDalamudTextureWrap(DalamudAsset.Empty4X4);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
lock (this.syncRoot)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -22,17 +22,22 @@ namespace Dalamud.Storage;
|
|||
/// This is not an early-loaded service, as it is needed before they are initialized.
|
||||
/// </remarks>
|
||||
[ServiceManager.ProvidedService]
|
||||
public class ReliableFileStorage : IServiceType, IDisposable
|
||||
[Api10ToDo("Make internal and IInternalDisposableService, and remove #pragma guard from the caller.")]
|
||||
public class ReliableFileStorage : IPublicDisposableService
|
||||
{
|
||||
private static readonly ModuleLog Log = new("VFS");
|
||||
|
||||
private readonly object syncRoot = new();
|
||||
|
||||
private SQLiteConnection? db;
|
||||
private bool isService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReliableFileStorage"/> class.
|
||||
/// </summary>
|
||||
/// <param name="vfsDbPath">Path to the VFS.</param>
|
||||
[Obsolete("Dalamud internal use only.", false)]
|
||||
[Api10ToDo("Make internal, and remove #pragma guard from the caller.")]
|
||||
public ReliableFileStorage(string vfsDbPath)
|
||||
{
|
||||
var databasePath = Path.Combine(vfsDbPath, "dalamudVfs.db");
|
||||
|
|
@ -60,7 +65,7 @@ public class ReliableFileStorage : IServiceType, IDisposable
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Check if a file exists.
|
||||
/// This will return true if the file does not exist on the filesystem, but in the transparent backup.
|
||||
|
|
@ -288,9 +293,20 @@ public class ReliableFileStorage : IServiceType, IDisposable
|
|||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.db?.Dispose();
|
||||
if (!this.isService)
|
||||
this.DisposeCore();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
if (this.isService)
|
||||
this.DisposeCore();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IPublicDisposableService.MarkDisposeOnlyFromService() => this.isService = true;
|
||||
|
||||
/// <summary>
|
||||
/// Replace possible non-portable parts of a path with portable versions.
|
||||
/// </summary>
|
||||
|
|
@ -312,6 +328,8 @@ public class ReliableFileStorage : IServiceType, IDisposable
|
|||
this.db.CreateTable<DbFile>();
|
||||
}
|
||||
|
||||
private void DisposeCore() => this.db?.Dispose();
|
||||
|
||||
private class DbFile
|
||||
{
|
||||
[PrimaryKey]
|
||||
|
|
|
|||
|
|
@ -11,9 +11,19 @@ internal sealed class Api10ToDoAttribute : Attribute
|
|||
/// </summary>
|
||||
public const string DeleteCompatBehavior = "Delete. This is for making API 9 plugins work.";
|
||||
|
||||
/// <summary>
|
||||
/// Marks that this should be moved to an another namespace.
|
||||
/// </summary>
|
||||
public const string MoveNamespace = "Move to another namespace.";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Api10ToDoAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="what">The explanation.</param>
|
||||
public Api10ToDoAttribute(string what) => _ = what;
|
||||
/// <param name="what2">The explanation 2.</param>
|
||||
public Api10ToDoAttribute(string what, string what2 = "")
|
||||
{
|
||||
_ = what;
|
||||
_ = what2;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
125
Dalamud/Utility/DateTimeSpanExtensions.cs
Normal file
125
Dalamud/Utility/DateTimeSpanExtensions.cs
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
|
||||
using CheapLoc;
|
||||
|
||||
using Dalamud.Logging.Internal;
|
||||
|
||||
namespace Dalamud.Utility;
|
||||
|
||||
/// <summary>
|
||||
/// Utility functions for <see cref="DateTime"/> and <see cref="TimeSpan"/>.
|
||||
/// </summary>
|
||||
public static class DateTimeSpanExtensions
|
||||
{
|
||||
private static readonly ModuleLog Log = new(nameof(DateTimeSpanExtensions));
|
||||
|
||||
private static ParsedRelativeFormatStrings? relativeFormatStringLong;
|
||||
|
||||
private static ParsedRelativeFormatStrings? relativeFormatStringShort;
|
||||
|
||||
/// <summary>Formats an instance of <see cref="DateTime"/> as a localized absolute time.</summary>
|
||||
/// <param name="when">When.</param>
|
||||
/// <returns>The formatted string.</returns>
|
||||
/// <remarks>The string will be formatted according to Square Enix Account region settings, if Dalamud default
|
||||
/// language is English.</remarks>
|
||||
public static unsafe string LocAbsolute(this DateTime when)
|
||||
{
|
||||
var culture = Service<Localization>.GetNullable()?.DalamudLanguageCultureInfo ?? CultureInfo.InvariantCulture;
|
||||
if (!Equals(culture, CultureInfo.InvariantCulture))
|
||||
return when.ToString("G", culture);
|
||||
|
||||
var framework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance();
|
||||
var region = 0;
|
||||
if (framework is not null)
|
||||
region = framework->Region;
|
||||
return region switch
|
||||
{
|
||||
1 => when.ToString("MM/dd/yyyy HH:mm:ss"), // na
|
||||
2 => when.ToString("dd-mm-yyyy HH:mm:ss"), // eu
|
||||
_ => when.ToString("yyyy-MM-dd HH:mm:ss"), // jp(0), cn(3), kr(4), and other possible errorneous cases
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Formats an instance of <see cref="DateTime"/> as a localized relative time.</summary>
|
||||
/// <param name="when">When.</param>
|
||||
/// <returns>The formatted string.</returns>
|
||||
public static string LocRelativePastLong(this DateTime when)
|
||||
{
|
||||
var loc = Loc.Localize(
|
||||
"DateTimeSpanExtensions.RelativeFormatStringsLong",
|
||||
"172800,{0:%d} days ago\n86400,yesterday\n7200,{0:%h} hours ago\n3600,an hour ago\n120,{0:%m} minutes ago\n60,a minute ago\n2,{0:%s} seconds ago\n1,a second ago\n-Infinity,just now");
|
||||
Debug.Assert(loc != null, "loc != null");
|
||||
|
||||
if (relativeFormatStringLong?.FormatStringLoc != loc)
|
||||
relativeFormatStringLong ??= new(loc);
|
||||
|
||||
return relativeFormatStringLong.Format(DateTime.Now - when);
|
||||
}
|
||||
|
||||
/// <summary>Formats an instance of <see cref="DateTime"/> as a localized relative time.</summary>
|
||||
/// <param name="when">When.</param>
|
||||
/// <returns>The formatted string.</returns>
|
||||
public static string LocRelativePastShort(this DateTime when)
|
||||
{
|
||||
var loc = Loc.Localize(
|
||||
"DateTimeSpanExtensions.RelativeFormatStringsShort",
|
||||
"86400,{0:%d}d\n3600,{0:%h}h\n60,{0:%m}m\n1,{0:%s}s\n-Infinity,now");
|
||||
Debug.Assert(loc != null, "loc != null");
|
||||
|
||||
if (relativeFormatStringShort?.FormatStringLoc != loc)
|
||||
relativeFormatStringShort = new(loc);
|
||||
|
||||
return relativeFormatStringShort.Format(DateTime.Now - when);
|
||||
}
|
||||
|
||||
private sealed class ParsedRelativeFormatStrings
|
||||
{
|
||||
private readonly List<(float MinSeconds, string FormatString)> formatStrings = new();
|
||||
|
||||
public ParsedRelativeFormatStrings(string value)
|
||||
{
|
||||
this.FormatStringLoc = value;
|
||||
foreach (var line in value.Split("\n"))
|
||||
{
|
||||
var sep = line.IndexOf(',');
|
||||
if (sep < 0)
|
||||
{
|
||||
Log.Error("A line without comma has been found: {line}", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!float.TryParse(
|
||||
line.AsSpan(0, sep),
|
||||
NumberStyles.Float,
|
||||
CultureInfo.InvariantCulture,
|
||||
out var seconds))
|
||||
{
|
||||
Log.Error("Could not parse the duration: {line}", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.formatStrings.Add((seconds, line[(sep + 1)..]));
|
||||
}
|
||||
|
||||
this.formatStrings.Sort((a, b) => b.MinSeconds.CompareTo(a.MinSeconds));
|
||||
}
|
||||
|
||||
public string FormatStringLoc { get; }
|
||||
|
||||
/// <summary>Formats an instance of <see cref="TimeSpan"/> as a localized string.</summary>
|
||||
/// <param name="ts">The duration.</param>
|
||||
/// <returns>The formatted string.</returns>
|
||||
public string Format(TimeSpan ts)
|
||||
{
|
||||
foreach (var (minSeconds, formatString) in this.formatStrings)
|
||||
{
|
||||
if (ts.TotalSeconds >= minSeconds)
|
||||
return string.Format(formatString, ts);
|
||||
}
|
||||
|
||||
return this.formatStrings[^1].FormatString.Format(ts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -70,7 +70,16 @@ public static class DisposeSafety
|
|||
r =>
|
||||
{
|
||||
if (!r.IsCompletedSuccessfully)
|
||||
return ignoreAllExceptions ? Task.CompletedTask : r;
|
||||
{
|
||||
if (ignoreAllExceptions)
|
||||
{
|
||||
_ = r.Exception;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
r.Result.Dispose();
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ using Dalamud.Game.ClientState.Objects.SubKinds;
|
|||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Logging.Internal;
|
||||
using ImGuiNET;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Serilog;
|
||||
|
|
@ -638,42 +637,6 @@ public static class Util
|
|||
if (!Windows.Win32.PInvoke.MoveFileEx(tempPath, path, MOVE_FILE_FLAGS.MOVEFILE_REPLACE_EXISTING | MOVE_FILE_FLAGS.MOVEFILE_WRITE_THROUGH))
|
||||
throw new Win32Exception();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose this object.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to dispose.</param>
|
||||
/// <typeparam name="T">The type of object to dispose.</typeparam>
|
||||
internal static void ExplicitDispose<T>(this T obj) where T : IDisposable
|
||||
{
|
||||
obj.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose this object.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to dispose.</param>
|
||||
/// <param name="logMessage">Log message to print, if specified and an error occurs.</param>
|
||||
/// <param name="moduleLog">Module logger, if any.</param>
|
||||
/// <typeparam name="T">The type of object to dispose.</typeparam>
|
||||
internal static void ExplicitDisposeIgnoreExceptions<T>(
|
||||
this T obj, string? logMessage = null, ModuleLog? moduleLog = null) where T : IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
obj.Dispose();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (logMessage == null)
|
||||
return;
|
||||
|
||||
if (moduleLog != null)
|
||||
moduleLog.Error(e, logMessage);
|
||||
else
|
||||
Log.Error(e, logMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a random, inoffensive, human-friendly string.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue