feat: Controller hotbars with cross layout, separate storage, and sync with game

- Add controller hotbars: 8 cross bars (L2/R2 style), separate from normal hotbars 1-8
- Controller bar slot data stored in config (not game StandardHotbars) so layouts can differ per mode
- Drag-and-drop on controller bars: from game, shift+drag rearrange, release outside to clear
- Independent controller bar keybinds with modifier+trigger combinations (e.g. L2+South)
- Optional 'Sync bar mode with game client': follow Character Config Mouse/Gamepad toggle (PadMode)
- Clone/copy actions: normal hotbars ↔ controller bars
- Restore controller bar layout button; deploy to devPlugins on Release build

Made-with: Cursor
This commit is contained in:
Jorg
2026-02-26 22:18:40 -06:00
parent 369a770162
commit f3e10f27d2
13 changed files with 1706 additions and 21 deletions
@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Bindings.ImGui;
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Helpers;
namespace HSUI.Interface.GeneralElements
{
/// <summary>Stores custom keybinds for controller cross bars (8 bars × 8 slots). Format: "Key:VK_X" or "Pad:South", empty = unset.</summary>
[Exportable(false)]
[Section("Controller Hotbars", true)]
[SubSection("Keybinds", 0)]
public class ControllerBarKeybindsConfig : PluginConfigObject
{
public const int Bars = 8;
public const int SlotsPerBar = 8;
public const int TotalSlots = Bars * SlotsPerBar;
/// <summary>Index = (bar-1)*8 + slot. Value = "Key:VK_1" or "Pad:South" or "" for unset.</summary>
public List<string> KeybindStrings { get; set; } = new List<string>();
[ManualDraw]
public bool DrawOpenKeybindsButton(ref bool changed)
{
if (ImGui.Button("Setup controller bar keybinds", new Vector2(260, 28)))
{
ConfigurationManager.Instance?.OpenControllerBarKeybindsWindow();
return false;
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Open the keybind setup window to assign keyboard or gamepad buttons to each slot on the controller cross bars. These keybinds are independent of the game's keybind settings.");
return false;
}
public new static ControllerBarKeybindsConfig DefaultConfig() => new ControllerBarKeybindsConfig();
/// <summary>Get keybind for bar 1-8, slot 0-7. Returns "" if unset.</summary>
public string GetKeybind(int bar, int slot)
{
int idx = (bar - 1) * SlotsPerBar + slot;
if (idx < 0 || idx >= TotalSlots) return "";
EnsureCapacity();
return KeybindStrings.ElementAtOrDefault(idx) ?? "";
}
/// <summary>Set keybind for bar 1-8, slot 0-7. Value format: "Key:VK_X" or "Pad:South", or "" to clear.</summary>
public void SetKeybind(int bar, int slot, string value)
{
int idx = (bar - 1) * SlotsPerBar + slot;
if (idx < 0 || idx >= TotalSlots) return;
EnsureCapacity();
KeybindStrings[idx] = value ?? "";
}
private void EnsureCapacity()
{
while (KeybindStrings.Count < TotalSlots)
KeybindStrings.Add("");
}
}
}