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 System.Collections;
using ChatTwo.Code; using ChatTwo.Code;
using ChatTwo.GameFunctions.Types;
using ChatTwo.Resources; using ChatTwo.Resources;
using ChatTwo.Ui; using ChatTwo.Ui;
using Dalamud.Configuration; using Dalamud.Configuration;
using Dalamud.Game.ClientState.Keys;
using ImGuiNET; using ImGuiNET;
namespace ChatTwo; 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] [Serializable]
internal class Configuration : IPluginConfiguration internal class Configuration : IPluginConfiguration
{ {
@@ -68,6 +89,9 @@ internal class Configuration : IPluginConfiguration
public bool OverrideStyle; public bool OverrideStyle;
public string? ChosenStyle; public string? ChosenStyle;
public ConfigKeyBind? ChatTabForward = null;
public ConfigKeyBind? ChatTabBackward = null;
internal void UpdateFrom(Configuration other, bool backToOriginal) internal void UpdateFrom(Configuration other, bool backToOriginal)
{ {
if (backToOriginal) if (backToOriginal)
@@ -123,6 +147,8 @@ internal class Configuration : IPluginConfiguration
Tabs = other.Tabs.Select(t => t.Clone()).ToList(); Tabs = other.Tabs.Select(t => t.Clone()).ToList();
OverrideStyle = other.OverrideStyle; OverrideStyle = other.OverrideStyle;
ChosenStyle = other.ChosenStyle; 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; 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)>(); var turnedOff = new Dictionary<VirtualKey, (uint, string)>();
foreach (var toIntercept in KeybindsToIntercept.Keys) foreach (var toIntercept in KeybindsToIntercept.Keys)
{ {
@@ -281,20 +311,7 @@ internal sealed unsafe class Chat : IDisposable
void Intercept(VirtualKey key, ModifierFlag modifier) void Intercept(VirtualKey key, ModifierFlag modifier)
{ {
if (!Plugin.KeyState.IsVirtualKeyValid(key)) if (!KeyPressed(key, modifier))
return;
var modifierPressed = Plugin.Config.KeybindMode switch
{
KeybindMode.Strict => modifier == modifierState,
KeybindMode.Flexible => modifierState.HasFlag(modifier),
_ => false,
};
if (!modifierPressed)
return;
if (!Plugin.KeyState[key])
return; return;
var bits = BitOperations.PopCount((uint) modifier); var bits = BitOperations.PopCount((uint) modifier);
@@ -3,6 +3,7 @@ namespace ChatTwo.GameFunctions.Types;
[Flags] [Flags]
internal enum ModifierFlag internal enum ModifierFlag
{ {
None = 0,
Shift = 1 << 0, Shift = 1 << 0,
Ctrl = 1 << 1, Ctrl = 1 << 1,
Alt = 1 << 2, 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> /// <summary>
/// Looks up a localized string similar to Flexible. /// Looks up a localized string similar to Flexible.
/// </summary> /// </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> /// <summary>
/// Looks up a localized string similar to Clear the message history database. /// Looks up a localized string similar to Clear the message history database.
/// </summary> /// </summary>
+21
View File
@@ -514,6 +514,27 @@
<data name="Options_OverrideStyleDropdown_Name"> <data name="Options_OverrideStyleDropdown_Name">
<value>Styles</value> <value>Styles</value>
</data> </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"> <data name="AutoTranslate_Completion_Key">
<value>Ctrl + {0}</value> <value>Ctrl + {0}</value>
</data> </data>
+48 -14
View File
@@ -41,6 +41,7 @@ public sealed class ChatLogWindow : Window
internal Vector4 DefaultText { get; set; } internal Vector4 DefaultText { get; set; }
internal int? WantedTab { get; set; }
internal Tab? CurrentTab internal Tab? CurrentTab
{ {
get get
@@ -348,6 +349,34 @@ public sealed class ChatLogWindow : Window
if (ImGui.GetIO().KeyShift) if (ImGui.GetIO().KeyShift)
modifierState |= ModifierFlag.Shift; 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<VirtualKey, (uint, string)>(); var turnedOff = new Dictionary<VirtualKey, (uint, string)>();
foreach (var (toIntercept, keybind) in Plugin.Functions.Chat.Keybinds) foreach (var (toIntercept, keybind) in Plugin.Functions.Chat.Keybinds)
{ {
@@ -356,17 +385,7 @@ public sealed class ChatLogWindow : Window
void Intercept(VirtualKey vk, ModifierFlag modifier) void Intercept(VirtualKey vk, ModifierFlag modifier)
{ {
if (!vk.TryToImGui(out var key)) if (!KeyPressed(vk, modifier))
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)
return; return;
var bits = BitOperations.PopCount((uint) modifier); 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) private void TabChannelSwitch(Tab tab)
{ {
// Save the previous channel to restore it later // Save the previous channel to restore it later
@@ -1158,7 +1187,10 @@ public sealed class ChatLogWindow : Window
continue; continue;
var unread = tabI == LastTab || tab.UnreadMode == UnreadMode.None || tab.Unread == 0 ? "" : $" ({tab.Unread})"; 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); DrawTabContextMenu(tab, tabI);
if (!tabItem.Success) if (!tabItem.Success)
@@ -1174,6 +1206,7 @@ public sealed class ChatLogWindow : Window
DrawMessageLog(tab, PayloadHandler, GetRemainingHeightForMessageLog(), switchedTab); DrawMessageLog(tab, PayloadHandler, GetRemainingHeightForMessageLog(), switchedTab);
} }
WantedTab = null;
return currentTab; return currentTab;
} }
@@ -1203,10 +1236,10 @@ public sealed class ChatLogWindow : Window
continue; continue;
var unread = tabI == LastTab || tab.UnreadMode == UnreadMode.None || tab.Unread == 0 ? "" : $" ({tab.Unread})"; 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); DrawTabContextMenu(tab, tabI);
if (!clicked) if (!clicked && WantedTab != tabI)
continue; continue;
currentTab = tabI; currentTab = tabI;
@@ -1229,6 +1262,7 @@ public sealed class ChatLogWindow : Window
if (currentTab > -1) if (currentTab > -1)
DrawMessageLog(Plugin.Config.Tabs[currentTab], PayloadHandler, childHeight, switchedTab); DrawMessageLog(Plugin.Config.Tabs[currentTab], PayloadHandler, childHeight, switchedTab);
WantedTab = null;
return currentTab; return currentTab;
} }
+12
View File
@@ -72,6 +72,18 @@ internal sealed class ChatLog : ISettingsTab
ImGui.Separator(); ImGui.Separator();
ImGui.Spacing(); 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); ImGui.TextUnformatted(Language.Options_AdjustPosition_Name);
var pos = Plugin.ChatLogWindow.LastWindowPos; var pos = Plugin.ChatLogWindow.LastWindowPos;
ImGui.SetNextItemWidth(-1); ImGui.SetNextItemWidth(-1);
+70
View File
@@ -1,5 +1,7 @@
using System.Numerics; using System.Numerics;
using System.Text; using System.Text;
using ChatTwo.GameFunctions.Types;
using ChatTwo.Resources;
using Dalamud.Game.ClientState.Keys; using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Interface; 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) public static void DrawArrows(ref int selected, int min, int max, float spacing, int id = 0)
{ {
// Prevents changing values from triggering EndDisable // Prevents changing values from triggering EndDisable