diff --git a/ChatTwo/Configuration.cs b/ChatTwo/Configuration.cs index f9bf730..2afa0c7 100755 --- a/ChatTwo/Configuration.cs +++ b/ChatTwo/Configuration.cs @@ -20,6 +20,8 @@ internal class Configuration : IPluginConfiguration public bool HideWhenUiHidden = true; public bool HideInLoadingScreens; public bool HideInBattle; + public bool HideWhenInactive; + public int InactivityHideTimeout = 10; public bool ShowHideButton = true; public bool NativeItemTooltips = true; public bool PrettierTimestamps = true; @@ -78,6 +80,8 @@ internal class Configuration : IPluginConfiguration HideWhenUiHidden = other.HideWhenUiHidden; HideInLoadingScreens = other.HideInLoadingScreens; HideInBattle = other.HideInBattle; + HideWhenInactive = other.HideWhenInactive; + InactivityHideTimeout = other.InactivityHideTimeout; ShowHideButton = other.ShowHideButton; NativeItemTooltips = other.NativeItemTooltips; PrettierTimestamps = other.PrettierTimestamps; @@ -168,6 +172,9 @@ internal class Tab [NonSerialized] public uint Unread; + [NonSerialized] + public long LastMessageTime; + [NonSerialized] public MessageList Messages = new(); @@ -192,7 +199,10 @@ internal class Tab { Messages.AddPrune(message, MessageManager.MessageDisplayLimit); if (unread) + { Unread += 1; + LastMessageTime = Environment.TickCount64; + } } internal void Clear() diff --git a/ChatTwo/GameFunctions/Chat.cs b/ChatTwo/GameFunctions/Chat.cs index 0eea03f..421784f 100755 --- a/ChatTwo/GameFunctions/Chat.cs +++ b/ChatTwo/GameFunctions/Chat.cs @@ -253,9 +253,6 @@ internal sealed unsafe class Chat : IDisposable LastRefresh = Environment.TickCount64; } - if (Plugin.ChatLogWindow is { CurrentTab.InputDisabled: true, IsHidden: false }) - return; - // Vanilla text input has focus if (RaptureAtkModule.Instance()->AtkModule.IsTextInputActive()) return; diff --git a/ChatTwo/Resources/Language.Designer.cs b/ChatTwo/Resources/Language.Designer.cs index 022c8ac..606f01a 100755 --- a/ChatTwo/Resources/Language.Designer.cs +++ b/ChatTwo/Resources/Language.Designer.cs @@ -2417,6 +2417,24 @@ namespace ChatTwo.Resources { } } + /// + /// Looks up a localized string similar to Hide the chat after a configurable period of inactivity. The current tab and any tabs with unread indicators enabled are considered for activity.. + /// + internal static string Options_HideWhenInactive_Description { + get { + return ResourceManager.GetString("Options_HideWhenInactive_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Hide when inactive. + /// + internal static string Options_HideWhenInactive_Name { + get { + return ResourceManager.GetString("Options_HideWhenInactive_Name", resourceCulture); + } + } + /// /// Looks up a localized string similar to Hide {0} when you are not logged in to a character.. /// @@ -2453,6 +2471,24 @@ namespace ChatTwo.Resources { } } + /// + /// Looks up a localized string similar to How long to wait (in seconds) before considering the chat log inactive.. + /// + internal static string Options_InactivityHideTimeout_Description { + get { + return ResourceManager.GetString("Options_InactivityHideTimeout_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Inactivity timeout. + /// + internal static string Options_InactivityHideTimeout_Name { + get { + return ResourceManager.GetString("Options_InactivityHideTimeout_Name", resourceCulture); + } + } + /// /// Looks up a localized string similar to The font {0} will use to display Japanese text.. /// diff --git a/ChatTwo/Resources/Language.resx b/ChatTwo/Resources/Language.resx index f535927..ec6eb53 100644 --- a/ChatTwo/Resources/Language.resx +++ b/ChatTwo/Resources/Language.resx @@ -406,6 +406,18 @@ Hide {0} when the game UI is hidden. + + Hide when inactive + + + Hide the chat after a configurable period of inactivity. The current tab and any tabs with unread indicators enabled are considered for activity. + + + Inactivity timeout + + + How long to wait (in seconds) before considering the chat log inactive. + Keybind mode diff --git a/ChatTwo/Ui/ChatLogWindow.cs b/ChatTwo/Ui/ChatLogWindow.cs index 3a26655..86991d2 100644 --- a/ChatTwo/Ui/ChatLogWindow.cs +++ b/ChatTwo/Ui/ChatLogWindow.cs @@ -85,6 +85,9 @@ public sealed class ChatLogWindow : Window private const uint ChatCloseSfx = 3u; private bool PlayedClosingSound = true; + private long FrameTime; // set every frame + private long LastActivityTime = Environment.TickCount64; + private readonly ExcelSheet WorldSheet; private readonly ExcelSheet LogFilterSheet; private readonly ExcelSheet TextCommandSheet; @@ -148,6 +151,18 @@ public sealed class ChatLogWindow : Window private void Activated(ChatActivatedArgs args) { Activate = true; + PlayedClosingSound = false; + if (Plugin.Config.PlaySounds) + UIModule.PlaySound(ChatOpenSfx); + + // Don't set the channel or text content when activating a disabled tab. + if (CurrentTab?.InputDisabled == true) + { + // The closing sound would've been immediately played in this case. + PlayedClosingSound = true; + return; + } + if (args.AddIfNotPresent != null && !Chat.Contains(args.AddIfNotPresent)) Chat += args.AddIfNotPresent; @@ -203,10 +218,6 @@ public sealed class ChatLogWindow : Window if (info.Text != null && Chat.Length == 0) Chat = info.Text; - - PlayedClosingSound = false; - if (Plugin.Config.PlaySounds) - UIModule.PlaySound(ChatOpenSfx); } private bool IsValidCommand(string command) @@ -471,7 +482,23 @@ public sealed class ChatLogWindow : Window public override bool DrawConditions() { - return !IsHidden; + FrameTime = Environment.TickCount64; + if (IsHidden) + return false; + if (!Plugin.Config.HideWhenInactive || Activate) + { + LastActivityTime = FrameTime; + return true; + } + + var currentTab = CurrentTab; // local to avoid calling the getter repeatedly + var lastActivityTime = Plugin.Config.Tabs + .Where(tab => tab.UnreadMode is not UnreadMode.None || tab == currentTab) + .Select(tab => tab.LastMessageTime) + .DefaultIfEmpty(0) + .Max(); + lastActivityTime = Math.Max(lastActivityTime, LastActivityTime); + return FrameTime - lastActivityTime <= 1000 * Plugin.Config.InactivityHideTimeout; } public override void PreDraw() @@ -485,6 +512,12 @@ public sealed class ChatLogWindow : Window public override void PostDraw() { + // Set Activate to false after draw to avoid repeatedly trying to focus + // the text input in a tab with input disabled. The usual way that + // Activate gets disabled is via the text input callback, but that + // doesn't get called if the input is disabled. + if (CurrentTab?.InputDisabled == true) + Activate = false; if (Plugin.Config is { OverrideStyle: true, ChosenStyle: not null }) StyleModel.GetConfiguredStyles()?.FirstOrDefault(style => style.Name == Plugin.Config.ChosenStyle)?.Pop(); } @@ -740,7 +773,10 @@ public sealed class ChatLogWindow : Window } if (ImGui.IsItemActive()) + { HandleKeybinds(true); + LastActivityTime = FrameTime; + } // Only trigger unfocused if we are currently not calling the auto complete if (!Activate && !ImGui.IsItemActive() && AutoCompleteInfo == null) @@ -785,6 +821,9 @@ public sealed class ChatLogWindow : Window UserHide(); } + if (ImGui.IsWindowHovered(ImGuiHoveredFlags.ChildWindows)) + LastActivityTime = FrameTime; + if (!showNovice) return; diff --git a/ChatTwo/Ui/SettingsTabs/Display.cs b/ChatTwo/Ui/SettingsTabs/Display.cs index 36a0171..e3d773f 100755 --- a/ChatTwo/Ui/SettingsTabs/Display.cs +++ b/ChatTwo/Ui/SettingsTabs/Display.cs @@ -1,7 +1,5 @@ using ChatTwo.Resources; using ChatTwo.Util; -using Dalamud.Interface.Style; -using Dalamud.Interface.Utility.Raii; using ImGuiNET; namespace ChatTwo.Ui.SettingsTabs; @@ -39,6 +37,19 @@ internal sealed class Display : ISettingsTab ImGuiUtil.OptionCheckbox(ref Mutable.HideInBattle, Language.Options_HideInBattle_Name, Language.Options_HideInBattle_Description); ImGui.Spacing(); + ImGuiUtil.OptionCheckbox(ref Mutable.HideWhenInactive, Language.Options_HideWhenInactive_Name, Language.Options_HideWhenInactive_Description); + ImGui.Spacing(); + + if (Mutable.HideWhenInactive) + { + ImGuiUtil.InputIntVertical(Language.Options_InactivityHideTimeout_Name, + Language.Options_InactivityHideTimeout_Description, ref Mutable.InactivityHideTimeout, 1, 10); + // Enforce a minimum of 2 seconds to avoid people soft locking + // themselves. + Mutable.InactivityHideTimeout = Math.Max(2, Mutable.InactivityHideTimeout); + ImGui.Spacing(); + } + ImGuiUtil.OptionCheckbox(ref Mutable.PrettierTimestamps, Language.Options_PrettierTimestamps_Name, Language.Options_PrettierTimestamps_Description); if (Mutable.PrettierTimestamps)