From 352088dfed284f80cbe33ea4793eb753ffebdb49 Mon Sep 17 00:00:00 2001 From: Dean Sheather Date: Thu, 11 Jul 2024 00:32:13 +1000 Subject: [PATCH] feat: add configurable hotkeys to cycle tabs Adds two configurable hotkeys (plus the required code infrastructure to handle configurable hotkeys) for cycling the active chat tab forward by one and backwards by one. --- ChatTwo/Configuration.cs | 26 ++++++++ ChatTwo/GameFunctions/Chat.cs | 45 ++++++++----- ChatTwo/GameFunctions/Types/ModifierFlag.cs | 1 + ChatTwo/Resources/Language.Designer.cs | 63 +++++++++++++++++++ ChatTwo/Resources/Language.resx | 21 +++++++ ChatTwo/Ui/ChatLogWindow.cs | 62 +++++++++++++----- ChatTwo/Ui/SettingsTabs/ChatLog.cs | 12 ++++ ChatTwo/Util/ImGuiUtil.cs | 70 +++++++++++++++++++++ 8 files changed, 272 insertions(+), 28 deletions(-) diff --git a/ChatTwo/Configuration.cs b/ChatTwo/Configuration.cs index 2afa0c7..139282a 100755 --- a/ChatTwo/Configuration.cs +++ b/ChatTwo/Configuration.cs @@ -1,12 +1,33 @@ using System.Collections; using ChatTwo.Code; +using ChatTwo.GameFunctions.Types; using ChatTwo.Resources; using ChatTwo.Ui; using Dalamud.Configuration; +using Dalamud.Game.ClientState.Keys; using ImGuiNET; namespace ChatTwo; +[Serializable] +internal class ConfigKeyBind +{ + public ModifierFlag Modifier; + public VirtualKey Key; + + public override string ToString() + { + var modString = ""; + if (Modifier.HasFlag(ModifierFlag.Ctrl)) + modString += Language.Keybind_Modifier_Ctrl + " + "; + if (Modifier.HasFlag(ModifierFlag.Shift)) + modString += Language.Keybind_Modifier_Shift + " + "; + if (Modifier.HasFlag(ModifierFlag.Alt)) + modString += Language.Keybind_Modifier_Alt + " + "; + return modString+Key.GetFancyName(); + } +} + [Serializable] internal class Configuration : IPluginConfiguration { @@ -68,6 +89,9 @@ internal class Configuration : IPluginConfiguration public bool OverrideStyle; public string? ChosenStyle; + public ConfigKeyBind? ChatTabForward = null; + public ConfigKeyBind? ChatTabBackward = null; + internal void UpdateFrom(Configuration other, bool backToOriginal) { if (backToOriginal) @@ -123,6 +147,8 @@ internal class Configuration : IPluginConfiguration Tabs = other.Tabs.Select(t => t.Clone()).ToList(); OverrideStyle = other.OverrideStyle; ChosenStyle = other.ChosenStyle; + ChatTabForward = other.ChatTabForward; + ChatTabBackward = other.ChatTabBackward; } } diff --git a/ChatTwo/GameFunctions/Chat.cs b/ChatTwo/GameFunctions/Chat.cs index 902fd99..d36b307 100755 --- a/ChatTwo/GameFunctions/Chat.cs +++ b/ChatTwo/GameFunctions/Chat.cs @@ -266,6 +266,36 @@ internal sealed unsafe class Chat : IDisposable modifierState |= modifier; } + bool KeyPressed(VirtualKey key, ModifierFlag modifier) + { + if (!Plugin.KeyState.IsVirtualKeyValid(key)) + return false; + + var modifierPressed = Plugin.Config.KeybindMode switch + { + KeybindMode.Strict => modifier == modifierState, + KeybindMode.Flexible => modifierState.HasFlag(modifier), + _ => false, + }; + + return modifierPressed && Plugin.KeyState[key]; + } + + // Test for custom keybinds for changing chat tabs before checking + // vanilla keybinds. + if (Plugin.Config.ChatTabBackward != null && KeyPressed(Plugin.Config.ChatTabBackward.Key, Plugin.Config.ChatTabBackward.Modifier)) + { + Plugin.KeyState[Plugin.Config.ChatTabBackward.Key] = false; + Plugin.ChatLogWindow.ChangeTabDelta(-1); + return; + } + if (Plugin.Config.ChatTabForward != null && KeyPressed(Plugin.Config.ChatTabForward.Key, Plugin.Config.ChatTabForward.Modifier)) + { + Plugin.KeyState[Plugin.Config.ChatTabForward.Key] = false; + Plugin.ChatLogWindow.ChangeTabDelta(1); + return; + } + var turnedOff = new Dictionary(); foreach (var toIntercept in KeybindsToIntercept.Keys) { @@ -281,20 +311,7 @@ internal sealed unsafe class Chat : IDisposable void Intercept(VirtualKey key, ModifierFlag modifier) { - if (!Plugin.KeyState.IsVirtualKeyValid(key)) - return; - - var modifierPressed = Plugin.Config.KeybindMode switch - { - KeybindMode.Strict => modifier == modifierState, - KeybindMode.Flexible => modifierState.HasFlag(modifier), - _ => false, - }; - - if (!modifierPressed) - return; - - if (!Plugin.KeyState[key]) + if (!KeyPressed(key, modifier)) return; var bits = BitOperations.PopCount((uint) modifier); diff --git a/ChatTwo/GameFunctions/Types/ModifierFlag.cs b/ChatTwo/GameFunctions/Types/ModifierFlag.cs index 4fc9382..395055e 100755 --- a/ChatTwo/GameFunctions/Types/ModifierFlag.cs +++ b/ChatTwo/GameFunctions/Types/ModifierFlag.cs @@ -3,6 +3,7 @@ namespace ChatTwo.GameFunctions.Types; [Flags] internal enum ModifierFlag { + None = 0, Shift = 1 << 0, Ctrl = 1 << 1, Alt = 1 << 2, diff --git a/ChatTwo/Resources/Language.Designer.cs b/ChatTwo/Resources/Language.Designer.cs index 606f01a..def1633 100755 --- a/ChatTwo/Resources/Language.Designer.cs +++ b/ChatTwo/Resources/Language.Designer.cs @@ -1715,6 +1715,51 @@ namespace ChatTwo.Resources { } } + /// + /// Looks up a localized string similar to ESC to clear. + /// + internal static string Keybind_EscToClear { + get { + return ResourceManager.GetString("Keybind_EscToClear", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Alt. + /// + internal static string Keybind_Modifier_Alt { + get { + return ResourceManager.GetString("Keybind_Modifier_Alt", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Ctrl. + /// + internal static string Keybind_Modifier_Ctrl { + get { + return ResourceManager.GetString("Keybind_Modifier_Ctrl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shift. + /// + internal static string Keybind_Modifier_Shift { + get { + return ResourceManager.GetString("Keybind_Modifier_Shift", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to none set. + /// + internal static string Keybind_None { + get { + return ResourceManager.GetString("Keybind_None", resourceCulture); + } + } + /// /// Looks up a localized string similar to Flexible. /// @@ -1940,6 +1985,24 @@ namespace ChatTwo.Resources { } } + /// + /// Looks up a localized string similar to Cycle chat tab backwards keybind. + /// + internal static string Options_ChatTabBackwardKeybind_Name { + get { + return ResourceManager.GetString("Options_ChatTabBackwardKeybind_Name", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cycle chat tab forwards keybind. + /// + internal static string Options_ChatTabForwardKeybind_Name { + get { + return ResourceManager.GetString("Options_ChatTabForwardKeybind_Name", resourceCulture); + } + } + /// /// Looks up a localized string similar to Clear the message history database. /// diff --git a/ChatTwo/Resources/Language.resx b/ChatTwo/Resources/Language.resx index ec6eb53..9018653 100644 --- a/ChatTwo/Resources/Language.resx +++ b/ChatTwo/Resources/Language.resx @@ -514,6 +514,27 @@ Styles + + Cycle chat tab forwards keybind + + + Cycle chat tab backwards keybind + + + none set + + + ESC to clear + + + Ctrl + + + Alt + + + Shift + Ctrl + {0} diff --git a/ChatTwo/Ui/ChatLogWindow.cs b/ChatTwo/Ui/ChatLogWindow.cs index 86991d2..c0d925f 100644 --- a/ChatTwo/Ui/ChatLogWindow.cs +++ b/ChatTwo/Ui/ChatLogWindow.cs @@ -41,6 +41,7 @@ public sealed class ChatLogWindow : Window internal Vector4 DefaultText { get; set; } + internal int? WantedTab { get; set; } internal Tab? CurrentTab { get @@ -348,6 +349,34 @@ public sealed class ChatLogWindow : Window if (ImGui.GetIO().KeyShift) modifierState |= ModifierFlag.Shift; + bool KeyPressed(VirtualKey vk, ModifierFlag modifier) + { + if (!vk.TryToImGui(out var key)) + return false; + + var modifierPressed = Plugin.Config.KeybindMode switch + { + KeybindMode.Strict => modifier == modifierState, + KeybindMode.Flexible => modifierState.HasFlag(modifier), + _ => false, + }; + + return ImGui.IsKeyPressed(key) && modifierPressed && (modifier != 0 || !modifiersOnly); + } + + // Test for custom keybinds for changing chat tabs before checking + // vanilla keybinds. + if (Plugin.Config.ChatTabBackward != null && KeyPressed(Plugin.Config.ChatTabBackward.Key, Plugin.Config.ChatTabBackward.Modifier)) + { + Plugin.ChatLogWindow.ChangeTabDelta(-1); + return; + } + if (Plugin.Config.ChatTabForward != null && KeyPressed(Plugin.Config.ChatTabForward.Key, Plugin.Config.ChatTabForward.Modifier)) + { + Plugin.ChatLogWindow.ChangeTabDelta(1); + return; + } + var turnedOff = new Dictionary(); foreach (var (toIntercept, keybind) in Plugin.Functions.Chat.Keybinds) { @@ -356,17 +385,7 @@ public sealed class ChatLogWindow : Window void Intercept(VirtualKey vk, ModifierFlag modifier) { - if (!vk.TryToImGui(out var key)) - return; - - var modifierPressed = Plugin.Config.KeybindMode switch - { - KeybindMode.Strict => modifier == modifierState, - KeybindMode.Flexible => modifierState.HasFlag(modifier), - _ => false, - }; - - if (!ImGui.IsKeyPressed(key) || !modifierPressed || modifier == 0 && modifiersOnly) + if (!KeyPressed(vk, modifier)) return; var bits = BitOperations.PopCount((uint) modifier); @@ -395,6 +414,16 @@ public sealed class ChatLogWindow : Window } } + internal void ChangeTab(int index) => WantedTab = index; + + internal void ChangeTabDelta(int offset) + { + var newIndex = (LastTab + offset) % Plugin.Config.Tabs.Count; + while (newIndex < 0) + newIndex += Plugin.Config.Tabs.Count; + ChangeTab(newIndex); + } + private void TabChannelSwitch(Tab tab) { // Save the previous channel to restore it later @@ -1158,7 +1187,10 @@ public sealed class ChatLogWindow : Window continue; var unread = tabI == LastTab || tab.UnreadMode == UnreadMode.None || tab.Unread == 0 ? "" : $" ({tab.Unread})"; - using var tabItem = ImRaii.TabItem($"{tab.Name}{unread}###log-tab-{tabI}"); + var flags = ImGuiTabItemFlags.None; + if (WantedTab == tabI) + flags |= ImGuiTabItemFlags.SetSelected; + using var tabItem = ImRaii.TabItem($"{tab.Name}{unread}###log-tab-{tabI}", flags); DrawTabContextMenu(tab, tabI); if (!tabItem.Success) @@ -1174,6 +1206,7 @@ public sealed class ChatLogWindow : Window DrawMessageLog(tab, PayloadHandler, GetRemainingHeightForMessageLog(), switchedTab); } + WantedTab = null; return currentTab; } @@ -1203,10 +1236,10 @@ public sealed class ChatLogWindow : Window continue; var unread = tabI == LastTab || tab.UnreadMode == UnreadMode.None || tab.Unread == 0 ? "" : $" ({tab.Unread})"; - var clicked = ImGui.Selectable($"{tab.Name}{unread}###log-tab-{tabI}", LastTab == tabI); + var clicked = ImGui.Selectable($"{tab.Name}{unread}###log-tab-{tabI}", LastTab == tabI || WantedTab == tabI); DrawTabContextMenu(tab, tabI); - if (!clicked) + if (!clicked && WantedTab != tabI) continue; currentTab = tabI; @@ -1229,6 +1262,7 @@ public sealed class ChatLogWindow : Window if (currentTab > -1) DrawMessageLog(Plugin.Config.Tabs[currentTab], PayloadHandler, childHeight, switchedTab); + WantedTab = null; return currentTab; } diff --git a/ChatTwo/Ui/SettingsTabs/ChatLog.cs b/ChatTwo/Ui/SettingsTabs/ChatLog.cs index 2948a5b..4f4a305 100644 --- a/ChatTwo/Ui/SettingsTabs/ChatLog.cs +++ b/ChatTwo/Ui/SettingsTabs/ChatLog.cs @@ -72,6 +72,18 @@ internal sealed class ChatLog : ISettingsTab ImGui.Separator(); ImGui.Spacing(); + ImGui.TextUnformatted(Language.Options_ChatTabForwardKeybind_Name); + ImGui.SetNextItemWidth(-1); + ImGuiUtil.KeybindInput("ChatTabForwardKeybind", ref Mutable.ChatTabForward); + + ImGui.TextUnformatted(Language.Options_ChatTabBackwardKeybind_Name); + ImGui.SetNextItemWidth(-1); + ImGuiUtil.KeybindInput("ChatTabBackwardKeybind", ref Mutable.ChatTabBackward); + + ImGui.Spacing(); + ImGui.Separator(); + ImGui.Spacing(); + ImGui.TextUnformatted(Language.Options_AdjustPosition_Name); var pos = Plugin.ChatLogWindow.LastWindowPos; ImGui.SetNextItemWidth(-1); diff --git a/ChatTwo/Util/ImGuiUtil.cs b/ChatTwo/Util/ImGuiUtil.cs index 83aab61..4937854 100755 --- a/ChatTwo/Util/ImGuiUtil.cs +++ b/ChatTwo/Util/ImGuiUtil.cs @@ -1,5 +1,7 @@ using System.Numerics; using System.Text; +using ChatTwo.GameFunctions.Types; +using ChatTwo.Resources; using Dalamud.Game.ClientState.Keys; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Interface; @@ -304,6 +306,74 @@ internal static class ImGuiUtil } } + internal static bool KeybindInput(string id, ref ConfigKeyBind? keybind) + { + var idUint = ImGui.GetID(id); + using var pushedId = ImRaii.PushId(id); + if (ImGui.GetStateStorage().GetBool(idUint)) + { + var io = ImGui.GetIO(); + var currentMods = ModifierFlag.None; + var modString = ""; + if (io.KeyCtrl) + { + currentMods |= ModifierFlag.Ctrl; + modString += Language.Keybind_Modifier_Ctrl + " + "; + } + if (io.KeyShift) + { + currentMods |= ModifierFlag.Shift; + modString += Language.Keybind_Modifier_Shift + " + "; + } + if (io.KeyAlt) + { + currentMods |= ModifierFlag.Alt; + modString += Language.Keybind_Modifier_Alt + " + "; + } + + var text = $"{modString}... ({Language.Keybind_EscToClear})"; + using (ImRaii.PushColor(ImGuiCol.TextSelectedBg, Vector4.Zero)) + { + ImGui.SetKeyboardFocusHere(); + ImGui.InputText(id + "##keybind", ref text, 0, ImGuiInputTextFlags.ReadOnly); + } + + if (ImGui.IsKeyPressed(ImGuiKey.Escape)) + { + keybind = null; + ImGui.GetStateStorage().SetBool(idUint, false); + return false; + } + + foreach (var vk in Enum.GetValues(typeof(VirtualKey)).Cast()) + { + if (vk is VirtualKey.NO_KEY or VirtualKey.CONTROL or VirtualKey.LCONTROL or VirtualKey.RCONTROL or VirtualKey.SHIFT or VirtualKey.LSHIFT or VirtualKey.RSHIFT or VirtualKey.MENU or VirtualKey.LMENU or VirtualKey.RMENU) + continue; + + if (!TryToImGui(vk, out var imKey) || !ImGui.IsKeyPressed(imKey)) + continue; + + keybind = new ConfigKeyBind + { + Modifier = currentMods, + Key = vk + }; + ImGui.GetStateStorage().SetBool(idUint, false); + return true; + } + } + else + { + var text = $"({Language.Keybind_None})"; + if (keybind != null) + text = keybind.ToString(); + if (ImGui.Button(text, new Vector2(-1, 0))) + ImGui.GetStateStorage().SetBool(idUint, true); + } + + return false; + } + public static void DrawArrows(ref int selected, int min, int max, float spacing, int id = 0) { // Prevents changing values from triggering EndDisable