mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Separate progress and expiry animations
This commit is contained in:
parent
f434946137
commit
e96089f8b2
6 changed files with 122 additions and 39 deletions
|
|
@ -59,6 +59,9 @@ public interface IActiveNotification : INotification
|
|||
/// <inheritdoc cref="INotification.Expiry"/>
|
||||
new DateTime Expiry { get; set; }
|
||||
|
||||
/// <inheritdoc cref="INotification.ShowIndeterminateIfNoExpiry"/>
|
||||
new bool ShowIndeterminateIfNoExpiry { get; set; }
|
||||
|
||||
/// <inheritdoc cref="INotification.Interactable"/>
|
||||
new bool Interactable { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@ public interface INotification : IDisposable
|
|||
/// (sticky, indeterminate, permanent, or persistent).</remarks>
|
||||
DateTime Expiry { get; }
|
||||
|
||||
/// <summary>Gets a value indicating whether to show an indeterminate expiration animation if <see cref="Expiry"/>
|
||||
/// is set to <see cref="DateTime.MaxValue"/>.</summary>
|
||||
bool ShowIndeterminateIfNoExpiry { 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
|
||||
|
|
@ -58,8 +62,7 @@ public interface INotification : IDisposable
|
|||
/// </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>
|
||||
/// <summary>Gets the progress for the background progress bar of the notification.</summary>
|
||||
/// <remarks>The progress should be in the range between 0 and 1.</remarks>
|
||||
float Progress { get; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ internal sealed class ActiveNotification : IActiveNotification
|
|||
this.InitiatorPlugin = initiatorPlugin;
|
||||
this.showEasing = new InCubic(NotificationConstants.ShowAnimationDuration);
|
||||
this.hideEasing = new OutCubic(NotificationConstants.HideAnimationDuration);
|
||||
this.progressEasing = new InOutCubic(NotificationConstants.ProgressAnimationDuration);
|
||||
this.progressEasing = new InOutCubic(NotificationConstants.ProgressChangeAnimationDuration);
|
||||
|
||||
this.showEasing.Start();
|
||||
this.progressEasing.Start();
|
||||
|
|
@ -142,6 +142,18 @@ internal sealed class ActiveNotification : IActiveNotification
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IActiveNotification.ShowIndeterminateIfNoExpiry"/>
|
||||
public bool ShowIndeterminateIfNoExpiry
|
||||
{
|
||||
get => this.underlyingNotification.ShowIndeterminateIfNoExpiry;
|
||||
set
|
||||
{
|
||||
if (this.IsDismissed)
|
||||
return;
|
||||
this.underlyingNotification.ShowIndeterminateIfNoExpiry = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IActiveNotification.Interactable"/>
|
||||
public bool Interactable
|
||||
{
|
||||
|
|
@ -212,9 +224,6 @@ internal sealed class ActiveNotification : IActiveNotification
|
|||
get
|
||||
{
|
||||
var underlyingProgress = this.underlyingNotification.Progress;
|
||||
if (underlyingProgress < 0)
|
||||
return 0f;
|
||||
|
||||
if (Math.Abs(underlyingProgress - this.progressBefore) < 0.000001f || this.progressEasing.IsDone)
|
||||
return underlyingProgress;
|
||||
|
||||
|
|
@ -409,6 +418,7 @@ internal sealed class ActiveNotification : IActiveNotification
|
|||
ImGuiWindowFlags.NoFocusOnAppearing |
|
||||
ImGuiWindowFlags.NoDocking);
|
||||
|
||||
this.DrawWindowBackgroundProgressBar();
|
||||
this.DrawNotificationMainWindowContent(width);
|
||||
var windowPos = ImGui.GetWindowPos();
|
||||
var windowSize = ImGui.GetWindowSize();
|
||||
|
|
@ -440,6 +450,7 @@ internal sealed class ActiveNotification : IActiveNotification
|
|||
ImGuiWindowFlags.NoFocusOnAppearing |
|
||||
ImGuiWindowFlags.NoDocking);
|
||||
|
||||
this.DrawWindowBackgroundProgressBar();
|
||||
this.DrawNotificationActionWindowContent(interfaceManager, width);
|
||||
windowSize.Y += actionWindowHeight;
|
||||
windowPos.Y -= actionWindowHeight;
|
||||
|
|
@ -517,7 +528,7 @@ internal sealed class ActiveNotification : IActiveNotification
|
|||
this.underlyingNotification.IconSource = newIconSource;
|
||||
this.UpdateIcon();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Removes non-Dalamud invocation targets from events.</summary>
|
||||
public void RemoveNonDalamudInvocations()
|
||||
{
|
||||
|
|
@ -563,6 +574,49 @@ internal sealed class ActiveNotification : IActiveNotification
|
|||
this.MaterializedIcon = null;
|
||||
}
|
||||
|
||||
private void DrawWindowBackgroundProgressBar()
|
||||
{
|
||||
var elapsed = (float)(((DateTime.Now - this.CreatedAt).TotalMilliseconds %
|
||||
NotificationConstants.ProgressWaveLoopDuration) /
|
||||
NotificationConstants.ProgressWaveLoopDuration);
|
||||
elapsed /= NotificationConstants.ProgressWaveIdleTimeRatio;
|
||||
|
||||
var colorElapsed =
|
||||
elapsed < NotificationConstants.ProgressWaveLoopMaxColorTimeRatio
|
||||
? elapsed / NotificationConstants.ProgressWaveLoopMaxColorTimeRatio
|
||||
: ((NotificationConstants.ProgressWaveLoopMaxColorTimeRatio * 2) - elapsed) /
|
||||
NotificationConstants.ProgressWaveLoopMaxColorTimeRatio;
|
||||
|
||||
elapsed = Math.Clamp(elapsed, 0f, 1f);
|
||||
colorElapsed = Math.Clamp(colorElapsed, 0f, 1f);
|
||||
colorElapsed = MathF.Sin(colorElapsed * (MathF.PI / 2f));
|
||||
|
||||
var progress = Math.Clamp(this.ProgressEased, 0f, 1f);
|
||||
if (progress >= 1f)
|
||||
elapsed = colorElapsed = 0f;
|
||||
|
||||
var windowPos = ImGui.GetWindowPos();
|
||||
var windowSize = ImGui.GetWindowSize();
|
||||
var rb = windowPos + windowSize;
|
||||
var midp = windowPos + windowSize with { X = windowSize.X * progress * elapsed };
|
||||
var rp = windowPos + windowSize with { X = windowSize.X * progress };
|
||||
|
||||
ImGui.PushClipRect(windowPos, rb, false);
|
||||
ImGui.GetWindowDrawList().AddRectFilled(
|
||||
windowPos,
|
||||
midp,
|
||||
ImGui.GetColorU32(
|
||||
Vector4.Lerp(
|
||||
NotificationConstants.BackgroundProgressColorMin,
|
||||
NotificationConstants.BackgroundProgressColorMax,
|
||||
colorElapsed)));
|
||||
ImGui.GetWindowDrawList().AddRectFilled(
|
||||
midp with { Y = 0 },
|
||||
rp,
|
||||
ImGui.GetColorU32(NotificationConstants.BackgroundProgressColorMin));
|
||||
ImGui.PopClipRect();
|
||||
}
|
||||
|
||||
private void DrawNotificationMainWindowContent(float width)
|
||||
{
|
||||
var basePos = ImGui.GetCursorPos();
|
||||
|
|
@ -580,62 +634,61 @@ internal sealed class ActiveNotification : IActiveNotification
|
|||
// Top padding is zero, as the action window will add the padding.
|
||||
ImGui.Dummy(new(NotificationConstants.ScaledWindowPadding));
|
||||
|
||||
float progressL, progressR;
|
||||
float barL, barR;
|
||||
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);
|
||||
barL = midpoint - (length * v);
|
||||
barR = midpoint + (length * v);
|
||||
}
|
||||
else if (this.Expiry == DateTime.MaxValue)
|
||||
{
|
||||
if (this.Progress >= 0)
|
||||
{
|
||||
progressL = 0f;
|
||||
progressR = this.ProgressEased;
|
||||
}
|
||||
else
|
||||
if (this.ShowIndeterminateIfNoExpiry)
|
||||
{
|
||||
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);
|
||||
barL = Math.Max(elapsed - (1f / 3), 0f) / (2f / 3);
|
||||
barR = Math.Min(elapsed, 2f / 3) / (2f / 3);
|
||||
barL = MathF.Pow(barL, 3);
|
||||
barR = 1f - MathF.Pow(1f - barR, 3);
|
||||
this.prevProgressL = barL;
|
||||
this.prevProgressR = barR;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.prevProgressL = barL = 0f;
|
||||
this.prevProgressR = barR = 1f;
|
||||
}
|
||||
|
||||
this.prevProgressL = progressL;
|
||||
this.prevProgressR = progressR;
|
||||
}
|
||||
else if (this.HoverExtendDuration > TimeSpan.Zero && this.IsMouseHovered)
|
||||
{
|
||||
progressL = 0f;
|
||||
progressR = 1f;
|
||||
this.prevProgressL = progressL;
|
||||
this.prevProgressR = progressR;
|
||||
barL = 0f;
|
||||
barR = 1f;
|
||||
this.prevProgressL = barL;
|
||||
this.prevProgressR = barR;
|
||||
}
|
||||
else
|
||||
{
|
||||
progressL = 1f - (float)((this.Expiry - DateTime.Now).TotalMilliseconds /
|
||||
(this.Expiry - this.ExpiryRelativeToTime).TotalMilliseconds);
|
||||
progressR = 1f;
|
||||
this.prevProgressL = progressL;
|
||||
this.prevProgressR = progressR;
|
||||
barL = 1f - (float)((this.Expiry - DateTime.Now).TotalMilliseconds /
|
||||
(this.Expiry - this.ExpiryRelativeToTime).TotalMilliseconds);
|
||||
barR = 1f;
|
||||
this.prevProgressL = barL;
|
||||
this.prevProgressR = barR;
|
||||
}
|
||||
|
||||
progressR = Math.Clamp(progressR, 0f, 1f);
|
||||
barR = Math.Clamp(barR, 0f, 1f);
|
||||
|
||||
var windowPos = ImGui.GetWindowPos();
|
||||
var windowSize = ImGui.GetWindowSize();
|
||||
ImGui.PushClipRect(windowPos, windowPos + windowSize, false);
|
||||
ImGui.GetWindowDrawList().AddRectFilled(
|
||||
windowPos + new Vector2(
|
||||
windowSize.X * progressL,
|
||||
windowSize.X * barL,
|
||||
windowSize.Y - NotificationConstants.ScaledExpiryProgressBarHeight),
|
||||
windowPos + windowSize with { X = windowSize.X * progressR },
|
||||
windowPos + windowSize with { X = windowSize.X * barR },
|
||||
ImGui.GetColorU32(this.DefaultIconColor));
|
||||
ImGui.PopClipRect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ public sealed record Notification : INotification
|
|||
/// <inheritdoc/>
|
||||
public DateTime Expiry { get; set; } = DateTime.Now + NotificationConstants.DefaultDisplayDuration;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool ShowIndeterminateIfNoExpiry { get; set; } = true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Interactable { get; set; } = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -37,14 +37,24 @@ public static class NotificationConstants
|
|||
/// <summary>The duration of indeterminate progress bar loop in milliseconds.</summary>
|
||||
internal const float IndeterminateProgressbarLoopDuration = 2000f;
|
||||
|
||||
/// <summary>The duration of the progress wave animation in milliseconds.</summary>
|
||||
internal const float ProgressWaveLoopDuration = 2000f;
|
||||
|
||||
/// <summary>The time ratio of a progress wave loop where the animation is idle.</summary>
|
||||
internal const float ProgressWaveIdleTimeRatio = 0.5f;
|
||||
|
||||
/// <summary>The time ratio of a non-idle portion of the progress wave loop where the color is the most opaque.
|
||||
/// </summary>
|
||||
internal const float ProgressWaveLoopMaxColorTimeRatio = 0.7f;
|
||||
|
||||
/// <summary>Duration of show animation.</summary>
|
||||
internal static readonly TimeSpan ShowAnimationDuration = TimeSpan.FromMilliseconds(300);
|
||||
|
||||
/// <summary>Duration of hide animation.</summary>
|
||||
internal static readonly TimeSpan HideAnimationDuration = TimeSpan.FromMilliseconds(300);
|
||||
|
||||
/// <summary>Duration of hide animation.</summary>
|
||||
internal static readonly TimeSpan ProgressAnimationDuration = TimeSpan.FromMilliseconds(200);
|
||||
/// <summary>Duration of progress change animation.</summary>
|
||||
internal static readonly TimeSpan ProgressChangeAnimationDuration = TimeSpan.FromMilliseconds(200);
|
||||
|
||||
/// <summary>Text color for the when.</summary>
|
||||
internal static readonly Vector4 WhenTextColor = new(0.8f, 0.8f, 0.8f, 1f);
|
||||
|
|
@ -61,6 +71,12 @@ public static class NotificationConstants
|
|||
/// <summary>Text color for the body.</summary>
|
||||
internal static readonly Vector4 BodyTextColor = new(0.9f, 0.9f, 0.9f, 1f);
|
||||
|
||||
/// <summary>Color for the background progress bar (determinate progress only).</summary>
|
||||
internal static readonly Vector4 BackgroundProgressColorMax = new(1f, 1f, 1f, 0.1f);
|
||||
|
||||
/// <summary>Color for the background progress bar (determinate progress only).</summary>
|
||||
internal static readonly Vector4 BackgroundProgressColorMin = new(1f, 1f, 1f, 0.05f);
|
||||
|
||||
/// <summary>Gets the relative time format strings.</summary>
|
||||
private static readonly (TimeSpan MinSpan, string? FormatString)[] RelativeFormatStrings =
|
||||
{
|
||||
|
|
@ -94,7 +110,7 @@ public static class NotificationConstants
|
|||
internal static float ScaledIconSize => MathF.Round(IconSize * ImGuiHelpers.GlobalScale);
|
||||
|
||||
/// <summary>Gets the height of the expiry progress bar.</summary>
|
||||
internal static float ScaledExpiryProgressBarHeight => MathF.Round(2 * ImGuiHelpers.GlobalScale);
|
||||
internal static float ScaledExpiryProgressBarHeight => MathF.Round(3 * ImGuiHelpers.GlobalScale);
|
||||
|
||||
/// <summary>Gets the string format of the initiator name field, if the initiator is unloaded.</summary>
|
||||
internal static string UnloadedInitiatorNameFormat => "{0} (unloaded)";
|
||||
|
|
|
|||
|
|
@ -120,6 +120,8 @@ internal class ImGuiWidget : IDataWindowWidget
|
|||
|
||||
ImGui.Checkbox("Interactable", ref this.notificationTemplate.Interactable);
|
||||
|
||||
ImGui.Checkbox("Show Indeterminate If No Expiry", ref this.notificationTemplate.ShowIndeterminateIfNoExpiry);
|
||||
|
||||
ImGui.Checkbox("User Dismissable", ref this.notificationTemplate.UserDismissable);
|
||||
|
||||
ImGui.Checkbox(
|
||||
|
|
@ -147,6 +149,7 @@ internal class ImGuiWidget : IDataWindowWidget
|
|||
Content = text,
|
||||
Title = title,
|
||||
Type = type,
|
||||
ShowIndeterminateIfNoExpiry = this.notificationTemplate.ShowIndeterminateIfNoExpiry,
|
||||
Interactable = this.notificationTemplate.Interactable,
|
||||
UserDismissable = this.notificationTemplate.UserDismissable,
|
||||
Expiry = duration == TimeSpan.MaxValue ? DateTime.MaxValue : DateTime.Now + duration,
|
||||
|
|
@ -347,6 +350,7 @@ internal class ImGuiWidget : IDataWindowWidget
|
|||
public bool ManualType;
|
||||
public int TypeInt;
|
||||
public int DurationInt;
|
||||
public bool ShowIndeterminateIfNoExpiry;
|
||||
public bool Interactable;
|
||||
public bool UserDismissable;
|
||||
public bool ActionBar;
|
||||
|
|
@ -364,6 +368,7 @@ internal class ImGuiWidget : IDataWindowWidget
|
|||
this.ManualType = false;
|
||||
this.TypeInt = (int)NotificationType.None;
|
||||
this.DurationInt = 2;
|
||||
this.ShowIndeterminateIfNoExpiry = true;
|
||||
this.Interactable = true;
|
||||
this.UserDismissable = true;
|
||||
this.ActionBar = true;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue