diff --git a/Dalamud/Interface/ImGuiNotification/EventArgs/INotificationClickArgs.cs b/Dalamud/Interface/ImGuiNotification/EventArgs/INotificationClickArgs.cs new file mode 100644 index 000000000..b85a96004 --- /dev/null +++ b/Dalamud/Interface/ImGuiNotification/EventArgs/INotificationClickArgs.cs @@ -0,0 +1,9 @@ +namespace Dalamud.Interface.ImGuiNotification.EventArgs; + +/// Arguments for use with . +/// Not to be implemented by plugins. +public interface INotificationClickArgs +{ + /// Gets the notification being clicked. + IActiveNotification Notification { get; } +} diff --git a/Dalamud/Interface/ImGuiNotification/EventArgs/INotificationDismissArgs.cs b/Dalamud/Interface/ImGuiNotification/EventArgs/INotificationDismissArgs.cs new file mode 100644 index 000000000..7f664efa1 --- /dev/null +++ b/Dalamud/Interface/ImGuiNotification/EventArgs/INotificationDismissArgs.cs @@ -0,0 +1,12 @@ +namespace Dalamud.Interface.ImGuiNotification.EventArgs; + +/// Arguments for use with . +/// Not to be implemented by plugins. +public interface INotificationDismissArgs +{ + /// Gets the notification being dismissed. + IActiveNotification Notification { get; } + + /// Gets the dismiss reason. + NotificationDismissReason Reason { get; } +} diff --git a/Dalamud/Interface/ImGuiNotification/EventArgs/INotificationDrawArgs.cs b/Dalamud/Interface/ImGuiNotification/EventArgs/INotificationDrawArgs.cs new file mode 100644 index 000000000..221f769e0 --- /dev/null +++ b/Dalamud/Interface/ImGuiNotification/EventArgs/INotificationDrawArgs.cs @@ -0,0 +1,19 @@ +using System.Numerics; + +namespace Dalamud.Interface.ImGuiNotification.EventArgs; + +/// Arguments for use with . +/// Not to be implemented by plugins. +public interface INotificationDrawArgs +{ + /// Gets the notification being drawn. + IActiveNotification Notification { get; } + + /// Gets the top left coordinates of the area being drawn. + Vector2 MinCoord { get; } + + /// Gets the bottom right coordinates of the area being drawn. + /// Note that can be , in which case there is no + /// vertical limits to the drawing region. + Vector2 MaxCoord { get; } +} diff --git a/Dalamud/Interface/ImGuiNotification/IActiveNotification.cs b/Dalamud/Interface/ImGuiNotification/IActiveNotification.cs index 340c052cd..c3ea2b9de 100644 --- a/Dalamud/Interface/ImGuiNotification/IActiveNotification.cs +++ b/Dalamud/Interface/ImGuiNotification/IActiveNotification.cs @@ -1,49 +1,48 @@ using System.Threading; +using Dalamud.Interface.ImGuiNotification.EventArgs; using Dalamud.Interface.Internal; namespace Dalamud.Interface.ImGuiNotification; /// Represents an active notification. +/// Not to be implemented by plugins. public interface IActiveNotification : INotification { /// The counter for field. private static long idCounter; /// Invoked upon dismissing the notification. - /// The event callback will not be called, - /// if a user interacts with the notification after the plugin is unloaded. - event NotificationDismissedDelegate Dismiss; + /// The event callback will not be called, if it gets dismissed after plugin unload. + event Action Dismiss; /// Invoked upon clicking on the notification. - /// - /// Note that this function may be called even after has been invoked. - /// Refer to . - /// - event Action Click; + /// Note that this function may be called even after has been invoked. + event Action Click; /// Invoked upon drawing the action bar of the notification. - /// - /// Note that this function may be called even after has been invoked. - /// Refer to . - /// - event Action DrawActions; + /// Note that this function may be called even after has been invoked. + event Action DrawActions; /// Gets the ID of this notification. + /// This value does not change. long Id { get; } /// Gets the time of creating this notification. + /// This value does not change. DateTime CreatedAt { get; } /// Gets the effective expiry time. /// Contains if the notification does not expire. + /// This value will change depending on property changes and user interactions. DateTime EffectiveExpiry { get; } - /// Gets a value indicating whether the notification has been dismissed. + /// Gets the reason how this notification got dismissed. null if not dismissed. /// This includes when the hide animation is being played. - bool IsDismissed { get; } + NotificationDismissReason? DismissReason { get; } /// Dismisses this notification. + /// If the notification has already been dismissed, this function does nothing. void DismissNow(); /// Extends this notifiation. @@ -57,8 +56,8 @@ public interface IActiveNotification : INotification /// /// 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 true, then calling this function will simply dispose the passed - /// without actually updating the icon. + /// If is not null, then calling this function will simply dispose the + /// passed without actually updating the icon. /// void SetIconTexture(IDalamudTextureWrap? textureWrap); diff --git a/Dalamud/Interface/ImGuiNotification/INotification.cs b/Dalamud/Interface/ImGuiNotification/INotification.cs index e6861726f..2bc8e751c 100644 --- a/Dalamud/Interface/ImGuiNotification/INotification.cs +++ b/Dalamud/Interface/ImGuiNotification/INotification.cs @@ -4,6 +4,7 @@ using Dalamud.Plugin.Services; namespace Dalamud.Interface.ImGuiNotification; /// Represents a notification. +/// Not to be implemented by plugins. public interface INotification { /// Gets or sets the content body of the notification. diff --git a/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.EventArgs.cs b/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.EventArgs.cs new file mode 100644 index 000000000..428d9103f --- /dev/null +++ b/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.EventArgs.cs @@ -0,0 +1,87 @@ +using System.Numerics; + +using Dalamud.Interface.ImGuiNotification.EventArgs; + +namespace Dalamud.Interface.ImGuiNotification.Internal; + +/// Represents an active notification. +internal sealed partial class ActiveNotification : INotificationDismissArgs +{ + /// + public event Action? Dismiss; + + /// + IActiveNotification INotificationDismissArgs.Notification => this; + + /// + 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"); + } + } +} + +/// Represents an active notification. +internal sealed partial class ActiveNotification : INotificationClickArgs +{ + /// + public event Action? Click; + + /// + IActiveNotification INotificationClickArgs.Notification => this; + + private void InvokeClick() + { + try + { + this.Click?.Invoke(this); + } + catch (Exception e) + { + this.LogEventInvokeError(e, $"{nameof(this.Click)} error"); + } + } +} + +/// Represents an active notification. +internal sealed partial class ActiveNotification : INotificationDrawArgs +{ + private Vector2 drawActionArgMinCoord; + private Vector2 drawActionArgMaxCoord; + + /// + public event Action? DrawActions; + + /// + IActiveNotification INotificationDrawArgs.Notification => this; + + /// + Vector2 INotificationDrawArgs.MinCoord => this.drawActionArgMinCoord; + + /// + 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"); + } + } +} diff --git a/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.ImGui.cs b/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.ImGui.cs index 99b924923..60e8e28e6 100644 --- a/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.ImGui.cs +++ b/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.ImGui.cs @@ -2,7 +2,6 @@ using System.Numerics; using Dalamud.Interface.Internal; using Dalamud.Interface.Utility; -using Dalamud.Utility; using ImGuiNET; @@ -83,7 +82,7 @@ internal sealed partial class ActiveNotification this.EffectiveExpiry = this.CalculateEffectiveExpiry(ref warrantsExtension); - if (!this.IsDismissed && DateTime.Now > this.EffectiveExpiry) + if (DateTime.Now > this.EffectiveExpiry) this.DismissNow(NotificationDismissReason.Timeout); if (this.ExtensionDurationSinceLastInterest > TimeSpan.Zero && warrantsExtension) @@ -121,7 +120,7 @@ internal sealed partial class ActiveNotification if (ImGui.IsMouseClicked(ImGuiMouseButton.Left) || ImGui.IsMouseClicked(ImGuiMouseButton.Right) || ImGui.IsMouseClicked(ImGuiMouseButton.Middle)) - this.Click.InvokeSafely(this); + this.InvokeClick(); } } @@ -419,22 +418,16 @@ internal sealed partial class ActiveNotification ImGui.PopTextWrapPos(); if (this.DrawActions is not null) { - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + NotificationConstants.ScaledComponentGap); - try - { - this.DrawActions.Invoke(this); - } - catch - { - // ignore - } + this.InvokeDrawActions( + minCoord with { Y = ImGui.GetCursorPosY() + NotificationConstants.ScaledComponentGap }, + new(minCoord.X + width, float.MaxValue)); } } private void DrawExpiryBar(DateTime effectiveExpiry, bool warrantsExtension) { float barL, barR; - if (this.IsDismissed) + if (this.DismissReason is not null) { var v = this.hideEasing.IsDone ? 0f : 1f - (float)this.hideEasing.Value; var midpoint = (this.prevProgressL + this.prevProgressR) / 2f; diff --git a/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.cs b/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.cs index 357752f6e..475ae7e68 100644 --- a/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.cs +++ b/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.cs @@ -1,16 +1,15 @@ -using System.Numerics; using System.Runtime.Loader; using System.Threading; using Dalamud.Interface.Animation; using Dalamud.Interface.Animation.EasingFunctions; -using Dalamud.Interface.Colors; using Dalamud.Interface.Internal; using Dalamud.Interface.Internal.Notifications; using Dalamud.Plugin.Internal.Types; using Dalamud.Utility; using Serilog; +using Serilog.Events; namespace Dalamud.Interface.ImGuiNotification.Internal; @@ -71,15 +70,6 @@ internal sealed partial class ActiveNotification : IActiveNotification this.progressEasing.Start(); } - /// - public event NotificationDismissedDelegate? Dismiss; - - /// - public event Action? Click; - - /// - public event Action? DrawActions; - /// public long Id { get; } = IActiveNotification.CreateNewId(); @@ -90,60 +80,35 @@ internal sealed partial class ActiveNotification : IActiveNotification public string Content { get => this.underlyingNotification.Content; - set - { - if (this.IsDismissed) - return; - this.underlyingNotification.Content = value; - } + set => this.underlyingNotification.Content = value; } /// public string? Title { get => this.underlyingNotification.Title; - set - { - if (this.IsDismissed) - return; - this.underlyingNotification.Title = value; - } + set => this.underlyingNotification.Title = value; } /// public string? MinimizedText { get => this.underlyingNotification.MinimizedText; - set - { - if (this.IsDismissed) - return; - this.underlyingNotification.MinimizedText = value; - } + set => this.underlyingNotification.MinimizedText = value; } /// public NotificationType Type { get => this.underlyingNotification.Type; - set - { - if (this.IsDismissed) - return; - this.underlyingNotification.Type = value; - } + set => this.underlyingNotification.Type = value; } /// public INotificationIcon? Icon { get => this.underlyingNotification.Icon; - set - { - if (this.IsDismissed) - return; - this.underlyingNotification.Icon = value; - } + set => this.underlyingNotification.Icon = value; } /// @@ -152,7 +117,7 @@ internal sealed partial class ActiveNotification : IActiveNotification get => this.underlyingNotification.HardExpiry; set { - if (this.underlyingNotification.HardExpiry == value || this.IsDismissed) + if (this.underlyingNotification.HardExpiry == value) return; this.underlyingNotification.HardExpiry = value; this.lastInterestTime = DateTime.Now; @@ -165,8 +130,6 @@ internal sealed partial class ActiveNotification : IActiveNotification get => this.underlyingNotification.InitialDuration; set { - if (this.IsDismissed) - return; this.underlyingNotification.InitialDuration = value; this.lastInterestTime = DateTime.Now; } @@ -178,8 +141,6 @@ internal sealed partial class ActiveNotification : IActiveNotification get => this.underlyingNotification.ExtensionDurationSinceLastInterest; set { - if (this.IsDismissed) - return; this.underlyingNotification.ExtensionDurationSinceLastInterest = value; this.lastInterestTime = DateTime.Now; } @@ -188,57 +149,37 @@ internal sealed partial class ActiveNotification : IActiveNotification /// public DateTime EffectiveExpiry { get; private set; } + /// + public NotificationDismissReason? DismissReason { get; private set; } + /// public bool ShowIndeterminateIfNoExpiry { get => this.underlyingNotification.ShowIndeterminateIfNoExpiry; - set - { - if (this.IsDismissed) - return; - this.underlyingNotification.ShowIndeterminateIfNoExpiry = value; - } + set => this.underlyingNotification.ShowIndeterminateIfNoExpiry = value; } /// public bool Minimized { get => this.newMinimized ?? this.underlyingNotification.Minimized; - set - { - if (this.IsDismissed) - return; - this.newMinimized = value; - } + set => this.newMinimized = value; } /// public bool UserDismissable { get => this.underlyingNotification.UserDismissable; - set - { - if (this.IsDismissed) - return; - this.underlyingNotification.UserDismissable = value; - } + set => this.underlyingNotification.UserDismissable = value; } /// public float Progress { get => this.newProgress ?? this.underlyingNotification.Progress; - set - { - if (this.IsDismissed) - return; - this.newProgress = value; - } + set => this.newProgress = value; } - /// - public bool IsDismissed => this.hideEasing.IsRunning; - /// Gets the eased progress. private float ProgressEased { @@ -271,20 +212,12 @@ internal sealed partial class ActiveNotification : IActiveNotification /// The reason of dismissal. public void DismissNow(NotificationDismissReason reason) { - if (this.hideEasing.IsRunning) + if (this.DismissReason is not null) return; + this.DismissReason = reason; this.hideEasing.Start(); - try - { - this.Dismiss?.Invoke(this, reason); - } - catch (Exception e) - { - Log.Error( - e, - $"{nameof(this.Dismiss)} error; notification is owned by {this.initiatorPlugin?.Name ?? NotificationConstants.DefaultInitiator}"); - } + this.InvokeDismiss(); } /// @@ -298,7 +231,7 @@ internal sealed partial class ActiveNotification : IActiveNotification /// public void SetIconTexture(IDalamudTextureWrap? textureWrap) { - if (this.IsDismissed) + if (this.DismissReason is not null) { textureWrap?.Dispose(); return; @@ -408,4 +341,9 @@ internal sealed partial class ActiveNotification : IActiveNotification this.DrawActions = null; this.initiatorPlugin = null; } + + private void LogEventInvokeError(Exception exception, string message) => + Log.Error( + exception, + $"[{nameof(ActiveNotification)}:{this.initiatorPlugin?.Name ?? NotificationConstants.DefaultInitiator}] {message}"); } diff --git a/Dalamud/Interface/ImGuiNotification/Internal/NotificationIcon/GamePathNotificationIcon.cs b/Dalamud/Interface/ImGuiNotification/Internal/NotificationIcon/GamePathNotificationIcon.cs index c1db8820c..e0699e1b6 100644 --- a/Dalamud/Interface/ImGuiNotification/Internal/NotificationIcon/GamePathNotificationIcon.cs +++ b/Dalamud/Interface/ImGuiNotification/Internal/NotificationIcon/GamePathNotificationIcon.cs @@ -15,7 +15,7 @@ internal class GamePathNotificationIcon : INotificationIcon /// The path to a .tex file inside the game resources. /// Use to get the game path from icon IDs. public GamePathNotificationIcon(string gamePath) => this.gamePath = gamePath; - + /// public bool DrawIcon(Vector2 minCoord, Vector2 maxCoord, Vector4 color) => NotificationUtilities.DrawIconFrom( diff --git a/Dalamud/Interface/ImGuiNotification/Internal/NotificationManager.cs b/Dalamud/Interface/ImGuiNotification/Internal/NotificationManager.cs index 5ee9fed3e..973e93c72 100644 --- a/Dalamud/Interface/ImGuiNotification/Internal/NotificationManager.cs +++ b/Dalamud/Interface/ImGuiNotification/Internal/NotificationManager.cs @@ -137,7 +137,7 @@ internal class NotificationManagerPluginScoped : INotificationManager, IServiceT { var an = this.notificationManagerService.AddNotification(notification, this.localPlugin); _ = 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; } diff --git a/Dalamud/Interface/ImGuiNotification/NotificationDismissedDelegate.cs b/Dalamud/Interface/ImGuiNotification/NotificationDismissedDelegate.cs deleted file mode 100644 index 09d6fd818..000000000 --- a/Dalamud/Interface/ImGuiNotification/NotificationDismissedDelegate.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Dalamud.Interface.ImGuiNotification; - -/// Delegate representing the dismissal of an active notification. -/// The notification being dismissed. -/// The reason of dismissal. -public delegate void NotificationDismissedDelegate( - IActiveNotification notification, - NotificationDismissReason dismissReason); diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/ImGuiWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/ImGuiWidget.cs index 6c94a2273..d51f18216 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/ImGuiWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/ImGuiWidget.cs @@ -219,7 +219,7 @@ internal class ImGuiWidget : IDataWindowWidget Task.Run( async () => { - for (var i = 0; i <= 10 && !n.IsDismissed; i++) + for (var i = 0; i <= 10 && !n.DismissReason.HasValue; i++) { await Task.Delay(500); n.Progress = i / 10f; @@ -230,7 +230,7 @@ internal class ImGuiWidget : IDataWindowWidget Task.Run( async () => { - for (var i = 0; i <= 10 && !n.IsDismissed; i++) + for (var i = 0; i <= 10 && !n.DismissReason.HasValue; i++) { await Task.Delay(500); n.Progress = i / 10f; @@ -257,16 +257,17 @@ internal class ImGuiWidget : IDataWindowWidget if (ImGui.Button("Update")) { NewRandom(out title, out type, out progress); - an.Title = title; - an.Type = type; - an.Progress = progress; + an.Notification.Title = title; + an.Notification.Type = type; + an.Notification.Progress = progress; } ImGui.SameLine(); if (ImGui.Button("Dismiss")) - an.DismissNow(); + an.Notification.DismissNow(); ImGui.SameLine(); + ImGui.SetNextItemWidth(an.MaxCoord.X - ImGui.GetCursorPosX()); ImGui.InputText("##input", ref testString, 255); }; } diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs index 3a90d52c1..2053d9354 100644 --- a/Dalamud/Interface/UiBuilder.cs +++ b/Dalamud/Interface/UiBuilder.cs @@ -583,7 +583,7 @@ public sealed class UiBuilder : IDisposable }, this.localPlugin); _ = this.notifications.TryAdd(an, 0); - an.Dismiss += (a, unused) => this.notifications.TryRemove(an, out _); + an.Dismiss += a => this.notifications.TryRemove(a.Notification, out _); } ///