mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Normalize namespaces
This commit is contained in:
parent
04c6be5671
commit
7aba15ef5b
21 changed files with 124 additions and 47 deletions
|
|
@ -1,8 +1,6 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
|
||||
namespace Dalamud.Interface.ImGuiNotification;
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ public interface INotification
|
|||
/// <remarks>Set to <see cref="DateTime.MaxValue"/> to make the notification not have an expiry time
|
||||
/// (sticky, indeterminate, permanent, or persistent).</remarks>
|
||||
DateTime Expiry { get; }
|
||||
|
||||
|
||||
/// <summary>Gets a value indicating whether this notification may be interacted.</summary>
|
||||
/// <remarks>
|
||||
/// Set this value to <c>true</c> if you want to respond to user inputs from
|
||||
|
|
@ -52,7 +52,7 @@ public interface INotification
|
|||
/// This property is applicable regardless of <see cref="Interactible"/>.
|
||||
/// </remarks>
|
||||
TimeSpan HoverExtendDuration { get; }
|
||||
|
||||
|
||||
/// <summary>Gets the progress for the progress bar of the notification.
|
||||
/// The progress should either be in the range between 0 and 1 or be a negative value.
|
||||
/// Specifying a negative value will show an indeterminate progress bar.</summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,798 @@
|
|||
using System.Numerics;
|
||||
using System.Runtime.Loader;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Interface.Animation;
|
||||
using Dalamud.Interface.Animation.EasingFunctions;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||
using Dalamud.Interface.Internal.Windows;
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Plugin.Internal.Types;
|
||||
using Dalamud.Storage.Assets;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Notifications;
|
||||
|
||||
/// <summary>Represents an active notification.</summary>
|
||||
internal sealed class ActiveNotification : IActiveNotification, IDisposable
|
||||
{
|
||||
private readonly Notification underlyingNotification;
|
||||
|
||||
private readonly Easing showEasing;
|
||||
private readonly Easing hideEasing;
|
||||
private readonly Easing progressEasing;
|
||||
|
||||
/// <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>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.ProgressAnimationDuration);
|
||||
|
||||
this.showEasing.Start();
|
||||
this.progressEasing.Start();
|
||||
this.UpdateIcon();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event NotificationDismissedDelegate? Dismiss;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action<IActiveNotification>? Click;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action<IActiveNotification>? DrawActions;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action<IActiveNotification>? MouseEnter;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action<IActiveNotification>? MouseLeave;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public long Id { get; } = IActiveNotification.CreateNewId();
|
||||
|
||||
/// <summary>Gets the time of creating this notification.</summary>
|
||||
public DateTime CreatedAt { get; } = DateTime.Now;
|
||||
|
||||
/// <summary>Gets the time of starting to count the timer for the expiration.</summary>
|
||||
public DateTime ExpiryRelativeToTime { get; private set; } = DateTime.Now;
|
||||
|
||||
/// <inheritdoc cref="IActiveNotification.Content"/>
|
||||
public string Content
|
||||
{
|
||||
get => this.underlyingNotification.Content;
|
||||
set
|
||||
{
|
||||
if (this.IsDismissed)
|
||||
return;
|
||||
this.underlyingNotification.Content = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IActiveNotification.Title"/>
|
||||
public string? Title
|
||||
{
|
||||
get => this.underlyingNotification.Title;
|
||||
set
|
||||
{
|
||||
if (this.IsDismissed)
|
||||
return;
|
||||
this.underlyingNotification.Title = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IActiveNotification.Type"/>
|
||||
public NotificationType Type
|
||||
{
|
||||
get => this.underlyingNotification.Type;
|
||||
set
|
||||
{
|
||||
if (this.IsDismissed)
|
||||
return;
|
||||
this.underlyingNotification.Type = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IActiveNotification.IconCreator"/>
|
||||
public Func<Task<object>>? IconCreator
|
||||
{
|
||||
get => this.underlyingNotification.IconCreator;
|
||||
set
|
||||
{
|
||||
if (this.IsDismissed)
|
||||
return;
|
||||
this.underlyingNotification.IconCreator = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IActiveNotification.Expiry"/>
|
||||
public DateTime Expiry
|
||||
{
|
||||
get => this.underlyingNotification.Expiry;
|
||||
set
|
||||
{
|
||||
if (this.underlyingNotification.Expiry == value || this.IsDismissed)
|
||||
return;
|
||||
this.underlyingNotification.Expiry = value;
|
||||
this.ExpiryRelativeToTime = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IActiveNotification.Interactible"/>
|
||||
public bool Interactible
|
||||
{
|
||||
get => this.underlyingNotification.Interactible;
|
||||
set
|
||||
{
|
||||
if (this.IsDismissed)
|
||||
return;
|
||||
this.underlyingNotification.Interactible = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IActiveNotification.HoverExtendDuration"/>
|
||||
public TimeSpan HoverExtendDuration
|
||||
{
|
||||
get => this.underlyingNotification.HoverExtendDuration;
|
||||
set
|
||||
{
|
||||
if (this.IsDismissed)
|
||||
return;
|
||||
this.underlyingNotification.HoverExtendDuration = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IActiveNotification.Progress"/>
|
||||
public float Progress
|
||||
{
|
||||
get => this.underlyingNotification.Progress;
|
||||
set
|
||||
{
|
||||
if (this.IsDismissed)
|
||||
return;
|
||||
|
||||
this.progressBefore = this.ProgressEased;
|
||||
this.underlyingNotification.Progress = value;
|
||||
this.progressEasing.Restart();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsMouseHovered { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsDismissed => this.hideEasing.IsRunning;
|
||||
|
||||
/// <summary>Gets a value indicating whether <see cref="InitiatorPlugin"/> has been unloaded.</summary>
|
||||
public bool IsInitiatorUnloaded { get; private set; }
|
||||
|
||||
/// <summary>Gets or sets the plugin that initiated this notification.</summary>
|
||||
public LocalPlugin? InitiatorPlugin { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the icon of this notification.</summary>
|
||||
public Task<object>? IconTask { get; set; }
|
||||
|
||||
/// <summary>Gets the eased progress.</summary>
|
||||
private float ProgressEased
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.Progress < 0)
|
||||
return 0f;
|
||||
|
||||
if (Math.Abs(this.Progress - this.progressBefore) < 0.000001f || this.progressEasing.IsDone)
|
||||
return this.Progress;
|
||||
|
||||
var state = Math.Clamp((float)this.progressEasing.Value, 0f, 1f);
|
||||
return this.progressBefore + (state * (this.Progress - this.progressBefore));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the default color of the notification.</summary>
|
||||
private Vector4 DefaultIconColor => this.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 default icon of the notification.</summary>
|
||||
private string? DefaultIconString => this.Type switch
|
||||
{
|
||||
NotificationType.None => null,
|
||||
NotificationType.Success => FontAwesomeIcon.CheckCircle.ToIconString(),
|
||||
NotificationType.Warning => FontAwesomeIcon.ExclamationCircle.ToIconString(),
|
||||
NotificationType.Error => FontAwesomeIcon.TimesCircle.ToIconString(),
|
||||
NotificationType.Info => FontAwesomeIcon.InfoCircle.ToIconString(),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
/// <summary>Gets the default title of the notification.</summary>
|
||||
private string? DefaultTitle => this.Type switch
|
||||
{
|
||||
NotificationType.None => null,
|
||||
NotificationType.Success => NotificationType.Success.ToString(),
|
||||
NotificationType.Warning => NotificationType.Warning.ToString(),
|
||||
NotificationType.Error => NotificationType.Error.ToString(),
|
||||
NotificationType.Info => NotificationType.Info.ToString(),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
/// <summary>Gets the string for the initiator field.</summary>
|
||||
private string InitiatorString =>
|
||||
this.InitiatorPlugin is not { } initiatorPlugin
|
||||
? NotificationConstants.DefaultInitiator
|
||||
: this.IsInitiatorUnloaded
|
||||
? NotificationConstants.UnloadedInitiatorNameFormat.Format(initiatorPlugin.Name)
|
||||
: initiatorPlugin.Name;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.ClearIconTask();
|
||||
this.underlyingNotification.IconCreator = null;
|
||||
this.Dismiss = null;
|
||||
this.Click = null;
|
||||
this.DrawActions = null;
|
||||
this.InitiatorPlugin = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Notification CloneNotification() => this.underlyingNotification with { };
|
||||
|
||||
/// <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.hideEasing.IsRunning)
|
||||
return;
|
||||
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Updates animations.</summary>
|
||||
/// <returns><c>true</c> if the notification is over.</returns>
|
||||
public bool UpdateAnimations()
|
||||
{
|
||||
this.showEasing.Update();
|
||||
this.hideEasing.Update();
|
||||
this.progressEasing.Update();
|
||||
return this.hideEasing.IsRunning && this.hideEasing.IsDone;
|
||||
}
|
||||
|
||||
/// <summary>Draws this notification.</summary>
|
||||
/// <param name="maxWidth">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 maxWidth, float offsetY)
|
||||
{
|
||||
if (!this.IsDismissed
|
||||
&& DateTime.Now > this.Expiry
|
||||
&& (this.HoverExtendDuration <= TimeSpan.Zero || !this.IsMouseHovered))
|
||||
{
|
||||
this.DismissNow(NotificationDismissReason.Timeout);
|
||||
}
|
||||
|
||||
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 notificationManager = Service<NotificationManager>.Get();
|
||||
var interfaceManager = Service<InterfaceManager>.Get();
|
||||
var unboundedWidth = ImGui.CalcTextSize(this.Content).X;
|
||||
float closeButtonHorizontalSpaceReservation;
|
||||
using (interfaceManager.IconFontHandle?.Push())
|
||||
{
|
||||
closeButtonHorizontalSpaceReservation = ImGui.CalcTextSize(FontAwesomeIcon.Times.ToIconString()).X;
|
||||
closeButtonHorizontalSpaceReservation += NotificationConstants.ScaledWindowPadding;
|
||||
}
|
||||
|
||||
unboundedWidth = Math.Max(
|
||||
unboundedWidth,
|
||||
ImGui.CalcTextSize(this.Title ?? this.DefaultTitle ?? string.Empty).X);
|
||||
unboundedWidth = Math.Max(
|
||||
unboundedWidth,
|
||||
ImGui.CalcTextSize(this.InitiatorString).X);
|
||||
unboundedWidth = Math.Max(
|
||||
unboundedWidth,
|
||||
ImGui.CalcTextSize(this.CreatedAt.FormatAbsoluteDateTime()).X + closeButtonHorizontalSpaceReservation);
|
||||
unboundedWidth = Math.Max(
|
||||
unboundedWidth,
|
||||
ImGui.CalcTextSize(this.CreatedAt.FormatRelativeDateTime()).X + closeButtonHorizontalSpaceReservation);
|
||||
|
||||
unboundedWidth += NotificationConstants.ScaledWindowPadding * 3;
|
||||
unboundedWidth += NotificationConstants.ScaledIconSize;
|
||||
|
||||
var width = Math.Min(maxWidth, unboundedWidth);
|
||||
|
||||
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);
|
||||
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, 0), new(width, float.MaxValue));
|
||||
ImGui.PushStyleVar(
|
||||
ImGuiStyleVar.WindowPadding,
|
||||
new Vector2(NotificationConstants.ScaledWindowPadding, 0));
|
||||
ImGui.Begin(
|
||||
$"##NotifyMainWindow{this.Id}",
|
||||
ImGuiWindowFlags.AlwaysAutoResize |
|
||||
ImGuiWindowFlags.NoDecoration |
|
||||
(this.Interactible
|
||||
? ImGuiWindowFlags.None
|
||||
: ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoBringToFrontOnFocus) |
|
||||
ImGuiWindowFlags.NoNav |
|
||||
ImGuiWindowFlags.NoMove |
|
||||
ImGuiWindowFlags.NoFocusOnAppearing |
|
||||
ImGuiWindowFlags.NoDocking);
|
||||
|
||||
this.DrawNotificationMainWindowContent(notificationManager, width);
|
||||
var windowPos = ImGui.GetWindowPos();
|
||||
var windowSize = ImGui.GetWindowSize();
|
||||
var hovered = ImGui.IsWindowHovered();
|
||||
|
||||
ImGui.End();
|
||||
ImGui.PopStyleVar();
|
||||
|
||||
offsetY += windowSize.Y;
|
||||
|
||||
var actionWindowHeight =
|
||||
// Content
|
||||
ImGui.GetTextLineHeight() +
|
||||
// Top and bottom padding
|
||||
(NotificationConstants.ScaledWindowPadding * 2);
|
||||
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, actionWindowHeight));
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
|
||||
ImGui.Begin(
|
||||
$"##NotifyActionWindow{this.Id}",
|
||||
ImGuiWindowFlags.NoDecoration |
|
||||
ImGuiWindowFlags.NoNav |
|
||||
ImGuiWindowFlags.NoFocusOnAppearing |
|
||||
ImGuiWindowFlags.NoDocking);
|
||||
|
||||
this.DrawNotificationActionWindowContent(interfaceManager, width);
|
||||
windowSize.Y += actionWindowHeight;
|
||||
windowPos.Y -= actionWindowHeight;
|
||||
hovered |= ImGui.IsWindowHovered();
|
||||
|
||||
ImGui.End();
|
||||
ImGui.PopStyleVar();
|
||||
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.PopStyleVar(2);
|
||||
ImGui.PopID();
|
||||
|
||||
if (hovered)
|
||||
{
|
||||
if (this.Click is null)
|
||||
{
|
||||
if (ImGui.IsMouseClicked(ImGuiMouseButton.Left))
|
||||
this.DismissNow(NotificationDismissReason.Manual);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ImGui.IsMouseClicked(ImGuiMouseButton.Left)
|
||||
|| ImGui.IsMouseClicked(ImGuiMouseButton.Right)
|
||||
|| ImGui.IsMouseClicked(ImGuiMouseButton.Middle))
|
||||
this.Click.InvokeSafely(this);
|
||||
}
|
||||
}
|
||||
|
||||
if (windowPos.X <= ImGui.GetIO().MousePos.X
|
||||
&& windowPos.Y <= ImGui.GetIO().MousePos.Y
|
||||
&& ImGui.GetIO().MousePos.X < windowPos.X + windowSize.X
|
||||
&& ImGui.GetIO().MousePos.Y < windowPos.Y + windowSize.Y)
|
||||
{
|
||||
if (!this.IsMouseHovered)
|
||||
{
|
||||
this.IsMouseHovered = true;
|
||||
this.MouseEnter.InvokeSafely(this);
|
||||
}
|
||||
}
|
||||
else if (this.IsMouseHovered)
|
||||
{
|
||||
if (this.HoverExtendDuration > TimeSpan.Zero)
|
||||
{
|
||||
var newExpiry = DateTime.Now + this.HoverExtendDuration;
|
||||
if (newExpiry > this.Expiry)
|
||||
{
|
||||
this.underlyingNotification.Expiry = newExpiry;
|
||||
this.ExpiryRelativeToTime = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
this.IsMouseHovered = false;
|
||||
this.MouseLeave.InvokeSafely(this);
|
||||
}
|
||||
|
||||
return windowSize.Y;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Update(INotification newNotification)
|
||||
{
|
||||
if (this.IsDismissed)
|
||||
return;
|
||||
this.Content = newNotification.Content;
|
||||
this.Title = newNotification.Title;
|
||||
this.Type = newNotification.Type;
|
||||
this.IconCreator = newNotification.IconCreator;
|
||||
this.Expiry = newNotification.Expiry;
|
||||
this.Interactible = newNotification.Interactible;
|
||||
this.HoverExtendDuration = newNotification.HoverExtendDuration;
|
||||
this.Progress = newNotification.Progress;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UpdateIcon()
|
||||
{
|
||||
if (this.IsDismissed)
|
||||
return;
|
||||
this.ClearIconTask();
|
||||
this.IconTask = this.IconCreator?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>Removes non-Dalamud invocation targets from events.</summary>
|
||||
public void RemoveNonDalamudInvocations()
|
||||
{
|
||||
var dalamudContext = AssemblyLoadContext.GetLoadContext(typeof(NotificationManager).Assembly);
|
||||
this.Dismiss = RemoveNonDalamudInvocationsCore(this.Dismiss);
|
||||
this.Click = RemoveNonDalamudInvocationsCore(this.Click);
|
||||
this.DrawActions = RemoveNonDalamudInvocationsCore(this.DrawActions);
|
||||
this.MouseEnter = RemoveNonDalamudInvocationsCore(this.MouseEnter);
|
||||
this.MouseLeave = RemoveNonDalamudInvocationsCore(this.MouseLeave);
|
||||
|
||||
this.underlyingNotification.Interactible = false;
|
||||
this.IsInitiatorUnloaded = true;
|
||||
|
||||
var now = DateTime.Now;
|
||||
var newMaxExpiry = now + NotificationConstants.DefaultDisplayDuration;
|
||||
if (this.underlyingNotification.Expiry > newMaxExpiry)
|
||||
{
|
||||
this.underlyingNotification.Expiry = newMaxExpiry;
|
||||
this.ExpiryRelativeToTime = now;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
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 &&
|
||||
AssemblyLoadContext.GetLoadContext(target.GetType().Assembly) != dalamudContext)
|
||||
{
|
||||
@delegate = (T)Delegate.Remove(@delegate, il);
|
||||
}
|
||||
}
|
||||
|
||||
return @delegate;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearIconTask()
|
||||
{
|
||||
_ = this.IconTask?.ContinueWith(
|
||||
r =>
|
||||
{
|
||||
if (r.IsCompletedSuccessfully && r.Result is IDisposable d)
|
||||
d.Dispose();
|
||||
});
|
||||
this.IconTask = null;
|
||||
}
|
||||
|
||||
private void DrawNotificationMainWindowContent(NotificationManager notificationManager, float width)
|
||||
{
|
||||
var basePos = ImGui.GetCursorPos();
|
||||
this.DrawIcon(
|
||||
notificationManager,
|
||||
basePos,
|
||||
basePos + new Vector2(NotificationConstants.ScaledIconSize));
|
||||
basePos.X += NotificationConstants.ScaledIconSize + NotificationConstants.ScaledWindowPadding;
|
||||
width -= NotificationConstants.ScaledIconSize + (NotificationConstants.ScaledWindowPadding * 2);
|
||||
this.DrawTitle(basePos, basePos + new Vector2(width, 0));
|
||||
basePos.Y = ImGui.GetCursorPosY();
|
||||
this.DrawContentBody(basePos, basePos + new Vector2(width, 0));
|
||||
|
||||
// Intention was to have left, right, and bottom have the window padding and top have the component gap,
|
||||
// but as ImGui only allows horz/vert padding, we add the extra bottom padding.
|
||||
// Top padding is zero, as the action window will add the padding.
|
||||
ImGui.Dummy(new(NotificationConstants.ScaledWindowPadding));
|
||||
|
||||
float progressL, progressR;
|
||||
if (this.IsDismissed)
|
||||
{
|
||||
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;
|
||||
progressL = midpoint - (length * v);
|
||||
progressR = midpoint + (length * v);
|
||||
}
|
||||
else if (this.Expiry == DateTime.MaxValue)
|
||||
{
|
||||
if (this.Progress >= 0)
|
||||
{
|
||||
progressL = 0f;
|
||||
progressR = this.ProgressEased;
|
||||
}
|
||||
else
|
||||
{
|
||||
var elapsed = (float)(((DateTime.Now - this.CreatedAt).TotalMilliseconds %
|
||||
NotificationConstants.IndeterminateProgressbarLoopDuration) /
|
||||
NotificationConstants.IndeterminateProgressbarLoopDuration);
|
||||
progressL = Math.Max(elapsed - (1f / 3), 0f) / (2f / 3);
|
||||
progressR = Math.Min(elapsed, 2f / 3) / (2f / 3);
|
||||
progressL = MathF.Pow(progressL, 3);
|
||||
progressR = 1f - MathF.Pow(1f - progressR, 3);
|
||||
}
|
||||
|
||||
this.prevProgressL = progressL;
|
||||
this.prevProgressR = progressR;
|
||||
}
|
||||
else if (this.HoverExtendDuration > TimeSpan.Zero && this.IsMouseHovered)
|
||||
{
|
||||
progressL = 0f;
|
||||
progressR = 1f;
|
||||
this.prevProgressL = progressL;
|
||||
this.prevProgressR = progressR;
|
||||
}
|
||||
else
|
||||
{
|
||||
progressL = 1f - (float)((this.Expiry - DateTime.Now).TotalMilliseconds /
|
||||
(this.Expiry - this.ExpiryRelativeToTime).TotalMilliseconds);
|
||||
progressR = 1f;
|
||||
this.prevProgressL = progressL;
|
||||
this.prevProgressR = progressR;
|
||||
}
|
||||
|
||||
progressR = Math.Clamp(progressR, 0f, 1f);
|
||||
|
||||
var windowPos = ImGui.GetWindowPos();
|
||||
var windowSize = ImGui.GetWindowSize();
|
||||
ImGui.PushClipRect(windowPos, windowPos + windowSize, false);
|
||||
ImGui.GetWindowDrawList().AddRectFilled(
|
||||
windowPos + new Vector2(
|
||||
windowSize.X * progressL,
|
||||
windowSize.Y - NotificationConstants.ScaledExpiryProgressBarHeight),
|
||||
windowPos + windowSize with { X = windowSize.X * progressR },
|
||||
ImGui.GetColorU32(this.DefaultIconColor));
|
||||
ImGui.PopClipRect();
|
||||
}
|
||||
|
||||
private void DrawIcon(NotificationManager notificationManager, Vector2 minCoord, Vector2 maxCoord)
|
||||
{
|
||||
string? iconString = null;
|
||||
IFontHandle? fontHandle = null;
|
||||
IDalamudTextureWrap? iconTexture = null;
|
||||
switch (this.IconTask?.IsCompletedSuccessfully is true ? this.IconTask.Result : null)
|
||||
{
|
||||
case IDalamudTextureWrap wrap:
|
||||
iconTexture = wrap;
|
||||
break;
|
||||
case SeIconChar icon:
|
||||
iconString = string.Empty + (char)icon;
|
||||
fontHandle = notificationManager.IconAxisFontHandle;
|
||||
break;
|
||||
case FontAwesomeIcon icon:
|
||||
iconString = icon.ToIconString();
|
||||
fontHandle = notificationManager.IconFontAwesomeFontHandle;
|
||||
break;
|
||||
default:
|
||||
iconString = this.DefaultIconString;
|
||||
fontHandle = notificationManager.IconFontAwesomeFontHandle;
|
||||
break;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(iconString))
|
||||
{
|
||||
var dam = Service<DalamudAssetManager>.Get();
|
||||
if (this.InitiatorPlugin is null)
|
||||
{
|
||||
iconTexture = dam.GetDalamudTextureWrap(DalamudAsset.LogoSmall);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Service<PluginImageCache>.Get().TryGetIcon(
|
||||
this.InitiatorPlugin,
|
||||
this.InitiatorPlugin.Manifest,
|
||||
this.InitiatorPlugin.IsThirdParty,
|
||||
out iconTexture) || iconTexture is null)
|
||||
{
|
||||
iconTexture = this.InitiatorPlugin switch
|
||||
{
|
||||
{ IsDev: true } => dam.GetDalamudTextureWrap(DalamudAsset.DevPluginIcon),
|
||||
{ IsThirdParty: true } => dam.GetDalamudTextureWrap(DalamudAsset.ThirdInstalledIcon),
|
||||
_ => dam.GetDalamudTextureWrap(DalamudAsset.InstalledIcon),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (iconTexture is not null)
|
||||
{
|
||||
var size = iconTexture.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;
|
||||
var pos = ((minCoord + maxCoord) - size) / 2;
|
||||
ImGui.SetCursorPos(pos);
|
||||
ImGui.Image(iconTexture.ImGuiHandle, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Just making it extremely sure
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
||||
if (fontHandle is null || iconString is null)
|
||||
// ReSharper disable once HeuristicUnreachableCode
|
||||
return;
|
||||
|
||||
using (fontHandle.Push())
|
||||
{
|
||||
var size = ImGui.CalcTextSize(iconString);
|
||||
var pos = ((minCoord + maxCoord) - size) / 2;
|
||||
ImGui.SetCursorPos(pos);
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, this.DefaultIconColor);
|
||||
ImGui.TextUnformatted(iconString);
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawTitle(Vector2 minCoord, Vector2 maxCoord)
|
||||
{
|
||||
ImGui.PushTextWrapPos(maxCoord.X);
|
||||
|
||||
ImGui.SetCursorPos(minCoord);
|
||||
if ((this.Title ?? this.DefaultTitle) 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();
|
||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + NotificationConstants.ScaledComponentGap);
|
||||
}
|
||||
|
||||
private void DrawContentBody(Vector2 minCoord, Vector2 maxCoord)
|
||||
{
|
||||
ImGui.SetCursorPos(minCoord);
|
||||
ImGui.PushTextWrapPos(maxCoord.X);
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, NotificationConstants.BodyTextColor);
|
||||
ImGui.TextUnformatted(this.Content);
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.PopTextWrapPos();
|
||||
if (this.DrawActions is not null)
|
||||
{
|
||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + NotificationConstants.ScaledComponentGap);
|
||||
try
|
||||
{
|
||||
this.DrawActions.Invoke(this);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawNotificationActionWindowContent(InterfaceManager interfaceManager, float width)
|
||||
{
|
||||
ImGui.SetCursorPos(new(NotificationConstants.ScaledWindowPadding));
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, NotificationConstants.WhenTextColor);
|
||||
ImGui.TextUnformatted(
|
||||
this.IsMouseHovered
|
||||
? this.CreatedAt.FormatAbsoluteDateTime()
|
||||
: this.CreatedAt.FormatRelativeDateTime());
|
||||
ImGui.PopStyleColor();
|
||||
|
||||
this.DrawCloseButton(
|
||||
interfaceManager,
|
||||
new(width - NotificationConstants.ScaledWindowPadding, NotificationConstants.ScaledWindowPadding),
|
||||
NotificationConstants.ScaledWindowPadding);
|
||||
}
|
||||
|
||||
private void DrawCloseButton(InterfaceManager interfaceManager, Vector2 rt, float pad)
|
||||
{
|
||||
using (interfaceManager.IconFontHandle?.Push())
|
||||
{
|
||||
var str = FontAwesomeIcon.Times.ToIconString();
|
||||
var textSize = ImGui.CalcTextSize(str);
|
||||
var size = Math.Max(textSize.X, textSize.Y);
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero);
|
||||
if (!this.IsMouseHovered)
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0f);
|
||||
ImGui.PushStyleColor(ImGuiCol.Button, 0);
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, NotificationConstants.CloseTextColor);
|
||||
|
||||
ImGui.SetCursorPos(rt - new Vector2(size, 0) - new Vector2(pad));
|
||||
if (ImGui.Button(str, new(size + (pad * 2))))
|
||||
this.DismissNow();
|
||||
|
||||
ImGui.PopStyleColor(2);
|
||||
if (!this.IsMouseHovered)
|
||||
ImGui.PopStyleVar();
|
||||
ImGui.PopStyleVar();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
using System.Numerics;
|
||||
|
||||
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 show in place of this_plugin if the notification is shown by Dalamud.</summary>
|
||||
public const string DefaultInitiator = "Dalamud";
|
||||
|
||||
/// <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>Duration of show animation.</summary>
|
||||
public static readonly TimeSpan ShowAnimationDuration = TimeSpan.FromMilliseconds(300);
|
||||
|
||||
/// <summary>Default duration of the notification.</summary>
|
||||
public static readonly TimeSpan DefaultDisplayDuration = TimeSpan.FromSeconds(3);
|
||||
|
||||
/// <summary>Default duration of the notification.</summary>
|
||||
public static readonly TimeSpan DefaultHoverExtendDuration = TimeSpan.FromSeconds(3);
|
||||
|
||||
/// <summary>Duration of hide animation.</summary>
|
||||
public static readonly TimeSpan HideAnimationDuration = TimeSpan.FromMilliseconds(300);
|
||||
|
||||
/// <summary>Duration of hide animation.</summary>
|
||||
public static readonly TimeSpan ProgressAnimationDuration = TimeSpan.FromMilliseconds(200);
|
||||
|
||||
/// <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>Gets the relative time format strings.</summary>
|
||||
private static readonly (TimeSpan MinSpan, string? FormatString)[] RelativeFormatStrings =
|
||||
{
|
||||
(TimeSpan.FromDays(7), null),
|
||||
(TimeSpan.FromDays(2), "{0:%d} days ago"),
|
||||
(TimeSpan.FromDays(1), "yesterday"),
|
||||
(TimeSpan.FromHours(2), "{0:%h} hours ago"),
|
||||
(TimeSpan.FromHours(1), "an hour ago"),
|
||||
(TimeSpan.FromMinutes(2), "{0:%m} minutes ago"),
|
||||
(TimeSpan.FromMinutes(1), "a minute ago"),
|
||||
(TimeSpan.FromSeconds(2), "{0:%s} seconds ago"),
|
||||
(TimeSpan.FromSeconds(1), "a second ago"),
|
||||
(TimeSpan.MinValue, "just now"),
|
||||
};
|
||||
|
||||
/// <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(2 * ImGuiHelpers.GlobalScale);
|
||||
|
||||
/// <summary>Gets the string format of the initiator name field, if the initiator is unloaded.</summary>
|
||||
public static string UnloadedInitiatorNameFormat => "{0} (unloaded)";
|
||||
|
||||
/// <summary>
|
||||
/// Formats an instance of <see cref="DateTime"/> as a relative time.
|
||||
/// </summary>
|
||||
/// <param name="when">When.</param>
|
||||
/// <returns>The formatted string.</returns>
|
||||
public static string FormatRelativeDateTime(this DateTime when)
|
||||
{
|
||||
var ts = DateTime.Now - when;
|
||||
foreach (var (minSpan, formatString) in RelativeFormatStrings)
|
||||
{
|
||||
if (ts < minSpan)
|
||||
continue;
|
||||
if (formatString is null)
|
||||
break;
|
||||
return string.Format(formatString, ts);
|
||||
}
|
||||
|
||||
return when.FormatAbsoluteDateTime();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats an instance of <see cref="DateTime"/> as an absolute time.
|
||||
/// </summary>
|
||||
/// <param name="when">When.</param>
|
||||
/// <returns>The formatted string.</returns>
|
||||
public static string FormatAbsoluteDateTime(this DateTime when) => $"{when:G}";
|
||||
}
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
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;
|
||||
|
||||
namespace Dalamud.Interface.ImGuiNotification.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Class handling notifications/toasts in ImGui.
|
||||
/// Ported from https://github.com/patrickcjk/imgui-notify.
|
||||
/// </summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class NotificationManager : INotificationManager, IServiceType, IDisposable
|
||||
{
|
||||
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; }
|
||||
|
||||
private IFontAtlas PrivateAtlas { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.PrivateAtlas.Dispose();
|
||||
foreach (var n in this.pendingNotifications)
|
||||
n.Dispose();
|
||||
foreach (var n in this.notifications)
|
||||
n.Dispose();
|
||||
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 new 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;
|
||||
|
||||
while (this.pendingNotifications.TryTake(out var newNotification))
|
||||
this.notifications.Add(newNotification);
|
||||
|
||||
var maxWidth = Math.Max(320 * ImGuiHelpers.GlobalScale, viewportSize.X / 3);
|
||||
|
||||
this.notifications.RemoveAll(x => x.UpdateAnimations());
|
||||
foreach (var tn in this.notifications)
|
||||
height += tn.Draw(maxWidth, 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, unused) => this.notifications.TryRemove(an, 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
|
||||
namespace Dalamud.Interface.ImGuiNotification;
|
||||
|
|
|
|||
|
|
@ -1,22 +1,16 @@
|
|||
namespace Dalamud.Interface.ImGuiNotification;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the reason of dismissal for a notification.
|
||||
/// </summary>
|
||||
/// <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.Expiry"/> is met.
|
||||
/// </summary>
|
||||
/// <summary>The notification is dismissed because the expiry specified from <see cref="INotification.Expiry"/> is
|
||||
/// met.</summary>
|
||||
Timeout = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The notification is dismissed because the user clicked on the close button on a notification window.
|
||||
|
||||
/// <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>
|
||||
|
||||
/// <summary>The notification is dismissed from calling <see cref="IActiveNotification.DismissNow"/>.</summary>
|
||||
Programmatical = 3,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
namespace Dalamud.Interface.ImGuiNotification;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate representing the dismissal of an active notification.
|
||||
/// </summary>
|
||||
/// <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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue