diff --git a/Dalamud/Configuration/DalamudConfiguration.cs b/Dalamud/Configuration/DalamudConfiguration.cs
index dc9ea65ed..46ef9f208 100644
--- a/Dalamud/Configuration/DalamudConfiguration.cs
+++ b/Dalamud/Configuration/DalamudConfiguration.cs
@@ -122,6 +122,11 @@ namespace Dalamud.Configuration
///
public bool IsDisableViewport { get; set; } = true;
+ ///
+ /// Gets or sets a value indicating whether or not navigation via a gamepad should be globally enabled in ImGui.
+ ///
+ public bool IsGamepadNavigationEnabled { get; set; } = true;
+
///
/// Load a configuration from the provided path.
///
diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs
index 69b5661c9..f5215cb1c 100644
--- a/Dalamud/Game/ClientState/ClientState.cs
+++ b/Dalamud/Game/ClientState/ClientState.cs
@@ -98,6 +98,9 @@ namespace Dalamud.Game.ClientState
///
public KeyState KeyState;
+ ///
+ /// Provides access to the button state of gamepad buttons in game.
+ ///
public GamepadState GamepadState;
///
@@ -133,7 +136,7 @@ namespace Dalamud.Game.ClientState
this.KeyState = new KeyState(Address, scanner.Module.BaseAddress);
- this.GamepadState = new GamepadState(scanner);
+ this.GamepadState = new GamepadState(this.Address);
this.Condition = new Condition( Address );
diff --git a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs
index adede5248..94591bf4e 100644
--- a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs
+++ b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs
@@ -16,7 +16,14 @@ namespace Dalamud.Game.ClientState
public IntPtr SetupTerritoryType { get; private set; }
//public IntPtr SomeActorTableAccess { get; private set; }
//public IntPtr PartyListUpdate { get; private set; }
-
+
+ ///
+ /// Game function which polls the gamepads for data.
+ ///
+ /// Called every frame, even when `Enable Gamepad` is off in the settings.
+ ///
+ public IntPtr GamepadPoll { get; private set; }
+
public IntPtr ConditionFlags { get; private set; }
protected override void Setup64Bit(SigScanner sig) {
@@ -38,6 +45,8 @@ namespace Dalamud.Game.ClientState
ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? BA ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01 48 83 C4 30");
TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB", 3);
+
+ this.GamepadPoll = sig.ScanText("40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B");
}
}
}
diff --git a/Dalamud/Game/ClientState/GamepadButtons.cs b/Dalamud/Game/ClientState/GamepadButtons.cs
index a3b91711c..e1db685e0 100644
--- a/Dalamud/Game/ClientState/GamepadButtons.cs
+++ b/Dalamud/Game/ClientState/GamepadButtons.cs
@@ -1,24 +1,88 @@
namespace Dalamud.Game.ClientState
{
+ ///
+ /// Bitmask of the Button ushort used by the game.
+ ///
public enum GamepadButtons : ushort
{
- XINPUT_GAMEPAD_DPAD_UP = 0x0001,
- XINPUT_GAMEPAD_DPAD_DOWN = 0x0002,
- XINPUT_GAMEPAD_DPAD_LEFT = 0x0004,
- XINPUT_GAMEPAD_DPAD_RIGHT = 0x0008,
- XINPUT_GAMEPAD_Y = 0x0010,
- XINPUT_GAMEPAD_A = 0x0020,
- XINPUT_GAMEPAD_X = 0x0040,
- XINPUT_GAMEPAD_B = 0x0080,
- XINPUT_GAMEPAD_LEFT_1 = 0x0100,
- XINPUT_GAMEPAD_LEFT_2 = 0x0200, // The back one
- XINPUT_GAMEPAD_LEFT_3 = 0x0400,
- XINPUT_GAMEPAD_RIGHT_1 = 0x0800,
- XINPUT_GAMEPAD_RIGHT_2 = 0x1000, // The back one
- XINPUT_GAMEPAD_RIGHT_3 = 0x2000,
- XINPUT_GAMEPAD_START = 0x8000,
- XINPUT_GAMEPAD_SELECT = 0x4000,
+ ///
+ /// Digipad up.
+ ///
+ DpadUp = 0x0001,
+ ///
+ /// Digipad down.
+ ///
+ DpadDown = 0x0002,
+ ///
+ /// Digipad left.
+ ///
+ DpadLeft = 0x0004,
+
+ ///
+ /// Digipad right.
+ ///
+ DpadRight = 0x0008,
+
+ ///
+ /// North action button. Triangle on PS, Y on Xbox.
+ ///
+ North = 0x0010,
+
+ ///
+ /// South action button. Cross on PS, A on Xbox.
+ ///
+ South = 0x0020,
+
+ ///
+ /// West action button. Square on PS, X on Xbos.
+ ///
+ West = 0x0040,
+
+ ///
+ /// East action button. Circle on PS, B on Xbox.
+ ///
+ East = 0x0080,
+
+ ///
+ /// First button on left shoulder side.
+ ///
+ L1 = 0x0100,
+
+ ///
+ /// Second button on left shoulder side. Analog input lost in this bitmask.
+ ///
+ L2 = 0x0200,
+
+ ///
+ /// Press on left analogue stick.
+ ///
+ L3 = 0x0400,
+
+ ///
+ /// First button on right shoulder.
+ ///
+ R1 = 0x0800,
+
+ ///
+ /// Second button on right shoulder. Analog input lost in this bitmask.
+ ///
+ R2 = 0x1000,
+
+ ///
+ /// Press on right analogue stick.
+ ///
+ R3 = 0x2000,
+
+ ///
+ /// Button on the right inner side of the controller. Options on PS, Start on Xbox.
+ ///
+ Start = 0x8000,
+
+ ///
+ /// Button on the left inner side of the controller. ??? on PS, Back on Xbox.
+ ///
+ Select = 0x4000,
}
}
diff --git a/Dalamud/Game/ClientState/GamepadState.cs b/Dalamud/Game/ClientState/GamepadState.cs
index 50957ef52..0c33f28bf 100644
--- a/Dalamud/Game/ClientState/GamepadState.cs
+++ b/Dalamud/Game/ClientState/GamepadState.cs
@@ -1,150 +1,240 @@
using System;
-using System.Windows.Forms;
+
using Dalamud.Game.ClientState.Structs;
using Dalamud.Hooking;
-using OpenGL;
+using ImGuiNET;
namespace Dalamud.Game.ClientState
{
+ ///
+ /// Exposes the game gamepad state to dalamud.
+ ///
+ /// Will block game's gamepad input if is set.
+ ///
public unsafe class GamepadState
{
+ private readonly Hook gamepadPoll;
- public float ButtonSouth => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_A) > 0 ? 1 : 0;
- public float ButtonEast => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_B) > 0 ? 1 : 0;
- public float ButtonWest => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_X) > 0 ? 1 : 0;
- public float ButtonNorth => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_Y) > 0 ? 1 : 0;
- public float DPadDown => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_DPAD_DOWN) > 0 ? 1 : 0;
- public float DPadRight => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_DPAD_RIGHT) > 0 ? 1 : 0;
- public float DPadUp => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_DPAD_UP) > 0 ? 1 : 0;
- public float DPadLeft => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_DPAD_LEFT) > 0 ? 1 : 0;
- public float L1 => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_LEFT_1) > 0 ? 1 : 0;
- public float L2 => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_LEFT_2) > 0 ? 1 : 0;
- public float L3 => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_LEFT_3) > 0 ? 1 : 0;
- public float R1 => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_RIGHT_1) > 0 ? 1 : 0;
- public float R2 => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_RIGHT_2) > 0 ? 1 : 0;
- public float R3 => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_RIGHT_3) > 0 ? 1 : 0;
- public float Start => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_START) > 0 ? 1 : 0;
- public float Select => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_SELECT) > 0 ? 1 : 0;
- public float LeftStickLeft => this.leftStickX < 0 ? -this.leftStickX / 100f : 0;
- public float LeftStickRight => this.leftStickX > 0 ? this.leftStickX / 100f : 0;
- public float LeftStickUp => this.leftStickY > 0 ? this.leftStickY / 100f : 0;
- public float LeftStickDown => this.leftStickY < 0 ? -this.leftStickY / 100f : 0;
- public float RightStickLeft => this.rightStickX < 0 ? -this.rightStickX / 100f : 0;
- public float RightStickRight => this.rightStickX > 0 ? this.rightStickX / 100f : 0;
- public float RightStickUp => this.rightStickY > 0 ? this.rightStickY / 100f : 0;
- public float RightStickDown => this.rightStickY < 0 ? -this.rightStickY / 100f : 0;
-
- public float Tapped(GamepadButtons button)
- => (this.buttonsTapped & (ushort)button) > 0 ? 1 : 0;
-
- public float Holding(GamepadButtons button)
- => (this.buttonsHolding & (ushort)button) > 0 ? 1 : 0;
-
- public float Released(GamepadButtons button)
- => (this.buttonsReleased & (ushort)button) > 0 ? 1 : 0;
-
- private delegate int ControllerPoll(IntPtr controllerInput);
-
- private Hook controllerPoll;
- //private GamepadInput* _gamePadInput;
private bool isDisposed;
private int leftStickX;
private int leftStickY;
private int rightStickX;
private int rightStickY;
- private ushort buttons;
- private ushort buttonsTapped;
- private ushort buttonsReleased;
- private ushort buttonsHolding;
- private bool imGuiMode;
-
- public GamepadState(SigScanner scanner)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Resolver knowing the pointer to the GamepadPoll function.
+ public GamepadState(ClientStateAddressResolver resolver)
{
- const string controllerPollSignature =
- "40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B";
- this.controllerPoll = new Hook(
- scanner.ScanText(controllerPollSignature),
- (ControllerPoll) ControllerPollDetour);
+ this.gamepadPoll = new Hook(
+ resolver.GamepadPoll,
+ (ControllerPoll)this.GamepadPollDetour);
}
-
- private unsafe int ControllerPollDetour(IntPtr gamepadInput)
+ ///
+ /// Finalizes an instance of the class.
+ ///
+ ~GamepadState()
{
- //this._gamePadInput =(GamepadInput*) gamepadInput;
- var original = this.controllerPoll.Original(gamepadInput);
+ this.Dispose(false);
+ }
+
+ private delegate int ControllerPoll(IntPtr controllerInput);
+
+#if DEBUG
+ ///
+ /// Gets the pointer to the current instance of the GamepadInput struct.
+ ///
+ public IntPtr GamepadInput { get; private set; }
+#endif
+
+ ///
+ /// Gets the state of the left analogue stick in the left direction between 0 (not tilted) and 1 (max tilt).
+ ///
+ public float LeftStickLeft => this.leftStickX < 0 ? -this.leftStickX / 100f : 0;
+
+ ///
+ /// Gets the state of the left analogue stick in the right direction between 0 (not tilted) and 1 (max tilt).
+ ///
+ public float LeftStickRight => this.leftStickX > 0 ? this.leftStickX / 100f : 0;
+
+ ///
+ /// Gets the state of the left analogue stick in the up direction between 0 (not tilted) and 1 (max tilt).
+ ///
+ public float LeftStickUp => this.leftStickY > 0 ? this.leftStickY / 100f : 0;
+
+ ///
+ /// Gets the state of the left analogue stick in the down direction between 0 (not tilted) and 1 (max tilt).
+ ///
+ public float LeftStickDown => this.leftStickY < 0 ? -this.leftStickY / 100f : 0;
+
+ ///
+ /// Gets the state of the right analogue stick in the left direction between 0 (not tilted) and 1 (max tilt).
+ ///
+ public float RightStickLeft => this.rightStickX < 0 ? -this.rightStickX / 100f : 0;
+
+ ///
+ /// Gets the state of the right analogue stick in the right direction between 0 (not tilted) and 1 (max tilt).
+ ///
+ public float RightStickRight => this.rightStickX > 0 ? this.rightStickX / 100f : 0;
+
+ ///
+ /// Gets the state of the right analogue stick in the up direction between 0 (not tilted) and 1 (max tilt).
+ ///
+ public float RightStickUp => this.rightStickY > 0 ? this.rightStickY / 100f : 0;
+
+ ///
+ /// Gets the state of the right analogue stick in the down direction between 0 (not tilted) and 1 (max tilt).
+ ///
+ public float RightStickDown => this.rightStickY < 0 ? -this.rightStickY / 100f : 0;
+
+ ///
+ /// Gets buttons pressed bitmask, set once when the button is pressed. See for the mapping.
+ ///
+ /// Exposed internally for Debug Data window.
+ ///
+ internal ushort ButtonsPressed { get; private set; }
+
+ ///
+ /// Gets raw button bitmask, set the whole time while a button is held. See for the mapping.
+ ///
+ /// Exposed internally for Debug Data window.
+ ///
+ internal ushort ButtonsRaw { get; private set; }
+
+ ///
+ /// Gets button released bitmask, set once right after the button is not hold anymore. See for the mapping.
+ ///
+ /// Exposed internally for Debug Data window.
+ ///
+ internal ushort ButtonsReleased { get; private set; }
+
+ ///
+ /// Gets button repeat bitmask, emits the held button input in fixed intervals. See for the mapping.
+ ///
+ /// Exposed internally for Debug Data window.
+ ///
+ internal ushort ButtonsRepeat { get; private set; }
+
+ ///
+ /// Gets whether has been pressed.
+ ///
+ /// Only true on first frame of the press.
+ ///
+ /// The button to check for.
+ /// 1 if pressed, 0 otherwise.
+ public float Pressed(GamepadButtons button) => (this.ButtonsPressed & (ushort)button) > 0 ? 1 : 0;
+
+ ///
+ /// Gets whether is being pressed.
+ ///
+ /// True in intervals if button is held down.
+ ///
+ /// The button to check for.
+ /// 1 if still pressed during interval, 0 otherwise or in between intervals.
+ public float Repeat(GamepadButtons button) => (this.ButtonsRepeat & (ushort)button) > 0 ? 1 : 0;
+
+ ///
+ /// Gets whether has been released.
+ ///
+ /// Only true the frame after release.
+ ///
+ /// The button to check for.
+ /// 1 if released, 0 otherwise.
+ public float Released(GamepadButtons button) => (this.ButtonsReleased & (ushort)button) > 0 ? 1 : 0;
+
+ ///
+ /// Gets the raw state of .
+ ///
+ /// Is set the entire time a button is pressed down.
+ ///
+ /// The button to check for.
+ /// 1 the whole time button is pressed, 0 otherwise.
+ public float Raw(GamepadButtons button) => (this.ButtonsRaw & (ushort)button) > 0 ? 1 : 0;
+
+ ///
+ /// Enables the hook of the GamepadPoll function.
+ ///
+ public void Enable()
+ {
+ this.gamepadPoll.Enable();
+ }
+
+ ///
+ /// Disposes this instance, alongside its hooks.
+ ///
+ public void Dispose()
+ {
+ this.Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ private int GamepadPollDetour(IntPtr gamepadInput)
+ {
+#if DEBUG
+ this.GamepadInput = gamepadInput;
+#endif
+ var original = this.gamepadPoll.Original(gamepadInput);
var input = (GamepadInput*)gamepadInput;
- if (
- (input->ButtonFlag_Holding & (ushort)GamepadButtons.XINPUT_GAMEPAD_RIGHT_1) > 0
- && (input->ButtonFlag & (ushort)GamepadButtons.XINPUT_GAMEPAD_LEFT_1) > 0)
- {
- this.imGuiMode = !this.imGuiMode;
- if (!this.imGuiMode)
- {
- this.leftStickX = 0;
- this.leftStickY = 0;
- this.rightStickY = 0;
- this.rightStickX = 0;
- this.buttons = 0;
- this.buttonsTapped = 0;
- this.buttonsReleased = 0;
- this.buttonsHolding = 0;
- }
- }
+ this.leftStickX = input->LeftStickX;
+ this.leftStickY = input->LeftStickY;
+ this.rightStickX = input->RightStickX;
+ this.rightStickY = input->RightStickY;
+ this.ButtonsRaw = input->ButtonsRaw;
+ this.ButtonsPressed = input->ButtonsPressed;
+ this.ButtonsReleased = input->ButtonsReleased;
+ this.ButtonsRepeat = input->ButtonsRepeat;
- if (this.imGuiMode)
+ if ((ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0)
{
- this.leftStickX = input->LeftStickX;
- this.leftStickY = input->LeftStickY;
- this.rightStickX = input->RightStickX;
- this.rightStickY = input->RightStickY;
- this.buttons = input->ButtonFlag;
- this.buttonsTapped = input->ButtonFlag_Tap;
- this.buttonsReleased = input->ButtonFlag_Release;
- this.buttonsHolding = input->ButtonFlag_Holding;
-
input->LeftStickX = 0;
input->LeftStickY = 0;
input->RightStickX = 0;
input->RightStickY = 0;
- input->ButtonFlag = 0;
- input->ButtonFlag_Tap = 0;
- input->ButtonFlag_Release = 0;
- input->ButtonFlag_Holding = 0;
+
+ // NOTE (Chiv) Zeroing `ButtonsRaw` destroys `ButtonPressed`, `ButtonReleased`
+ // and `ButtonRepeat` as the game uses the RAW input to determine those (apparently).
+ // It does block, however, all input to the game.
+ // Leaving `ButtonsRaw` as it is and only zeroing the other leaves e.g. long-hold L2/R2
+ // and the digipad (in some situations, but thankfully not in menus) functional.
+ // We can either:
+ // (a) Explicitly only set L2/R2/Digipad to 0 (and destroy their `ButtonPressed` field) => Needs to be documented, or
+ // (b) ignore it as so far it seems only a 'visual' error
+ // (L2/R2 being held down activates CrossHotBar but activating an ability is impossible because of the others blocked input,
+ // Digipad is ignored in menus but without any menu's one still switches target or party members, but cannot interact with them
+ // because of the other blocked input)
+ // `ButtonPressed` is pretty useful so we opt-in to (b).
+ // This is debatable.
+ // ImGui itself does not care either way as it uses the Raw values and does its own state handling.
+ // input->ButtonsRaw &= (ushort)~GamepadButtons.L2;
+ // input->ButtonsRaw &= (ushort)~GamepadButtons.R2;
+ // input->ButtonsRaw &= (ushort)~GamepadButtons.DpadDown;
+ // input->ButtonsRaw &= (ushort)~GamepadButtons.DpadLeft;
+ // input->ButtonsRaw &= (ushort)~GamepadButtons.DpadUp;
+ // input->ButtonsRaw &= (ushort)~GamepadButtons.DpadRight;
+ input->ButtonsPressed = 0;
+ input->ButtonsReleased = 0;
+ input->ButtonsRepeat = 0;
+ return 0;
}
- // Not so sure about the return value, does not seem to matter if we return the
+ // NOTE (Chiv) Not so sure about the return value, does not seem to matter if we return the
// original, zero or do the work adjusting the bits.
return original;
}
- public void Enable()
- {
- this.controllerPoll.Enable();
- }
-
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
private void Dispose(bool disposing)
{
if (this.isDisposed) return;
if (disposing)
{
- this.controllerPoll?.Disable();
- this.controllerPoll?.Dispose();
+ this.gamepadPoll?.Disable();
+ this.gamepadPoll?.Dispose();
}
this.isDisposed = true;
}
-
- ~GamepadState()
- {
- Dispose(false);
- }
}
}
diff --git a/Dalamud/Game/ClientState/Structs/GamepadInput.cs b/Dalamud/Game/ClientState/Structs/GamepadInput.cs
index f9bdd1c00..ce7440b61 100644
--- a/Dalamud/Game/ClientState/Structs/GamepadInput.cs
+++ b/Dalamud/Game/ClientState/Structs/GamepadInput.cs
@@ -2,37 +2,63 @@
namespace Dalamud.Game.ClientState.Structs
{
- // It is bigger, but I dunno how big in real
+ ///
+ /// Struct which gets populated by polling the gamepads.
+ ///
+ /// Has an array of gamepads, among many other things (here not mapped).
+ /// All we really care about is the final data which the game uses to determine input.
+ ///
+ /// The size is definitely bigger than only the following fields but I do not know how big.
+ ///
[StructLayout(LayoutKind.Explicit)]
public struct GamepadInput
{
- // Each stick is -99 till 99
+ ///
+ /// Left analogue stick's horizontal value, -99 for left, 99 for right.
+ ///
[FieldOffset(0x88)]
public int LeftStickX;
+ ///
+ /// Left analogue stick's vertical value, -99 for down, 99 for up.
+ ///
[FieldOffset(0x8C)]
public int LeftStickY;
+ ///
+ /// Right analogue stick's horizontal value, -99 for left, 99 for right.
+ ///
[FieldOffset(0x90)]
public int RightStickX;
+ ///
+ /// Right analogue stick's vertical value, -99 for down, 99 for up.
+ ///
[FieldOffset(0x94)]
public int RightStickY;
- // Seems to be source of true, instant population, keeps value while hold.
+ ///
+ /// Raw input, set the whole time while a button is held. See for the mapping.
+ ///
[FieldOffset(0x98)]
- public ushort ButtonFlag; // bitfield
+ public ushort ButtonsRaw; // bitfield
- // Gets populated only if released after a short tick
+ ///
+ /// Button pressed, set once when the button is pressed. See for the mapping.
+ ///
[FieldOffset(0x9C)]
- public ushort ButtonFlag_Tap; // bitfield
+ public ushort ButtonsPressed; // bitfield
- // Gets populated on button release
+ ///
+ /// Button released input, set once right after the button is not hold anymore. See for the mapping.
+ ///
[FieldOffset(0xA0)]
- public ushort ButtonFlag_Release; // bitfield
+ public ushort ButtonsReleased; // bitfield
- // Gets populated after a tick and keeps being set while button is held
+ ///
+ /// Repeatedly emits the held button input in fixed intervals. See for the mapping.
+ ///
[FieldOffset(0xA4)]
- public ushort ButtonFlag_Holding; // bitfield
+ public ushort ButtonsRepeat; // bitfield
}
}
diff --git a/Dalamud/Interface/DalamudSettingsWindow.cs b/Dalamud/Interface/DalamudSettingsWindow.cs
index 152e7ff32..c350e9e13 100644
--- a/Dalamud/Interface/DalamudSettingsWindow.cs
+++ b/Dalamud/Interface/DalamudSettingsWindow.cs
@@ -23,7 +23,7 @@ namespace Dalamud.Interface
{
this.dalamud = dalamud;
- this.Size = new Vector2(740, 500);
+ this.Size = new Vector2(740, 525);
this.SizeCondition = ImGuiCond.FirstUseEver;
this.dalamudMessagesChatType = this.dalamud.Configuration.GeneralChatType;
@@ -38,6 +38,7 @@ namespace Dalamud.Interface
this.doDocking = this.dalamud.Configuration.IsDocking;
this.doViewport = !this.dalamud.Configuration.IsDisableViewport;
+ this.doGamepad = this.dalamud.Configuration.IsGamepadNavigationEnabled;
this.doPluginTest = this.dalamud.Configuration.DoPluginTest;
this.thirdRepoList = this.dalamud.Configuration.ThirdRepoList.Select(x => x.Clone()).ToList();
@@ -133,6 +134,7 @@ namespace Dalamud.Interface
private bool doToggleUiHideDuringGpose;
private bool doDocking;
private bool doViewport;
+ private bool doGamepad;
private List thirdRepoList;
private bool printPluginsWelcomeMsg;
@@ -228,6 +230,9 @@ namespace Dalamud.Interface
ImGui.Checkbox(Loc.Localize("DalamudSettingToggleDocking", "Enable window docking"), ref this.doDocking);
ImGui.TextColored(this.hintTextColor, Loc.Localize("DalamudSettingToggleDockingHint", "This will allow you to fuse and tab plugin windows."));
+ ImGui.Checkbox(Loc.Localize("DalamudSettingToggleGamepadNavigation", "Enable navigation of ImGui windows via gamepad."), ref this.doGamepad);
+ ImGui.TextColored(this.hintTextColor, Loc.Localize("DalamudSettingToggleGamepadNavigationHint", "This will allow you to toggle between game and ImGui navigation via L1+L3."));
+
ImGui.EndTabItem();
}
@@ -378,6 +383,7 @@ namespace Dalamud.Interface
this.dalamud.Configuration.ToggleUiHideDuringGpose = this.doToggleUiHideDuringGpose;
this.dalamud.Configuration.IsDocking = this.doDocking;
+ this.dalamud.Configuration.IsGamepadNavigationEnabled = this.doGamepad;
// This is applied every frame in InterfaceManager::CheckViewportState()
this.dalamud.Configuration.IsDisableViewport = !this.doViewport;
@@ -392,6 +398,18 @@ namespace Dalamud.Interface
ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable;
}
+ // NOTE (Chiv) Toggle gamepad navigation via setting
+ if (!this.dalamud.Configuration.IsGamepadNavigationEnabled)
+ {
+ ImGui.GetIO().BackendFlags &= ~ImGuiBackendFlags.HasGamepad;
+ ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableSetMousePos;
+ }
+ else
+ {
+ ImGui.GetIO().BackendFlags |= ImGuiBackendFlags.HasGamepad;
+ ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.NavEnableSetMousePos;
+ }
+
this.dalamud.Configuration.DoPluginTest = this.doPluginTest;
this.dalamud.Configuration.ThirdRepoList = this.thirdRepoList.Select(x => x.Clone()).ToList();
diff --git a/Dalamud/Interface/InterfaceManager.cs b/Dalamud/Interface/InterfaceManager.cs
index 78681cf86..0b67f9a63 100644
--- a/Dalamud/Interface/InterfaceManager.cs
+++ b/Dalamud/Interface/InterfaceManager.cs
@@ -6,6 +6,7 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Dalamud.Game;
+using Dalamud.Game.ClientState;
using Dalamud.Game.Internal.DXGI;
using Dalamud.Hooking;
using EasyHook;
@@ -291,9 +292,18 @@ namespace Dalamud.Interface
ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable;
}
- //TODO (Chiv) Addition
- ImGui.GetIO().BackendFlags |= ImGuiBackendFlags.HasGamepad;
-
+ // NOTE (Chiv) Toggle gamepad navigation via setting
+ if (!this.dalamud.Configuration.IsGamepadNavigationEnabled)
+ {
+ ImGui.GetIO().BackendFlags &= ~ImGuiBackendFlags.HasGamepad;
+ ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableSetMousePos;
+ }
+ else
+ {
+ ImGui.GetIO().BackendFlags |= ImGuiBackendFlags.HasGamepad;
+ ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.NavEnableSetMousePos;
+ }
+
ImGuiHelpers.MainViewport = ImGui.GetMainViewport();
Log.Information("[IM] Scene & ImGui setup OK!");
@@ -463,24 +473,40 @@ namespace Dalamud.Interface
this.dalamud.ClientState.KeyState.ClearAll();
}
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Activate] = this.dalamud.ClientState.GamepadState.ButtonSouth;
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Cancel] = this.dalamud.ClientState.GamepadState.ButtonEast;
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Input] = this.dalamud.ClientState.GamepadState.ButtonNorth;
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Menu] = this.dalamud.ClientState.GamepadState.ButtonWest;
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadLeft] = this.dalamud.ClientState.GamepadState.DPadLeft;
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadRight] = this.dalamud.ClientState.GamepadState.DPadRight;
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadUp] = this.dalamud.ClientState.GamepadState.DPadUp;
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadDown] = this.dalamud.ClientState.GamepadState.DPadDown;
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickLeft] = this.dalamud.ClientState.GamepadState.LeftStickLeft;
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickRight] = this.dalamud.ClientState.GamepadState.LeftStickRight;
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickUp] = this.dalamud.ClientState.GamepadState.LeftStickUp;
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickDown] = this.dalamud.ClientState.GamepadState.LeftStickDown;
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.FocusPrev] = this.dalamud.ClientState.GamepadState.L1;
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.FocusNext] = this.dalamud.ClientState.GamepadState.R1;
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.TweakSlow] = this.dalamud.ClientState.GamepadState.L2;
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.TweakFast] = this.dalamud.ClientState.GamepadState.R2;
-
// TODO: mouse state?
+
+ var gamepadEnabled = (ImGui.GetIO().BackendFlags & ImGuiBackendFlags.HasGamepad) > 0;
+
+ // NOTE (Chiv) Activate ImGui navigation via L1+L3 press
+ // (mimicking to how mouse navigation is activated via L1+R3 press in game).
+ if (gamepadEnabled
+ && this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.L1) > 0
+ && this.dalamud.ClientState.GamepadState.Pressed(GamepadButtons.L3) > 0)
+ {
+ ImGui.GetIO().ConfigFlags ^= ImGuiConfigFlags.NavEnableGamepad;
+ this.dalamud.DalamudUi.TogglePluginInstaller();
+ }
+
+ if (gamepadEnabled
+ && (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0)
+ {
+ ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Activate] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.South);
+ ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Cancel] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.East);
+ ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Input] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.North);
+ ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Menu] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.West);
+ ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadLeft] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.DpadLeft);
+ ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadRight] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.DpadRight);
+ ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadUp] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.DpadUp);
+ ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadDown] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.DpadDown);
+ ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickLeft] = this.dalamud.ClientState.GamepadState.LeftStickLeft;
+ ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickRight] = this.dalamud.ClientState.GamepadState.LeftStickRight;
+ ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickUp] = this.dalamud.ClientState.GamepadState.LeftStickUp;
+ ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickDown] = this.dalamud.ClientState.GamepadState.LeftStickDown;
+ ImGui.GetIO().NavInputs[(int)ImGuiNavInput.FocusPrev] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.L1);
+ ImGui.GetIO().NavInputs[(int)ImGuiNavInput.FocusNext] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.R1);
+ ImGui.GetIO().NavInputs[(int)ImGuiNavInput.TweakSlow] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.L2);
+ ImGui.GetIO().NavInputs[(int)ImGuiNavInput.TweakFast] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.R2);
+ }
}
private void Display()