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"))