diff --git a/Dalamud/Interface/ImGuiNotification/IActiveNotification.cs b/Dalamud/Interface/ImGuiNotification/IActiveNotification.cs
index e677471b4..8ed0d1e20 100644
--- a/Dalamud/Interface/ImGuiNotification/IActiveNotification.cs
+++ b/Dalamud/Interface/ImGuiNotification/IActiveNotification.cs
@@ -56,7 +56,7 @@ public interface IActiveNotification : INotification
/// from .
///
/// 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.
+ /// via another call to this function or overwriting the property. You do not have to dispose it yourself.
/// If is not null, then calling this function will simply dispose the
/// passed without actually updating the icon.
///
@@ -68,8 +68,8 @@ public interface IActiveNotification : INotification
/// revert back to the icon specified from .
///
/// The texture resulted from the 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 the
- /// resulted instance of yourself.
+ /// is dismissed or a new different texture is set via another call to this function over overwriting the property.
+ /// You do not have to dispose the resulted instance of yourself.
/// If the task fails for any reason, the exception will be silently ignored and the icon specified from
/// will be used instead.
/// If is not null, then calling this function will simply dispose the
@@ -77,6 +77,38 @@ public interface IActiveNotification : INotification
///
void SetIconTexture(Task? textureWrapTask);
+ /// Sets the icon from , overriding the icon.
+ /// The new texture wrap to use, or null to clear and revert back to the icon specified
+ /// from .
+ /// Whether to keep the passed not disposed.
+ ///
+ /// If is false, 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.
+ /// If is not null and is false, then
+ /// calling this function will simply dispose the passed without actually updating
+ /// the icon.
+ ///
+ void SetIconTexture(IDalamudTextureWrap? textureWrap, bool leaveOpen);
+
+ /// Sets the icon from , overriding the icon, once the given task
+ /// completes.
+ /// The task that will result in a new texture wrap to use, or null to clear and
+ /// revert back to the icon specified from .
+ /// Whether to keep the result from the passed not
+ /// disposed.
+ ///
+ /// If is false, the texture resulted from the 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 the resulted instance of
+ /// yourself.
+ /// If the task fails for any reason, the exception will be silently ignored and the icon specified from
+ /// will be used instead.
+ /// If is not null, then calling this function will simply dispose the
+ /// result of the passed without actually updating the icon.
+ ///
+ void SetIconTexture(Task? textureWrapTask, bool leaveOpen);
+
/// Generates a new value to use for .
/// The new value.
internal static long CreateNewId() => Interlocked.Increment(ref idCounter);
diff --git a/Dalamud/Interface/ImGuiNotification/INotification.cs b/Dalamud/Interface/ImGuiNotification/INotification.cs
index f9a043c0b..af34e0a1b 100644
--- a/Dalamud/Interface/ImGuiNotification/INotification.cs
+++ b/Dalamud/Interface/ImGuiNotification/INotification.cs
@@ -22,20 +22,43 @@ public interface INotification
/// Gets or sets the type of the notification.
NotificationType Type { get; set; }
- /// Gets or sets the icon source.
- /// Use or
+ /// Gets or sets the icon source, in case is not set or the task has faulted.
+ ///
+ INotificationIcon? Icon { get; set; }
+
+ /// Gets or sets a texture wrap that will be used in place of if set.
+ ///
+ /// A texture wrap set via this property will NOT be disposed when the notification is dismissed.
+ /// Use or
/// to use a texture, after calling
/// . Call either of those functions with null to revert
- /// the effective icon back to this property.
- INotificationIcon? Icon { get; set; }
+ /// the effective icon back to this property.
+ /// This property and are bound together. If the task is not null but
+ /// is false (because the task is still in progress or faulted,)
+ /// the property will return null. Setting this property will set to a new
+ /// completed with the new value as its result.
+ ///
+ public IDalamudTextureWrap? IconTexture { get; set; }
+
+ /// Gets or sets a task that results in a texture wrap that will be used in place of if
+ /// available.
+ ///
+ /// A texture wrap set via this property will NOT be disposed when the notification is dismissed.
+ /// Use or
+ /// to use a texture, after calling
+ /// . Call either of those functions with null to revert
+ /// the effective icon back to this property.
+ /// This property and are bound together.
+ ///
+ Task? IconTextureTask { get; set; }
/// Gets or sets the hard expiry.
///
/// Setting this value will override and , in that
/// the notification will be dismissed when this expiry expires.
/// Set to to make only take effect.
- /// If neither nor is not MaxValue, then the notification
- /// will not expire after a set time. It must be explicitly dismissed by the user of via calling
+ /// If both and are MaxValue, then the notification
+ /// will not expire after a set time. It must be explicitly dismissed by the user or via calling
/// .
/// Updating this value will reset the dismiss timer.
///
diff --git a/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.ImGui.cs b/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.ImGui.cs
index 6702f3e4b..fdccaab7b 100644
--- a/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.ImGui.cs
+++ b/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.ImGui.cs
@@ -404,7 +404,7 @@ internal sealed partial class ActiveNotification
var maxCoord = minCoord + size;
var iconColor = this.Type.ToColor();
- if (NotificationUtilities.DrawIconFrom(minCoord, maxCoord, this.iconTextureWrap))
+ if (NotificationUtilities.DrawIconFrom(minCoord, maxCoord, this.IconTextureTask))
return;
if (this.Icon?.DrawIcon(minCoord, maxCoord, iconColor) is true)
diff --git a/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.cs b/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.cs
index 5ae7de5f7..f1084fd20 100644
--- a/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.cs
+++ b/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.cs
@@ -1,5 +1,4 @@
using System.Runtime.Loader;
-using System.Threading;
using System.Threading.Tasks;
using Dalamud.Configuration.Internal;
@@ -24,15 +23,15 @@ internal sealed partial class ActiveNotification : IActiveNotification
private readonly Easing progressEasing;
private readonly Easing expandoEasing;
+ /// Whether to call on .
+ private bool hasIconTextureOwnership;
+
/// Gets the time of starting to count the timer for the expiration.
private DateTime lastInterestTime;
/// Gets the extended expiration time from .
private DateTime extendedExpiry;
- /// The icon texture to use if specified; otherwise, icon will be used from .
- private Task? iconTextureWrap;
-
/// The plugin that initiated this notification.
private LocalPlugin? initiatorPlugin;
@@ -119,6 +118,34 @@ internal sealed partial class ActiveNotification : IActiveNotification
set => this.underlyingNotification.Icon = value;
}
+ ///
+ public IDalamudTextureWrap? IconTexture
+ {
+ get => this.underlyingNotification.IconTexture;
+ set => this.IconTextureTask = value is null ? null : Task.FromResult(value);
+ }
+
+ ///
+ public Task? IconTextureTask
+ {
+ get => this.underlyingNotification.IconTextureTask;
+ set
+ {
+ // Do nothing if the value did not change.
+ if (this.underlyingNotification.IconTextureTask == value)
+ return;
+
+ if (this.hasIconTextureOwnership)
+ {
+ _ = this.underlyingNotification.IconTextureTask?.ToContentDisposedTask(true);
+ this.underlyingNotification.IconTextureTask = null;
+ this.hasIconTextureOwnership = false;
+ }
+
+ this.underlyingNotification.IconTextureTask = value;
+ }
+ }
+
///
public DateTime HardExpiry
{
@@ -239,26 +266,36 @@ internal sealed partial class ActiveNotification : IActiveNotification
}
///
- public void SetIconTexture(IDalamudTextureWrap? textureWrap)
- {
- this.SetIconTexture(textureWrap is null ? null : Task.FromResult(textureWrap));
- }
+ public void SetIconTexture(IDalamudTextureWrap? textureWrap) =>
+ this.SetIconTexture(textureWrap, false);
///
- public void SetIconTexture(Task? textureWrapTask)
+ public void SetIconTexture(IDalamudTextureWrap? textureWrap, bool leaveOpen) =>
+ this.SetIconTexture(textureWrap is null ? null : Task.FromResult(textureWrap), leaveOpen);
+
+ ///
+ public void SetIconTexture(Task? textureWrapTask) =>
+ this.SetIconTexture(textureWrapTask, false);
+
+ ///
+ public void SetIconTexture(Task? textureWrapTask, bool leaveOpen)
{
+ // If we're requested to replace the texture with the same texture, do nothing.
+ if (this.underlyingNotification.IconTextureTask == textureWrapTask)
+ return;
+
if (this.DismissReason is not null)
{
- textureWrapTask?.ToContentDisposedTask(true);
+ if (!leaveOpen)
+ 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);
- }
+ if (this.hasIconTextureOwnership)
+ _ = this.underlyingNotification.IconTextureTask?.ToContentDisposedTask(true);
+
+ this.hasIconTextureOwnership = !leaveOpen;
+ this.underlyingNotification.IconTextureTask = textureWrapTask;
}
/// Removes non-Dalamud invocation targets from events.
@@ -280,6 +317,11 @@ internal sealed partial class ActiveNotification : IActiveNotification
if (this.Icon is { } previousIcon && !IsOwnedByDalamud(previousIcon.GetType()))
this.Icon = null;
+ // Clear the texture if we don't have the ownership.
+ // The texture probably was owned by the plugin being unloaded in such case.
+ if (!this.hasIconTextureOwnership)
+ this.IconTextureTask = null;
+
this.isInitiatorUnloaded = true;
this.UserDismissable = true;
this.ExtensionDurationSinceLastInterest = NotificationConstants.DefaultDuration;
@@ -358,8 +400,13 @@ internal sealed partial class ActiveNotification : IActiveNotification
/// Clears the resources associated with this instance of .
internal void DisposeInternal()
{
- if (Interlocked.Exchange(ref this.iconTextureWrap, null) is { } wrapTaskToDispose)
- wrapTaskToDispose.ToContentDisposedTask(true);
+ if (this.hasIconTextureOwnership)
+ {
+ _ = this.underlyingNotification.IconTextureTask?.ToContentDisposedTask(true);
+ this.underlyingNotification.IconTextureTask = null;
+ this.hasIconTextureOwnership = false;
+ }
+
this.Dismiss = null;
this.Click = null;
this.DrawActions = null;
diff --git a/Dalamud/Interface/ImGuiNotification/Notification.cs b/Dalamud/Interface/ImGuiNotification/Notification.cs
index 5175985c7..0475628fd 100644
--- a/Dalamud/Interface/ImGuiNotification/Notification.cs
+++ b/Dalamud/Interface/ImGuiNotification/Notification.cs
@@ -1,4 +1,7 @@
+using System.Threading.Tasks;
+
using Dalamud.Interface.ImGuiNotification.Internal;
+using Dalamud.Interface.Internal;
using Dalamud.Interface.Internal.Notifications;
namespace Dalamud.Interface.ImGuiNotification;
@@ -26,6 +29,16 @@ public sealed record Notification : INotification
///
public INotificationIcon? Icon { get; set; }
+ ///
+ public IDalamudTextureWrap? IconTexture
+ {
+ get => this.IconTextureTask?.IsCompletedSuccessfully is true ? this.IconTextureTask.Result : null;
+ set => this.IconTextureTask = value is null ? null : Task.FromResult(value);
+ }
+
+ ///
+ public Task? IconTextureTask { get; set; }
+
///
public DateTime HardExpiry { get; set; } = DateTime.MaxValue;
diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/ImGuiWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/ImGuiWidget.cs
index 086b0c1ad..6817c82b3 100644
--- a/Dalamud/Interface/Internal/Windows/Data/Widgets/ImGuiWidget.cs
+++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/ImGuiWidget.cs
@@ -1,4 +1,5 @@
-using System.Linq;
+using System.Collections.Generic;
+using System.Linq;
using System.Threading.Tasks;
using Dalamud.Game.Text;
@@ -18,6 +19,7 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
///
internal class ImGuiWidget : IDataWindowWidget
{
+ private readonly HashSet notifications = new();
private NotificationTemplate notificationTemplate;
///
@@ -39,8 +41,10 @@ internal class ImGuiWidget : IDataWindowWidget
///
public void Draw()
{
+ this.notifications.RemoveWhere(x => x.DismissReason.HasValue);
+
var interfaceManager = Service.Get();
- var notifications = Service.Get();
+ var nm = Service.Get();
ImGui.Text("Monitor count: " + ImGui.GetPlatformIO().Monitors.Size);
ImGui.Text("OverrideGameCursor: " + interfaceManager.OverrideGameCursor);
@@ -139,6 +143,8 @@ internal class ImGuiWidget : IDataWindowWidget
"Action Bar (always on if not user dismissable for the example)",
ref this.notificationTemplate.ActionBar);
+ ImGui.Checkbox("Leave Textures Open", ref this.notificationTemplate.LeaveTexturesOpen);
+
if (ImGui.Button("Add notification"))
{
var text =
@@ -152,7 +158,7 @@ internal class ImGuiWidget : IDataWindowWidget
if (this.notificationTemplate.ManualType)
type = (NotificationType)this.notificationTemplate.TypeInt;
- var n = notifications.AddNotification(
+ var n = nm.AddNotification(
new()
{
Content = text,
@@ -198,27 +204,40 @@ internal class ImGuiWidget : IDataWindowWidget
},
});
+ this.notifications.Add(n);
+
var dam = Service.Get();
var tm = Service.Get();
switch (this.notificationTemplate.IconInt)
{
case 5:
n.SetIconTexture(
- dam.GetDalamudTextureWrap(
- Enum.Parse(
- NotificationTemplate.AssetSources[this.notificationTemplate.IconAssetInt])));
+ DisposeLoggingTextureWrap.Wrap(
+ dam.GetDalamudTextureWrap(
+ Enum.Parse(
+ NotificationTemplate.AssetSources[this.notificationTemplate.IconAssetInt]))),
+ this.notificationTemplate.LeaveTexturesOpen);
break;
case 6:
n.SetIconTexture(
dam.GetDalamudTextureWrapAsync(
- Enum.Parse(
- NotificationTemplate.AssetSources[this.notificationTemplate.IconAssetInt])));
+ Enum.Parse(
+ NotificationTemplate.AssetSources[this.notificationTemplate.IconAssetInt]))
+ .ContinueWith(
+ r => r.IsCompletedSuccessfully
+ ? Task.FromResult(DisposeLoggingTextureWrap.Wrap(r.Result))
+ : r).Unwrap(),
+ this.notificationTemplate.LeaveTexturesOpen);
break;
case 7:
- n.SetIconTexture(tm.GetTextureFromGame(this.notificationTemplate.IconText));
+ n.SetIconTexture(
+ DisposeLoggingTextureWrap.Wrap(tm.GetTextureFromGame(this.notificationTemplate.IconText)),
+ this.notificationTemplate.LeaveTexturesOpen);
break;
case 8:
- n.SetIconTexture(tm.GetTextureFromFile(new(this.notificationTemplate.IconText)));
+ n.SetIconTexture(
+ DisposeLoggingTextureWrap.Wrap(tm.GetTextureFromFile(new(this.notificationTemplate.IconText))),
+ this.notificationTemplate.LeaveTexturesOpen);
break;
}
@@ -261,7 +280,7 @@ internal class ImGuiWidget : IDataWindowWidget
{
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted($"{nclick}");
-
+
ImGui.SameLine();
if (ImGui.Button("Update"))
{
@@ -274,13 +293,23 @@ internal class ImGuiWidget : IDataWindowWidget
ImGui.SameLine();
if (ImGui.Button("Dismiss"))
an.Notification.DismissNow();
-
+
ImGui.SameLine();
ImGui.SetNextItemWidth(an.MaxCoord.X - ImGui.GetCursorPosX());
ImGui.InputText("##input", ref testString, 255);
};
}
}
+
+ ImGui.SameLine();
+ if (ImGui.Button("Replace images using setter"))
+ {
+ foreach (var n in this.notifications)
+ {
+ var i = (uint)Random.Shared.NextInt64(0, 200000);
+ n.IconTexture = DisposeLoggingTextureWrap.Wrap(Service.Get().GetIcon(i));
+ }
+ }
}
private static void NewRandom(out string? title, out NotificationType type, out float progress)
@@ -395,6 +424,7 @@ internal class ImGuiWidget : IDataWindowWidget
public bool Minimized;
public bool UserDismissable;
public bool ActionBar;
+ public bool LeaveTexturesOpen;
public int ProgressMode;
public void Reset()
@@ -416,8 +446,33 @@ internal class ImGuiWidget : IDataWindowWidget
this.Minimized = true;
this.UserDismissable = true;
this.ActionBar = true;
+ this.LeaveTexturesOpen = true;
this.ProgressMode = 0;
this.RespectUiHidden = true;
}
}
+
+ private sealed class DisposeLoggingTextureWrap : IDalamudTextureWrap
+ {
+ private readonly IDalamudTextureWrap inner;
+
+ public DisposeLoggingTextureWrap(IDalamudTextureWrap inner) => this.inner = inner;
+
+ public nint ImGuiHandle => this.inner.ImGuiHandle;
+
+ public int Width => this.inner.Width;
+
+ public int Height => this.inner.Height;
+
+ public static DisposeLoggingTextureWrap? Wrap(IDalamudTextureWrap? inner) => inner is null ? null : new(inner);
+
+ public void Dispose()
+ {
+ this.inner.Dispose();
+ Service.Get().AddNotification(
+ "Texture disposed",
+ "ImGui Widget",
+ NotificationType.Info);
+ }
+ }
}
diff --git a/Dalamud/Interface/Textures/DalamudTextureWrapExtensions.cs b/Dalamud/Interface/Textures/DalamudTextureWrapExtensions.cs
new file mode 100644
index 000000000..ceae9a476
--- /dev/null
+++ b/Dalamud/Interface/Textures/DalamudTextureWrapExtensions.cs
@@ -0,0 +1,21 @@
+using Dalamud.Interface.Internal;
+
+namespace Dalamud.Interface.Textures;
+
+/// Extension methods for .
+public static class DalamudTextureWrapExtensions
+{
+ /// Checks if two instances of point to a same underlying resource.
+ ///
+ /// The resource 1.
+ /// The resource 2.
+ /// true if both instances point to a same underlying resource.
+ public static bool ResourceEquals(this IDalamudTextureWrap? a, IDalamudTextureWrap? b)
+ {
+ if (a is null != b is null)
+ return false;
+ if (a is null)
+ return false;
+ return a.ImGuiHandle == b.ImGuiHandle;
+ }
+}