diff --git a/Dalamud/Interface/ImGuiNotification/IActiveNotification.cs b/Dalamud/Interface/ImGuiNotification/IActiveNotification.cs
index 504c6d6d5..dd4101c92 100644
--- a/Dalamud/Interface/ImGuiNotification/IActiveNotification.cs
+++ b/Dalamud/Interface/ImGuiNotification/IActiveNotification.cs
@@ -49,7 +49,10 @@ public interface IActiveNotification : INotification
DateTime EffectiveExpiry { get; }
/// Gets a value indicating whether the mouse cursor is on the notification window.
- bool IsMouseHovered { get; }
+ bool IsHovered { get; }
+
+ /// Gets a value indicating whether the notification window is focused.
+ bool IsFocused { get; }
/// Gets a value indicating whether the notification has been dismissed.
/// This includes when the hide animation is being played.
diff --git a/Dalamud/Interface/ImGuiNotification/INotification.cs b/Dalamud/Interface/ImGuiNotification/INotification.cs
index 8f5a30e79..349d66f72 100644
--- a/Dalamud/Interface/ImGuiNotification/INotification.cs
+++ b/Dalamud/Interface/ImGuiNotification/INotification.cs
@@ -33,7 +33,7 @@ public interface INotification : IDisposable
/// Gets or sets the hard expiry.
///
- /// Setting this value will override and , in that
+ /// Setting this value will override and , in that
/// the notification will be dismissed when this expiry expires.
/// Set to to make only take effect.
/// If neither nor is not MaxValue, then the notification
@@ -48,13 +48,14 @@ public interface INotification : IDisposable
/// Updating this value will reset the dismiss timer.
TimeSpan InitialDuration { get; set; }
- /// Gets or sets the new duration for this notification once the mouse cursor leaves the window.
+ /// Gets or sets the new duration for this notification once the mouse cursor leaves the window and the
+ /// window is no longer focused.
///
/// If set to or less, then this feature is turned off, and hovering the mouse on the
- /// notification will not make the notification stay.
+ /// notification or focusing on it will not make the notification stay.
/// Updating this value will reset the dismiss timer.
///
- TimeSpan HoverExtendDuration { get; set; }
+ TimeSpan DurationSinceLastInterest { get; set; }
/// Gets or sets a value indicating whether to show an indeterminate expiration animation if
/// is set to .
diff --git a/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.cs b/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.cs
index a89ebeb0b..8591695a6 100644
--- a/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.cs
+++ b/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.cs
@@ -93,7 +93,7 @@ internal sealed class ActiveNotification : IActiveNotification
public DateTime CreatedAt { get; } = DateTime.Now;
/// Gets the time of starting to count the timer for the expiration.
- public DateTime HoverRelativeToTime { get; private set; } = DateTime.Now;
+ public DateTime LastInterestTime { get; private set; } = DateTime.Now;
/// Gets the extended expiration time from .
public DateTime ExtendedExpiry { get; private set; } = DateTime.Now;
@@ -172,7 +172,7 @@ internal sealed class ActiveNotification : IActiveNotification
if (this.underlyingNotification.HardExpiry == value || this.IsDismissed)
return;
this.underlyingNotification.HardExpiry = value;
- this.HoverRelativeToTime = DateTime.Now;
+ this.LastInterestTime = DateTime.Now;
}
}
@@ -185,20 +185,20 @@ internal sealed class ActiveNotification : IActiveNotification
if (this.IsDismissed)
return;
this.underlyingNotification.InitialDuration = value;
- this.HoverRelativeToTime = DateTime.Now;
+ this.LastInterestTime = DateTime.Now;
}
}
///
- public TimeSpan HoverExtendDuration
+ public TimeSpan DurationSinceLastInterest
{
- get => this.underlyingNotification.HoverExtendDuration;
+ get => this.underlyingNotification.DurationSinceLastInterest;
set
{
if (this.IsDismissed)
return;
- this.underlyingNotification.HoverExtendDuration = value;
- this.HoverRelativeToTime = DateTime.Now;
+ this.underlyingNotification.DurationSinceLastInterest = value;
+ this.LastInterestTime = DateTime.Now;
}
}
@@ -214,8 +214,8 @@ internal sealed class ActiveNotification : IActiveNotification
: this.CreatedAt + initialDuration;
DateTime expiry;
- var hoverExtendDuration = this.HoverExtendDuration;
- if (hoverExtendDuration > TimeSpan.Zero && this.IsMouseHovered)
+ var hoverExtendDuration = this.DurationSinceLastInterest;
+ if (hoverExtendDuration > TimeSpan.Zero && (this.IsHovered || this.IsFocused))
{
expiry = DateTime.MaxValue;
}
@@ -224,7 +224,7 @@ internal sealed class ActiveNotification : IActiveNotification
var expiryExtend =
hoverExtendDuration == TimeSpan.MaxValue
? DateTime.MaxValue
- : this.HoverRelativeToTime + hoverExtendDuration;
+ : this.LastInterestTime + hoverExtendDuration;
expiry = expiryInitial > expiryExtend ? expiryInitial : expiryExtend;
if (expiry < this.ExtendedExpiry)
@@ -287,7 +287,10 @@ internal sealed class ActiveNotification : IActiveNotification
}
///
- public bool IsMouseHovered { get; private set; }
+ public bool IsHovered { get; private set; }
+
+ ///
+ public bool IsFocused { get; private set; }
///
public bool IsDismissed => this.hideEasing.IsRunning;
@@ -529,8 +532,12 @@ internal sealed class ActiveNotification : IActiveNotification
ImGuiWindowFlags.NoMove |
ImGuiWindowFlags.NoFocusOnAppearing |
ImGuiWindowFlags.NoDocking);
+ this.IsFocused = ImGui.IsWindowFocused();
+ if (this.IsFocused)
+ this.LastInterestTime = DateTime.Now;
this.DrawWindowBackgroundProgressBar();
+ this.DrawFocusIndicator();
this.DrawTopBar(interfaceManager, width, actionWindowHeight);
if (!this.underlyingNotification.Minimized && !this.expandoEasing.IsRunning)
{
@@ -562,14 +569,14 @@ internal sealed class ActiveNotification : IActiveNotification
&& ImGui.GetIO().MousePos.X < windowPos.X + windowSize.X
&& ImGui.GetIO().MousePos.Y < windowPos.Y + windowSize.Y)
{
- if (!this.IsMouseHovered)
+ if (!this.IsHovered)
{
- this.IsMouseHovered = true;
+ this.IsHovered = true;
this.MouseEnter.InvokeSafely(this);
}
- if (this.HoverExtendDuration > TimeSpan.Zero)
- this.HoverRelativeToTime = DateTime.Now;
+ if (this.DurationSinceLastInterest > TimeSpan.Zero)
+ this.LastInterestTime = DateTime.Now;
if (hovered)
{
@@ -587,9 +594,9 @@ internal sealed class ActiveNotification : IActiveNotification
}
}
}
- else if (this.IsMouseHovered)
+ else if (this.IsHovered)
{
- this.IsMouseHovered = false;
+ this.IsHovered = false;
this.MouseLeave.InvokeSafely(this);
}
@@ -625,7 +632,7 @@ internal sealed class ActiveNotification : IActiveNotification
this.IsInitiatorUnloaded = true;
this.UserDismissable = true;
- this.HoverExtendDuration = NotificationConstants.DefaultHoverExtendDuration;
+ this.DurationSinceLastInterest = NotificationConstants.DefaultHoverExtendDuration;
var newMaxExpiry = DateTime.Now + NotificationConstants.DefaultDisplayDuration;
if (this.EffectiveExpiry > newMaxExpiry)
@@ -700,6 +707,23 @@ internal sealed class ActiveNotification : IActiveNotification
ImGui.PopClipRect();
}
+ private void DrawFocusIndicator()
+ {
+ if (!this.IsFocused)
+ return;
+ var windowPos = ImGui.GetWindowPos();
+ var windowSize = ImGui.GetWindowSize();
+ ImGui.PushClipRect(windowPos, windowPos + windowSize, false);
+ ImGui.GetWindowDrawList().AddRect(
+ windowPos,
+ windowPos + windowSize,
+ ImGui.GetColorU32(NotificationConstants.FocusBorderColor * new Vector4(1f, 1f, 1f, ImGui.GetStyle().Alpha)),
+ 0f,
+ ImDrawFlags.None,
+ NotificationConstants.FocusIndicatorThickness);
+ ImGui.PopClipRect();
+ }
+
private void DrawTopBar(InterfaceManager interfaceManager, float width, float height)
{
var windowPos = ImGui.GetWindowPos();
@@ -744,7 +768,7 @@ internal sealed class ActiveNotification : IActiveNotification
relativeOpacity = this.underlyingNotification.Minimized ? 0f : 1f;
}
- if (this.IsMouseHovered)
+ if (this.IsHovered || this.IsFocused)
ImGui.PushClipRect(windowPos, windowPos + rtOffset with { Y = height }, false);
else
ImGui.PushClipRect(windowPos, windowPos + windowSize with { Y = height }, false);
@@ -755,7 +779,7 @@ internal sealed class ActiveNotification : IActiveNotification
ImGui.SetCursorPos(new(NotificationConstants.ScaledWindowPadding));
ImGui.PushStyleColor(ImGuiCol.Text, NotificationConstants.WhenTextColor);
ImGui.TextUnformatted(
- this.IsMouseHovered
+ this.IsHovered || this.IsFocused
? this.CreatedAt.FormatAbsoluteDateTime()
: this.CreatedAt.FormatRelativeDateTime());
ImGui.PopStyleColor();
@@ -799,7 +823,8 @@ internal sealed class ActiveNotification : IActiveNotification
private bool DrawIconButton(FontAwesomeIcon icon, Vector2 rt, float size)
{
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero);
- if (!this.IsMouseHovered)
+ var alphaPush = !this.IsHovered && !this.IsFocused;
+ if (alphaPush)
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0f);
ImGui.PushStyleColor(ImGuiCol.Button, 0);
ImGui.PushStyleColor(ImGuiCol.Text, NotificationConstants.CloseTextColor);
@@ -808,7 +833,7 @@ internal sealed class ActiveNotification : IActiveNotification
var r = ImGui.Button(icon.ToIconString(), new(size));
ImGui.PopStyleColor(2);
- if (!this.IsMouseHovered)
+ if (alphaPush)
ImGui.PopStyleVar();
ImGui.PopStyleVar();
return r;
@@ -912,7 +937,7 @@ internal sealed class ActiveNotification : IActiveNotification
barL = midpoint - (length * v);
barR = midpoint + (length * v);
}
- else if (this.HoverExtendDuration > TimeSpan.Zero && this.IsMouseHovered)
+ else if (this.DurationSinceLastInterest > TimeSpan.Zero && (this.IsHovered || this.IsFocused))
{
barL = 0f;
barR = 1f;
@@ -942,7 +967,7 @@ internal sealed class ActiveNotification : IActiveNotification
else
{
barL = 1f - (float)((effectiveExpiry - DateTime.Now).TotalMilliseconds /
- (effectiveExpiry - this.HoverRelativeToTime).TotalMilliseconds);
+ (effectiveExpiry - this.LastInterestTime).TotalMilliseconds);
barR = 1f;
this.prevProgressL = barL;
this.prevProgressR = barR;
diff --git a/Dalamud/Interface/ImGuiNotification/Notification.cs b/Dalamud/Interface/ImGuiNotification/Notification.cs
index dd1d87c42..33a3ad974 100644
--- a/Dalamud/Interface/ImGuiNotification/Notification.cs
+++ b/Dalamud/Interface/ImGuiNotification/Notification.cs
@@ -53,7 +53,7 @@ public sealed record Notification : INotification
public TimeSpan InitialDuration { get; set; } = NotificationConstants.DefaultDisplayDuration;
///
- public TimeSpan HoverExtendDuration { get; set; } = NotificationConstants.DefaultHoverExtendDuration;
+ public TimeSpan DurationSinceLastInterest { get; set; } = NotificationConstants.DefaultHoverExtendDuration;
///
public bool ShowIndeterminateIfNoExpiry { get; set; } = true;
@@ -85,7 +85,7 @@ public sealed record Notification : INotification
this.IconSource = copyFrom.IconSource?.Clone();
this.HardExpiry = copyFrom.HardExpiry;
this.InitialDuration = copyFrom.InitialDuration;
- this.HoverExtendDuration = copyFrom.HoverExtendDuration;
+ this.DurationSinceLastInterest = copyFrom.DurationSinceLastInterest;
this.ShowIndeterminateIfNoExpiry = copyFrom.ShowIndeterminateIfNoExpiry;
this.Minimized = copyFrom.Minimized;
this.UserDismissable = copyFrom.UserDismissable;
diff --git a/Dalamud/Interface/ImGuiNotification/NotificationConstants.cs b/Dalamud/Interface/ImGuiNotification/NotificationConstants.cs
index 08ef8aebd..d02ff47f5 100644
--- a/Dalamud/Interface/ImGuiNotification/NotificationConstants.cs
+++ b/Dalamud/Interface/ImGuiNotification/NotificationConstants.cs
@@ -60,6 +60,9 @@ public static class NotificationConstants
/// Duration of expando animation.
internal static readonly TimeSpan ExpandoAnimationDuration = TimeSpan.FromMilliseconds(300);
+ /// Text color for the rectangular border when the notification is focused.
+ internal static readonly Vector4 FocusBorderColor = new(0.4f, 0.4f, 0.4f, 1f);
+
/// Text color for the when.
internal static readonly Vector4 WhenTextColor = new(0.8f, 0.8f, 0.8f, 1f);
@@ -126,6 +129,9 @@ public static class NotificationConstants
/// Gets the height of the expiry progress bar.
internal static float ScaledExpiryProgressBarHeight => MathF.Round(3 * ImGuiHelpers.GlobalScale);
+ /// Gets the thickness of the focus indicator rectangle.
+ internal static float FocusIndicatorThickness => MathF.Round(3 * ImGuiHelpers.GlobalScale);
+
/// Gets the string format of the initiator name field, if the initiator is unloaded.
internal static string UnloadedInitiatorNameFormat => "{0} (unloaded)";
diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/ImGuiWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/ImGuiWidget.cs
index 4d3807417..dcd193496 100644
--- a/Dalamud/Interface/Internal/Windows/Data/Widgets/ImGuiWidget.cs
+++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/ImGuiWidget.cs
@@ -166,7 +166,7 @@ internal class ImGuiWidget : IDataWindowWidget
this.notificationTemplate.InitialDurationInt == 0
? TimeSpan.MaxValue
: NotificationTemplate.Durations[this.notificationTemplate.InitialDurationInt],
- HoverExtendDuration =
+ DurationSinceLastInterest =
this.notificationTemplate.HoverExtendDurationInt == 0
? TimeSpan.Zero
: NotificationTemplate.Durations[this.notificationTemplate.HoverExtendDurationInt],
@@ -246,10 +246,12 @@ internal class ImGuiWidget : IDataWindowWidget
if (this.notificationTemplate.ActionBar || !this.notificationTemplate.UserDismissable)
{
var nclick = 0;
+ var testString = "input";
+
n.Click += _ => nclick++;
n.DrawActions += an =>
{
- if (ImGui.Button("Update in place"))
+ if (ImGui.Button("Update"))
{
NewRandom(out title, out type, out progress);
an.Title = title;
@@ -257,7 +259,10 @@ internal class ImGuiWidget : IDataWindowWidget
an.Progress = progress;
}
- if (an.IsMouseHovered)
+ ImGui.SameLine();
+ ImGui.InputText("##input", ref testString, 255);
+
+ if (an.IsHovered)
{
ImGui.SameLine();
if (ImGui.Button("Dismiss"))