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()