mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
feat: Add support for gamepad control of ImGui
Alongside a settings option and the ability for plugins to query gamepad state. PLugins cannot, however, as of now set an input as 'handled' and block it for the game. Whether game or ImGui is receiving gamepad input can be be checked via `(ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0`
This commit is contained in:
parent
9d38069fb3
commit
76bf0bc52d
8 changed files with 396 additions and 155 deletions
|
|
@ -122,6 +122,11 @@ namespace Dalamud.Configuration
|
|||
/// </summary>
|
||||
public bool IsDisableViewport { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not navigation via a gamepad should be globally enabled in ImGui.
|
||||
/// </summary>
|
||||
public bool IsGamepadNavigationEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Load a configuration from the provided path.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -98,6 +98,9 @@ namespace Dalamud.Game.ClientState
|
|||
/// </summary>
|
||||
public KeyState KeyState;
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to the button state of gamepad buttons in game.
|
||||
/// </summary>
|
||||
public GamepadState GamepadState;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -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 );
|
||||
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Game function which polls the gamepads for data.
|
||||
///
|
||||
/// Called every frame, even when `Enable Gamepad` is off in the settings.
|
||||
/// </summary>
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,88 @@
|
|||
namespace Dalamud.Game.ClientState
|
||||
{
|
||||
/// <summary>
|
||||
/// Bitmask of the Button ushort used by the game.
|
||||
/// </summary>
|
||||
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,
|
||||
/// <summary>
|
||||
/// Digipad up.
|
||||
/// </summary>
|
||||
DpadUp = 0x0001,
|
||||
|
||||
/// <summary>
|
||||
/// Digipad down.
|
||||
/// </summary>
|
||||
DpadDown = 0x0002,
|
||||
|
||||
/// <summary>
|
||||
/// Digipad left.
|
||||
/// </summary>
|
||||
DpadLeft = 0x0004,
|
||||
|
||||
/// <summary>
|
||||
/// Digipad right.
|
||||
/// </summary>
|
||||
DpadRight = 0x0008,
|
||||
|
||||
/// <summary>
|
||||
/// North action button. Triangle on PS, Y on Xbox.
|
||||
/// </summary>
|
||||
North = 0x0010,
|
||||
|
||||
/// <summary>
|
||||
/// South action button. Cross on PS, A on Xbox.
|
||||
/// </summary>
|
||||
South = 0x0020,
|
||||
|
||||
/// <summary>
|
||||
/// West action button. Square on PS, X on Xbos.
|
||||
/// </summary>
|
||||
West = 0x0040,
|
||||
|
||||
/// <summary>
|
||||
/// East action button. Circle on PS, B on Xbox.
|
||||
/// </summary>
|
||||
East = 0x0080,
|
||||
|
||||
/// <summary>
|
||||
/// First button on left shoulder side.
|
||||
/// </summary>
|
||||
L1 = 0x0100,
|
||||
|
||||
/// <summary>
|
||||
/// Second button on left shoulder side. Analog input lost in this bitmask.
|
||||
/// </summary>
|
||||
L2 = 0x0200,
|
||||
|
||||
/// <summary>
|
||||
/// Press on left analogue stick.
|
||||
/// </summary>
|
||||
L3 = 0x0400,
|
||||
|
||||
/// <summary>
|
||||
/// First button on right shoulder.
|
||||
/// </summary>
|
||||
R1 = 0x0800,
|
||||
|
||||
/// <summary>
|
||||
/// Second button on right shoulder. Analog input lost in this bitmask.
|
||||
/// </summary>
|
||||
R2 = 0x1000,
|
||||
|
||||
/// <summary>
|
||||
/// Press on right analogue stick.
|
||||
/// </summary>
|
||||
R3 = 0x2000,
|
||||
|
||||
/// <summary>
|
||||
/// Button on the right inner side of the controller. Options on PS, Start on Xbox.
|
||||
/// </summary>
|
||||
Start = 0x8000,
|
||||
|
||||
/// <summary>
|
||||
/// Button on the left inner side of the controller. ??? on PS, Back on Xbox.
|
||||
/// </summary>
|
||||
Select = 0x4000,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Exposes the game gamepad state to dalamud.
|
||||
///
|
||||
/// Will block game's gamepad input if <see cref="ImGuiConfigFlags.NavEnableGamepad"/> is set.
|
||||
/// </summary>
|
||||
public unsafe class GamepadState
|
||||
{
|
||||
private readonly Hook<ControllerPoll> 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> 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)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GamepadState" /> class.
|
||||
/// </summary>
|
||||
/// <param name="resolver">Resolver knowing the pointer to the GamepadPoll function.</param>
|
||||
public GamepadState(ClientStateAddressResolver resolver)
|
||||
{
|
||||
const string controllerPollSignature =
|
||||
"40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B";
|
||||
this.controllerPoll = new Hook<ControllerPoll>(
|
||||
scanner.ScanText(controllerPollSignature),
|
||||
(ControllerPoll) ControllerPollDetour);
|
||||
this.gamepadPoll = new Hook<ControllerPoll>(
|
||||
resolver.GamepadPoll,
|
||||
(ControllerPoll)this.GamepadPollDetour);
|
||||
}
|
||||
|
||||
|
||||
private unsafe int ControllerPollDetour(IntPtr gamepadInput)
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="GamepadState" /> class.
|
||||
/// </summary>
|
||||
~GamepadState()
|
||||
{
|
||||
//this._gamePadInput =(GamepadInput*) gamepadInput;
|
||||
var original = this.controllerPoll.Original(gamepadInput);
|
||||
this.Dispose(false);
|
||||
}
|
||||
|
||||
private delegate int ControllerPoll(IntPtr controllerInput);
|
||||
|
||||
#if DEBUG
|
||||
/// <summary>
|
||||
/// Gets the pointer to the current instance of the GamepadInput struct.
|
||||
/// </summary>
|
||||
public IntPtr GamepadInput { get; private set; }
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the left analogue stick in the left direction between 0 (not tilted) and 1 (max tilt).
|
||||
/// </summary>
|
||||
public float LeftStickLeft => this.leftStickX < 0 ? -this.leftStickX / 100f : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the left analogue stick in the right direction between 0 (not tilted) and 1 (max tilt).
|
||||
/// </summary>
|
||||
public float LeftStickRight => this.leftStickX > 0 ? this.leftStickX / 100f : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the left analogue stick in the up direction between 0 (not tilted) and 1 (max tilt).
|
||||
/// </summary>
|
||||
public float LeftStickUp => this.leftStickY > 0 ? this.leftStickY / 100f : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the left analogue stick in the down direction between 0 (not tilted) and 1 (max tilt).
|
||||
/// </summary>
|
||||
public float LeftStickDown => this.leftStickY < 0 ? -this.leftStickY / 100f : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the right analogue stick in the left direction between 0 (not tilted) and 1 (max tilt).
|
||||
/// </summary>
|
||||
public float RightStickLeft => this.rightStickX < 0 ? -this.rightStickX / 100f : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the right analogue stick in the right direction between 0 (not tilted) and 1 (max tilt).
|
||||
/// </summary>
|
||||
public float RightStickRight => this.rightStickX > 0 ? this.rightStickX / 100f : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the right analogue stick in the up direction between 0 (not tilted) and 1 (max tilt).
|
||||
/// </summary>
|
||||
public float RightStickUp => this.rightStickY > 0 ? this.rightStickY / 100f : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the right analogue stick in the down direction between 0 (not tilted) and 1 (max tilt).
|
||||
/// </summary>
|
||||
public float RightStickDown => this.rightStickY < 0 ? -this.rightStickY / 100f : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets buttons pressed bitmask, set once when the button is pressed. See <see cref="GamepadButtons"/> for the mapping.
|
||||
///
|
||||
/// Exposed internally for Debug Data window.
|
||||
/// </summary>
|
||||
internal ushort ButtonsPressed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets raw button bitmask, set the whole time while a button is held. See <see cref="GamepadButtons"/> for the mapping.
|
||||
///
|
||||
/// Exposed internally for Debug Data window.
|
||||
/// </summary>
|
||||
internal ushort ButtonsRaw { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets button released bitmask, set once right after the button is not hold anymore. See <see cref="GamepadButtons"/> for the mapping.
|
||||
///
|
||||
/// Exposed internally for Debug Data window.
|
||||
/// </summary>
|
||||
internal ushort ButtonsReleased { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets button repeat bitmask, emits the held button input in fixed intervals. See <see cref="GamepadButtons"/> for the mapping.
|
||||
///
|
||||
/// Exposed internally for Debug Data window.
|
||||
/// </summary>
|
||||
internal ushort ButtonsRepeat { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether <paramref name="button"/> has been pressed.
|
||||
///
|
||||
/// Only true on first frame of the press.
|
||||
/// </summary>
|
||||
/// <param name="button">The button to check for.</param>
|
||||
/// <returns>1 if pressed, 0 otherwise.</returns>
|
||||
public float Pressed(GamepadButtons button) => (this.ButtonsPressed & (ushort)button) > 0 ? 1 : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether <paramref name="button"/> is being pressed.
|
||||
///
|
||||
/// True in intervals if button is held down.
|
||||
/// </summary>
|
||||
/// <param name="button">The button to check for.</param>
|
||||
/// <returns>1 if still pressed during interval, 0 otherwise or in between intervals.</returns>
|
||||
public float Repeat(GamepadButtons button) => (this.ButtonsRepeat & (ushort)button) > 0 ? 1 : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether <paramref name="button"/> has been released.
|
||||
///
|
||||
/// Only true the frame after release.
|
||||
/// </summary>
|
||||
/// <param name="button">The button to check for.</param>
|
||||
/// <returns>1 if released, 0 otherwise.</returns>
|
||||
public float Released(GamepadButtons button) => (this.ButtonsReleased & (ushort)button) > 0 ? 1 : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw state of <paramref name="button"/>.
|
||||
///
|
||||
/// Is set the entire time a button is pressed down.
|
||||
/// </summary>
|
||||
/// <param name="button">The button to check for.</param>
|
||||
/// <returns>1 the whole time button is pressed, 0 otherwise.</returns>
|
||||
public float Raw(GamepadButtons button) => (this.ButtonsRaw & (ushort)button) > 0 ? 1 : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Enables the hook of the GamepadPoll function.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
this.gamepadPoll.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes this instance, alongside its hooks.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,37 +2,63 @@
|
|||
|
||||
namespace Dalamud.Game.ClientState.Structs
|
||||
{
|
||||
// It is bigger, but I dunno how big in real
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct GamepadInput
|
||||
{
|
||||
// Each stick is -99 till 99
|
||||
/// <summary>
|
||||
/// Left analogue stick's horizontal value, -99 for left, 99 for right.
|
||||
/// </summary>
|
||||
[FieldOffset(0x88)]
|
||||
public int LeftStickX;
|
||||
|
||||
/// <summary>
|
||||
/// Left analogue stick's vertical value, -99 for down, 99 for up.
|
||||
/// </summary>
|
||||
[FieldOffset(0x8C)]
|
||||
public int LeftStickY;
|
||||
|
||||
/// <summary>
|
||||
/// Right analogue stick's horizontal value, -99 for left, 99 for right.
|
||||
/// </summary>
|
||||
[FieldOffset(0x90)]
|
||||
public int RightStickX;
|
||||
|
||||
/// <summary>
|
||||
/// Right analogue stick's vertical value, -99 for down, 99 for up.
|
||||
/// </summary>
|
||||
[FieldOffset(0x94)]
|
||||
public int RightStickY;
|
||||
|
||||
// Seems to be source of true, instant population, keeps value while hold.
|
||||
/// <summary>
|
||||
/// Raw input, set the whole time while a button is held. See <see cref="GamepadButtons"/> for the mapping.
|
||||
/// </summary>
|
||||
[FieldOffset(0x98)]
|
||||
public ushort ButtonFlag; // bitfield
|
||||
public ushort ButtonsRaw; // bitfield
|
||||
|
||||
// Gets populated only if released after a short tick
|
||||
/// <summary>
|
||||
/// Button pressed, set once when the button is pressed. See <see cref="GamepadButtons"/> for the mapping.
|
||||
/// </summary>
|
||||
[FieldOffset(0x9C)]
|
||||
public ushort ButtonFlag_Tap; // bitfield
|
||||
public ushort ButtonsPressed; // bitfield
|
||||
|
||||
// Gets populated on button release
|
||||
/// <summary>
|
||||
/// Button released input, set once right after the button is not hold anymore. See <see cref="GamepadButtons"/> for the mapping.
|
||||
/// </summary>
|
||||
[FieldOffset(0xA0)]
|
||||
public ushort ButtonFlag_Release; // bitfield
|
||||
public ushort ButtonsReleased; // bitfield
|
||||
|
||||
// Gets populated after a tick and keeps being set while button is held
|
||||
/// <summary>
|
||||
/// Repeatedly emits the held button input in fixed intervals. See <see cref="GamepadButtons"/> for the mapping.
|
||||
/// </summary>
|
||||
[FieldOffset(0xA4)]
|
||||
public ushort ButtonFlag_Holding; // bitfield
|
||||
public ushort ButtonsRepeat; // bitfield
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ThirdRepoSetting> 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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue