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
+68
View File
@@ -0,0 +1,68 @@
using System;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.GamePad;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.System.Framework;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using HSUI.Interface.GeneralElements;
namespace HSUI.Helpers
{
/// <summary>Provides current cross hotbar trigger state (L2/R2) and game controller-mode detection for sync with game client.</summary>
public static class ControllerHotbarHelper
{
private static IGamepadState? _gamepadState;
public static void SetGamepadState(IGamepadState? gamepadState)
{
_gamepadState = gamepadState;
}
/// <summary>True when the game's Character Configuration has controller (gamepad) mode enabled. Reads the PadMode config option so bar mode follows the toggle and does not switch on input.</summary>
public static unsafe bool IsGameInControllerMode()
{
try
{
Framework* framework = Framework.Instance();
if (framework == null) return false;
int option = (int)ConfigOption.PadMode;
if (framework->SystemConfig.SystemConfigBase.UiConfig.ConfigCount <= option)
return false;
uint value = framework->SystemConfig.SystemConfigBase.UiConfig.ConfigEntry[option].Value.UInt;
// PadMode: 0 = keyboard/mouse, 1 = gamepad
return value != 0;
}
catch
{
return false;
}
}
/// <summary>Returns the current trigger combo for which cross bar should be visible. Uses L2/R2 from gamepad.</summary>
public static CrossBarTrigger GetCurrentTrigger()
{
if (_gamepadState == null)
return CrossBarTrigger.None;
try
{
// Raw returns float (analog); treat as pressed when > 0.5
bool l2 = _gamepadState.Raw(GamepadButtons.L2) > 0.5f;
bool r2 = _gamepadState.Raw(GamepadButtons.R2) > 0.5f;
if (l2 && r2)
{
// Both held - could distinguish L2 then R2 vs R2 then L2 by order; for now use L2R2
return CrossBarTrigger.L2R2;
}
if (l2) return CrossBarTrigger.L2;
if (r2) return CrossBarTrigger.R2;
return CrossBarTrigger.None;
}
catch
{
return CrossBarTrigger.None;
}
}
}
}