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.
This commit is contained in:
Dean Sheather
2024-07-11 00:32:13 +10:00
parent ac45afcf4c
commit 352088dfed
8 changed files with 272 additions and 28 deletions
+26
View File
@@ -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;
}
}
+31 -14
View File
@@ -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<VirtualKey, (uint, string)>();
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);
@@ -3,6 +3,7 @@ namespace ChatTwo.GameFunctions.Types;
[Flags]
internal enum ModifierFlag
{
None = 0,
Shift = 1 << 0,
Ctrl = 1 << 1,
Alt = 1 << 2,
+63
View File
@@ -1715,6 +1715,51 @@ namespace ChatTwo.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to ESC to clear.
/// </summary>
internal static string Keybind_EscToClear {
get {
return ResourceManager.GetString("Keybind_EscToClear", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Alt.
/// </summary>
internal static string Keybind_Modifier_Alt {
get {
return ResourceManager.GetString("Keybind_Modifier_Alt", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Ctrl.
/// </summary>
internal static string Keybind_Modifier_Ctrl {
get {
return ResourceManager.GetString("Keybind_Modifier_Ctrl", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Shift.
/// </summary>
internal static string Keybind_Modifier_Shift {
get {
return ResourceManager.GetString("Keybind_Modifier_Shift", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to none set.
/// </summary>
internal static string Keybind_None {
get {
return ResourceManager.GetString("Keybind_None", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Flexible.
/// </summary>
@@ -1940,6 +1985,24 @@ namespace ChatTwo.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Cycle chat tab backwards keybind.
/// </summary>
internal static string Options_ChatTabBackwardKeybind_Name {
get {
return ResourceManager.GetString("Options_ChatTabBackwardKeybind_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cycle chat tab forwards keybind.
/// </summary>
internal static string Options_ChatTabForwardKeybind_Name {
get {
return ResourceManager.GetString("Options_ChatTabForwardKeybind_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Clear the message history database.
/// </summary>
+21
View File
@@ -514,6 +514,27 @@
<data name="Options_OverrideStyleDropdown_Name">
<value>Styles</value>
</data>
<data name="Options_ChatTabForwardKeybind_Name">
<value>Cycle chat tab forwards keybind</value>
</data>
<data name="Options_ChatTabBackwardKeybind_Name">
<value>Cycle chat tab backwards keybind</value>
</data>
<data name="Keybind_None">
<value>none set</value>
</data>
<data name="Keybind_EscToClear">
<value>ESC to clear</value>
</data>
<data name="Keybind_Modifier_Ctrl">
<value>Ctrl</value>
</data>
<data name="Keybind_Modifier_Alt">
<value>Alt</value>
</data>
<data name="Keybind_Modifier_Shift">
<value>Shift</value>
</data>
<data name="AutoTranslate_Completion_Key">
<value>Ctrl + {0}</value>
</data>
+46 -12
View File
@@ -41,6 +41,7 @@ public sealed class ChatLogWindow : Window
internal Vector4 DefaultText { get; set; }
internal int? WantedTab { get; set; }
internal Tab? CurrentTab
{
get
@@ -348,16 +349,10 @@ public sealed class ChatLogWindow : Window
if (ImGui.GetIO().KeyShift)
modifierState |= ModifierFlag.Shift;
var turnedOff = new Dictionary<VirtualKey, (uint, string)>();
foreach (var (toIntercept, keybind) in Plugin.Functions.Chat.Keybinds)
{
if (toIntercept is "CMD_CHAT" or "CMD_COMMAND")
continue;
void Intercept(VirtualKey vk, ModifierFlag modifier)
bool KeyPressed(VirtualKey vk, ModifierFlag modifier)
{
if (!vk.TryToImGui(out var key))
return;
return false;
var modifierPressed = Plugin.Config.KeybindMode switch
{
@@ -366,7 +361,31 @@ public sealed class ChatLogWindow : Window
_ => false,
};
if (!ImGui.IsKeyPressed(key) || !modifierPressed || modifier == 0 && modifiersOnly)
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<VirtualKey, (uint, string)>();
foreach (var (toIntercept, keybind) in Plugin.Functions.Chat.Keybinds)
{
if (toIntercept is "CMD_CHAT" or "CMD_COMMAND")
continue;
void Intercept(VirtualKey vk, ModifierFlag modifier)
{
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;
}
+12
View File
@@ -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);
+70
View File
@@ -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<VirtualKey>())
{
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