More cleanup

This commit is contained in:
Soreepeong 2024-02-27 23:54:57 +09:00
parent a7d5380796
commit 92302ffd89
13 changed files with 183 additions and 132 deletions

View file

@ -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; }
}

View file

@ -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; }
}

View file

@ -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; }
}

View file

@ -1,49 +1,48 @@
using System.Threading; using System.Threading;
using Dalamud.Interface.ImGuiNotification.EventArgs;
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
namespace Dalamud.Interface.ImGuiNotification; namespace Dalamud.Interface.ImGuiNotification;
/// <summary>Represents an active notification.</summary> /// <summary>Represents an active notification.</summary>
/// <remarks>Not to be implemented by plugins.</remarks>
public interface IActiveNotification : INotification public interface IActiveNotification : INotification
{ {
/// <summary>The counter for <see cref="Id"/> field.</summary> /// <summary>The counter for <see cref="Id"/> field.</summary>
private static long idCounter; private static long idCounter;
/// <summary>Invoked upon dismissing the notification.</summary> /// <summary>Invoked upon dismissing the notification.</summary>
/// <remarks>The event callback will not be called, /// <remarks>The event callback will not be called, if it gets dismissed after plugin unload.</remarks>
/// if a user interacts with the notification after the plugin is unloaded.</remarks> event Action<INotificationDismissArgs> Dismiss;
event NotificationDismissedDelegate Dismiss;
/// <summary>Invoked upon clicking on the notification.</summary> /// <summary>Invoked upon clicking on the notification.</summary>
/// <remarks> /// <remarks>Note that this function may be called even after <see cref="Dismiss"/> has been invoked.</remarks>
/// Note that this function may be called even after <see cref="Dismiss"/> has been invoked. event Action<INotificationClickArgs> Click;
/// Refer to <see cref="IsDismissed"/>.
/// </remarks>
event Action<IActiveNotification> Click;
/// <summary>Invoked upon drawing the action bar of the notification.</summary> /// <summary>Invoked upon drawing the action bar of the notification.</summary>
/// <remarks> /// <remarks>Note that this function may be called even after <see cref="Dismiss"/> has been invoked.</remarks>
/// Note that this function may be called even after <see cref="Dismiss"/> has been invoked. event Action<INotificationDrawArgs> DrawActions;
/// Refer to <see cref="IsDismissed"/>.
/// </remarks>
event Action<IActiveNotification> DrawActions;
/// <summary>Gets the ID of this notification.</summary> /// <summary>Gets the ID of this notification.</summary>
/// <remarks>This value does not change.</remarks>
long Id { get; } long Id { get; }
/// <summary>Gets the time of creating this notification.</summary> /// <summary>Gets the time of creating this notification.</summary>
/// <remarks>This value does not change.</remarks>
DateTime CreatedAt { get; } DateTime CreatedAt { get; }
/// <summary>Gets the effective expiry time.</summary> /// <summary>Gets the effective expiry time.</summary>
/// <remarks>Contains <see cref="DateTime.MaxValue"/> if the notification does not expire.</remarks> /// <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; } DateTime EffectiveExpiry { get; }
/// <summary>Gets a value indicating whether the notification has been dismissed.</summary> /// <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> /// <remarks>This includes when the hide animation is being played.</remarks>
bool IsDismissed { get; } NotificationDismissReason? DismissReason { get; }
/// <summary>Dismisses this notification.</summary> /// <summary>Dismisses this notification.</summary>
/// <remarks>If the notification has already been dismissed, this function does nothing.</remarks>
void DismissNow(); void DismissNow();
/// <summary>Extends this notifiation.</summary> /// <summary>Extends this notifiation.</summary>
@ -57,8 +56,8 @@ public interface IActiveNotification : INotification
/// <remarks> /// <remarks>
/// <para>The texture passed will be disposed when the notification is dismissed or a new different texture is set /// <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> /// via another call to this function. You do not have to dispose it yourself.</para>
/// <para>If <see cref="IsDismissed"/> is <c>true</c>, then calling this function will simply dispose the passed /// <para>If <see cref="DismissReason"/> is not <c>null</c>, then calling this function will simply dispose the
/// <paramref name="textureWrap"/> without actually updating the icon.</para> /// passed <paramref name="textureWrap"/> without actually updating the icon.</para>
/// </remarks> /// </remarks>
void SetIconTexture(IDalamudTextureWrap? textureWrap); void SetIconTexture(IDalamudTextureWrap? textureWrap);

View file

@ -4,6 +4,7 @@ using Dalamud.Plugin.Services;
namespace Dalamud.Interface.ImGuiNotification; namespace Dalamud.Interface.ImGuiNotification;
/// <summary>Represents a notification.</summary> /// <summary>Represents a notification.</summary>
/// <remarks>Not to be implemented by plugins.</remarks>
public interface INotification public interface INotification
{ {
/// <summary>Gets or sets the content body of the notification.</summary> /// <summary>Gets or sets the content body of the notification.</summary>

View file

@ -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");
}
}
}

View file

@ -2,7 +2,6 @@ using System.Numerics;
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Utility;
using ImGuiNET; using ImGuiNET;
@ -83,7 +82,7 @@ internal sealed partial class ActiveNotification
this.EffectiveExpiry = this.CalculateEffectiveExpiry(ref warrantsExtension); this.EffectiveExpiry = this.CalculateEffectiveExpiry(ref warrantsExtension);
if (!this.IsDismissed && DateTime.Now > this.EffectiveExpiry) if (DateTime.Now > this.EffectiveExpiry)
this.DismissNow(NotificationDismissReason.Timeout); this.DismissNow(NotificationDismissReason.Timeout);
if (this.ExtensionDurationSinceLastInterest > TimeSpan.Zero && warrantsExtension) if (this.ExtensionDurationSinceLastInterest > TimeSpan.Zero && warrantsExtension)
@ -121,7 +120,7 @@ internal sealed partial class ActiveNotification
if (ImGui.IsMouseClicked(ImGuiMouseButton.Left) if (ImGui.IsMouseClicked(ImGuiMouseButton.Left)
|| ImGui.IsMouseClicked(ImGuiMouseButton.Right) || ImGui.IsMouseClicked(ImGuiMouseButton.Right)
|| ImGui.IsMouseClicked(ImGuiMouseButton.Middle)) || ImGui.IsMouseClicked(ImGuiMouseButton.Middle))
this.Click.InvokeSafely(this); this.InvokeClick();
} }
} }
@ -419,22 +418,16 @@ internal sealed partial class ActiveNotification
ImGui.PopTextWrapPos(); ImGui.PopTextWrapPos();
if (this.DrawActions is not null) if (this.DrawActions is not null)
{ {
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + NotificationConstants.ScaledComponentGap); this.InvokeDrawActions(
try minCoord with { Y = ImGui.GetCursorPosY() + NotificationConstants.ScaledComponentGap },
{ new(minCoord.X + width, float.MaxValue));
this.DrawActions.Invoke(this);
}
catch
{
// ignore
}
} }
} }
private void DrawExpiryBar(DateTime effectiveExpiry, bool warrantsExtension) private void DrawExpiryBar(DateTime effectiveExpiry, bool warrantsExtension)
{ {
float barL, barR; float barL, barR;
if (this.IsDismissed) if (this.DismissReason is not null)
{ {
var v = this.hideEasing.IsDone ? 0f : 1f - (float)this.hideEasing.Value; var v = this.hideEasing.IsDone ? 0f : 1f - (float)this.hideEasing.Value;
var midpoint = (this.prevProgressL + this.prevProgressR) / 2f; var midpoint = (this.prevProgressL + this.prevProgressR) / 2f;

View file

@ -1,16 +1,15 @@
using System.Numerics;
using System.Runtime.Loader; using System.Runtime.Loader;
using System.Threading; using System.Threading;
using Dalamud.Interface.Animation; using Dalamud.Interface.Animation;
using Dalamud.Interface.Animation.EasingFunctions; using Dalamud.Interface.Animation.EasingFunctions;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.Internal.Notifications;
using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Internal.Types;
using Dalamud.Utility; using Dalamud.Utility;
using Serilog; using Serilog;
using Serilog.Events;
namespace Dalamud.Interface.ImGuiNotification.Internal; namespace Dalamud.Interface.ImGuiNotification.Internal;
@ -71,15 +70,6 @@ internal sealed partial class ActiveNotification : IActiveNotification
this.progressEasing.Start(); this.progressEasing.Start();
} }
/// <inheritdoc/>
public event NotificationDismissedDelegate? Dismiss;
/// <inheritdoc/>
public event Action<IActiveNotification>? Click;
/// <inheritdoc/>
public event Action<IActiveNotification>? DrawActions;
/// <inheritdoc/> /// <inheritdoc/>
public long Id { get; } = IActiveNotification.CreateNewId(); public long Id { get; } = IActiveNotification.CreateNewId();
@ -90,60 +80,35 @@ internal sealed partial class ActiveNotification : IActiveNotification
public string Content public string Content
{ {
get => this.underlyingNotification.Content; get => this.underlyingNotification.Content;
set set => this.underlyingNotification.Content = value;
{
if (this.IsDismissed)
return;
this.underlyingNotification.Content = value;
}
} }
/// <inheritdoc/> /// <inheritdoc/>
public string? Title public string? Title
{ {
get => this.underlyingNotification.Title; get => this.underlyingNotification.Title;
set set => this.underlyingNotification.Title = value;
{
if (this.IsDismissed)
return;
this.underlyingNotification.Title = value;
}
} }
/// <inheritdoc/> /// <inheritdoc/>
public string? MinimizedText public string? MinimizedText
{ {
get => this.underlyingNotification.MinimizedText; get => this.underlyingNotification.MinimizedText;
set set => this.underlyingNotification.MinimizedText = value;
{
if (this.IsDismissed)
return;
this.underlyingNotification.MinimizedText = value;
}
} }
/// <inheritdoc/> /// <inheritdoc/>
public NotificationType Type public NotificationType Type
{ {
get => this.underlyingNotification.Type; get => this.underlyingNotification.Type;
set set => this.underlyingNotification.Type = value;
{
if (this.IsDismissed)
return;
this.underlyingNotification.Type = value;
}
} }
/// <inheritdoc/> /// <inheritdoc/>
public INotificationIcon? Icon public INotificationIcon? Icon
{ {
get => this.underlyingNotification.Icon; get => this.underlyingNotification.Icon;
set set => this.underlyingNotification.Icon = value;
{
if (this.IsDismissed)
return;
this.underlyingNotification.Icon = value;
}
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -152,7 +117,7 @@ internal sealed partial class ActiveNotification : IActiveNotification
get => this.underlyingNotification.HardExpiry; get => this.underlyingNotification.HardExpiry;
set set
{ {
if (this.underlyingNotification.HardExpiry == value || this.IsDismissed) if (this.underlyingNotification.HardExpiry == value)
return; return;
this.underlyingNotification.HardExpiry = value; this.underlyingNotification.HardExpiry = value;
this.lastInterestTime = DateTime.Now; this.lastInterestTime = DateTime.Now;
@ -165,8 +130,6 @@ internal sealed partial class ActiveNotification : IActiveNotification
get => this.underlyingNotification.InitialDuration; get => this.underlyingNotification.InitialDuration;
set set
{ {
if (this.IsDismissed)
return;
this.underlyingNotification.InitialDuration = value; this.underlyingNotification.InitialDuration = value;
this.lastInterestTime = DateTime.Now; this.lastInterestTime = DateTime.Now;
} }
@ -178,8 +141,6 @@ internal sealed partial class ActiveNotification : IActiveNotification
get => this.underlyingNotification.ExtensionDurationSinceLastInterest; get => this.underlyingNotification.ExtensionDurationSinceLastInterest;
set set
{ {
if (this.IsDismissed)
return;
this.underlyingNotification.ExtensionDurationSinceLastInterest = value; this.underlyingNotification.ExtensionDurationSinceLastInterest = value;
this.lastInterestTime = DateTime.Now; this.lastInterestTime = DateTime.Now;
} }
@ -188,56 +149,36 @@ internal sealed partial class ActiveNotification : IActiveNotification
/// <inheritdoc/> /// <inheritdoc/>
public DateTime EffectiveExpiry { get; private set; } public DateTime EffectiveExpiry { get; private set; }
/// <inheritdoc/>
public NotificationDismissReason? DismissReason { get; private set; }
/// <inheritdoc/> /// <inheritdoc/>
public bool ShowIndeterminateIfNoExpiry public bool ShowIndeterminateIfNoExpiry
{ {
get => this.underlyingNotification.ShowIndeterminateIfNoExpiry; get => this.underlyingNotification.ShowIndeterminateIfNoExpiry;
set set => this.underlyingNotification.ShowIndeterminateIfNoExpiry = value;
{
if (this.IsDismissed)
return;
this.underlyingNotification.ShowIndeterminateIfNoExpiry = value;
}
} }
/// <inheritdoc/> /// <inheritdoc/>
public bool Minimized public bool Minimized
{ {
get => this.newMinimized ?? this.underlyingNotification.Minimized; get => this.newMinimized ?? this.underlyingNotification.Minimized;
set set => this.newMinimized = value;
{
if (this.IsDismissed)
return;
this.newMinimized = value;
}
} }
/// <inheritdoc/> /// <inheritdoc/>
public bool UserDismissable public bool UserDismissable
{ {
get => this.underlyingNotification.UserDismissable; get => this.underlyingNotification.UserDismissable;
set set => this.underlyingNotification.UserDismissable = value;
{
if (this.IsDismissed)
return;
this.underlyingNotification.UserDismissable = value;
}
} }
/// <inheritdoc/> /// <inheritdoc/>
public float Progress public float Progress
{ {
get => this.newProgress ?? this.underlyingNotification.Progress; get => this.newProgress ?? this.underlyingNotification.Progress;
set set => this.newProgress = value;
{
if (this.IsDismissed)
return;
this.newProgress = value;
} }
}
/// <inheritdoc/>
public bool IsDismissed => this.hideEasing.IsRunning;
/// <summary>Gets the eased progress.</summary> /// <summary>Gets the eased progress.</summary>
private float ProgressEased private float ProgressEased
@ -271,20 +212,12 @@ internal sealed partial class ActiveNotification : IActiveNotification
/// <param name="reason">The reason of dismissal.</param> /// <param name="reason">The reason of dismissal.</param>
public void DismissNow(NotificationDismissReason reason) public void DismissNow(NotificationDismissReason reason)
{ {
if (this.hideEasing.IsRunning) if (this.DismissReason is not null)
return; return;
this.DismissReason = reason;
this.hideEasing.Start(); this.hideEasing.Start();
try this.InvokeDismiss();
{
this.Dismiss?.Invoke(this, reason);
}
catch (Exception e)
{
Log.Error(
e,
$"{nameof(this.Dismiss)} error; notification is owned by {this.initiatorPlugin?.Name ?? NotificationConstants.DefaultInitiator}");
}
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -298,7 +231,7 @@ internal sealed partial class ActiveNotification : IActiveNotification
/// <inheritdoc/> /// <inheritdoc/>
public void SetIconTexture(IDalamudTextureWrap? textureWrap) public void SetIconTexture(IDalamudTextureWrap? textureWrap)
{ {
if (this.IsDismissed) if (this.DismissReason is not null)
{ {
textureWrap?.Dispose(); textureWrap?.Dispose();
return; return;
@ -408,4 +341,9 @@ internal sealed partial class ActiveNotification : IActiveNotification
this.DrawActions = null; this.DrawActions = null;
this.initiatorPlugin = null; this.initiatorPlugin = null;
} }
private void LogEventInvokeError(Exception exception, string message) =>
Log.Error(
exception,
$"[{nameof(ActiveNotification)}:{this.initiatorPlugin?.Name ?? NotificationConstants.DefaultInitiator}] {message}");
} }

View file

@ -137,7 +137,7 @@ internal class NotificationManagerPluginScoped : INotificationManager, IServiceT
{ {
var an = this.notificationManagerService.AddNotification(notification, this.localPlugin); var an = this.notificationManagerService.AddNotification(notification, this.localPlugin);
_ = this.notifications.TryAdd(an, 0); _ = this.notifications.TryAdd(an, 0);
an.Dismiss += (a, unused) => this.notifications.TryRemove(an, out _); an.Dismiss += a => this.notifications.TryRemove(a.Notification, out _);
return an; return an;
} }

View file

@ -1,8 +0,0 @@
namespace Dalamud.Interface.ImGuiNotification;
/// <summary>Delegate representing the dismissal of an active notification.</summary>
/// <param name="notification">The notification being dismissed.</param>
/// <param name="dismissReason">The reason of dismissal.</param>
public delegate void NotificationDismissedDelegate(
IActiveNotification notification,
NotificationDismissReason dismissReason);

View file

@ -219,7 +219,7 @@ internal class ImGuiWidget : IDataWindowWidget
Task.Run( Task.Run(
async () => async () =>
{ {
for (var i = 0; i <= 10 && !n.IsDismissed; i++) for (var i = 0; i <= 10 && !n.DismissReason.HasValue; i++)
{ {
await Task.Delay(500); await Task.Delay(500);
n.Progress = i / 10f; n.Progress = i / 10f;
@ -230,7 +230,7 @@ internal class ImGuiWidget : IDataWindowWidget
Task.Run( Task.Run(
async () => async () =>
{ {
for (var i = 0; i <= 10 && !n.IsDismissed; i++) for (var i = 0; i <= 10 && !n.DismissReason.HasValue; i++)
{ {
await Task.Delay(500); await Task.Delay(500);
n.Progress = i / 10f; n.Progress = i / 10f;
@ -257,16 +257,17 @@ internal class ImGuiWidget : IDataWindowWidget
if (ImGui.Button("Update")) if (ImGui.Button("Update"))
{ {
NewRandom(out title, out type, out progress); NewRandom(out title, out type, out progress);
an.Title = title; an.Notification.Title = title;
an.Type = type; an.Notification.Type = type;
an.Progress = progress; an.Notification.Progress = progress;
} }
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.Button("Dismiss")) if (ImGui.Button("Dismiss"))
an.DismissNow(); an.Notification.DismissNow();
ImGui.SameLine(); ImGui.SameLine();
ImGui.SetNextItemWidth(an.MaxCoord.X - ImGui.GetCursorPosX());
ImGui.InputText("##input", ref testString, 255); ImGui.InputText("##input", ref testString, 255);
}; };
} }

View file

@ -583,7 +583,7 @@ public sealed class UiBuilder : IDisposable
}, },
this.localPlugin); this.localPlugin);
_ = this.notifications.TryAdd(an, 0); _ = this.notifications.TryAdd(an, 0);
an.Dismiss += (a, unused) => this.notifications.TryRemove(an, out _); an.Dismiss += a => this.notifications.TryRemove(a.Notification, out _);
} }
/// <summary> /// <summary>