mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Merge pull request #328 from fitzchivalrik/feature/ControllerNavigation
This commit is contained in:
commit
2a79ac58e0
11 changed files with 642 additions and 3 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,11 @@ 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>
|
||||
/// Provides access to client conditions/player state. Allows you to check if a player is in a duty, mounted, etc.
|
||||
/// </summary>
|
||||
|
|
@ -131,6 +136,8 @@ namespace Dalamud.Game.ClientState
|
|||
|
||||
this.KeyState = new KeyState(Address, scanner.Module.BaseAddress);
|
||||
|
||||
this.GamepadState = new GamepadState(this.Address);
|
||||
|
||||
this.Condition = new Condition( Address );
|
||||
|
||||
this.Targets = new Targets(dalamud, Address);
|
||||
|
|
@ -150,6 +157,7 @@ namespace Dalamud.Game.ClientState
|
|||
}
|
||||
|
||||
public void Enable() {
|
||||
this.GamepadState.Enable();
|
||||
this.PartyList.Enable();
|
||||
this.setupTerritoryTypeHook.Enable();
|
||||
}
|
||||
|
|
@ -158,6 +166,7 @@ namespace Dalamud.Game.ClientState
|
|||
this.PartyList.Dispose();
|
||||
this.setupTerritoryTypeHook.Dispose();
|
||||
this.Actors.Dispose();
|
||||
this.GamepadState.Dispose();
|
||||
|
||||
this.dalamud.Framework.OnUpdateEvent -= FrameworkOnOnUpdateEvent;
|
||||
this.dalamud.NetworkHandlers.CfPop += NetworkHandlersOnCfPop;
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
96
Dalamud/Game/ClientState/GamepadButtons.cs
Normal file
96
Dalamud/Game/ClientState/GamepadButtons.cs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.ClientState
|
||||
{
|
||||
/// <summary>
|
||||
/// Bitmask of the Button ushort used by the game.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum GamepadButtons : ushort
|
||||
{
|
||||
/// <summary>
|
||||
/// No buttons pressed.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <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,
|
||||
}
|
||||
}
|
||||
270
Dalamud/Game/ClientState/GamepadState.cs
Normal file
270
Dalamud/Game/ClientState/GamepadState.cs
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
using System;
|
||||
|
||||
using Dalamud.Game.ClientState.Structs;
|
||||
using Dalamud.Hooking;
|
||||
using ImGuiNET;
|
||||
using Serilog;
|
||||
|
||||
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;
|
||||
|
||||
private bool isDisposed;
|
||||
|
||||
private int leftStickX;
|
||||
private int leftStickY;
|
||||
private int rightStickX;
|
||||
private int rightStickY;
|
||||
|
||||
/// <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)
|
||||
{
|
||||
#if DEBUG
|
||||
Log.Verbose("GamepadPoll address {GamepadPoll}", resolver.GamepadPoll);
|
||||
#endif
|
||||
this.gamepadPoll = new Hook<ControllerPoll>(
|
||||
resolver.GamepadPoll,
|
||||
(ControllerPoll)this.GamepadPollDetour);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="GamepadState" /> class.
|
||||
/// </summary>
|
||||
~GamepadState()
|
||||
{
|
||||
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 or sets a value indicating whether detour should block gamepad input for game.
|
||||
///
|
||||
/// Ideally, we would use
|
||||
/// (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0
|
||||
/// but this has a race condition during load with the detour which sets up ImGui
|
||||
/// and throws if our detour gets called before the other.
|
||||
/// </summary>
|
||||
internal bool NavEnableGamepad { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether <paramref name="button"/> has been pressed.
|
||||
///
|
||||
/// Only true on first frame of the press.
|
||||
/// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable.
|
||||
/// </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.
|
||||
/// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable.
|
||||
/// </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.
|
||||
/// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable.
|
||||
/// </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)
|
||||
{
|
||||
var original = this.gamepadPoll.Original(gamepadInput);
|
||||
try
|
||||
{
|
||||
#if DEBUG
|
||||
this.GamepadInput = gamepadInput;
|
||||
#endif
|
||||
var input = (GamepadInput*)gamepadInput;
|
||||
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.NavEnableGamepad)
|
||||
{
|
||||
input->LeftStickX = 0;
|
||||
input->LeftStickY = 0;
|
||||
input->RightStickX = 0;
|
||||
input->RightStickY = 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 but its hella confusing to the user, so we do (a) and advise plugins do not rely on
|
||||
// `ButtonPressed` while ImGuiConfigFlags.NavEnableGamepad is set.
|
||||
// This is debatable.
|
||||
// ImGui itself does not care either way as it uses the Raw values and does its own state handling.
|
||||
const ushort deletionMask = (ushort)(~GamepadButtons.L2
|
||||
& ~GamepadButtons.R2
|
||||
& ~GamepadButtons.DpadDown
|
||||
& ~GamepadButtons.DpadLeft
|
||||
& ~GamepadButtons.DpadUp
|
||||
& ~GamepadButtons.DpadRight);
|
||||
input->ButtonsRaw &= deletionMask;
|
||||
input->ButtonsPressed = 0;
|
||||
input->ButtonsReleased = 0;
|
||||
input->ButtonsRepeat = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"Gamepad Poll detour critical error! Gamepad navigation will not work!");
|
||||
|
||||
// NOTE (Chiv) Explicitly deactivate on error
|
||||
ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableGamepad;
|
||||
return original;
|
||||
}
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (this.isDisposed) return;
|
||||
if (disposing)
|
||||
{
|
||||
this.gamepadPoll?.Disable();
|
||||
this.gamepadPoll?.Dispose();
|
||||
}
|
||||
|
||||
this.isDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
64
Dalamud/Game/ClientState/Structs/GamepadInput.cs
Normal file
64
Dalamud/Game/ClientState/Structs/GamepadInput.cs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Structs
|
||||
{
|
||||
/// <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
|
||||
{
|
||||
/// <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;
|
||||
|
||||
/// <summary>
|
||||
/// Raw input, set the whole time while a button is held. See <see cref="GamepadButtons"/> for the mapping.
|
||||
/// </summary>
|
||||
[FieldOffset(0x98)]
|
||||
public ushort ButtonsRaw; // bitfield
|
||||
|
||||
/// <summary>
|
||||
/// Button pressed, set once when the button is pressed. See <see cref="GamepadButtons"/> for the mapping.
|
||||
/// </summary>
|
||||
[FieldOffset(0x9C)]
|
||||
public ushort ButtonsPressed; // bitfield
|
||||
|
||||
/// <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 ButtonsReleased; // bitfield
|
||||
|
||||
/// <summary>
|
||||
/// Repeatedly emits the held button input in fixed intervals. See <see cref="GamepadButtons"/> for the mapping.
|
||||
/// </summary>
|
||||
[FieldOffset(0xA4)]
|
||||
public ushort ButtonsRepeat; // bitfield
|
||||
}
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ namespace Dalamud.Interface
|
|||
private string[] dataKinds = new[]
|
||||
{
|
||||
"ServerOpCode", "Address", "Actor Table", "Font Test", "Party List", "Plugin IPC", "Condition",
|
||||
"Gauge", "Command", "Addon", "Addon Inspector", "StartInfo", "Target", "Toast", "ImGui", "Tex",
|
||||
"Gauge", "Command", "Addon", "Addon Inspector", "StartInfo", "Target", "Toast", "ImGui", "Tex", "Gamepad",
|
||||
};
|
||||
|
||||
private bool drawActors = false;
|
||||
|
|
@ -391,6 +391,60 @@ namespace Dalamud.Interface
|
|||
}
|
||||
|
||||
break;
|
||||
|
||||
// Gamepad
|
||||
case 16:
|
||||
Action<string, uint, Func<GamepadButtons, float>> helper = (text, mask, resolve) =>
|
||||
{
|
||||
ImGui.Text($"{text} {mask:X4}");
|
||||
ImGui.Text($"DPadLeft {resolve(GamepadButtons.DpadLeft)} " +
|
||||
$"DPadUp {resolve(GamepadButtons.DpadUp)} " +
|
||||
$"DPadRight {resolve(GamepadButtons.DpadRight)} " +
|
||||
$"DPadDown {resolve(GamepadButtons.DpadDown)} ");
|
||||
ImGui.Text($"West {resolve(GamepadButtons.West)} " +
|
||||
$"North {resolve(GamepadButtons.North)} " +
|
||||
$"East {resolve(GamepadButtons.East)} " +
|
||||
$"South {resolve(GamepadButtons.South)} ");
|
||||
ImGui.Text($"L1 {resolve(GamepadButtons.L1)} " +
|
||||
$"L2 {resolve(GamepadButtons.L2)} " +
|
||||
$"R1 {resolve(GamepadButtons.R1)} " +
|
||||
$"R2 {resolve(GamepadButtons.R2)} ");
|
||||
ImGui.Text($"Select {resolve(GamepadButtons.Select)} " +
|
||||
$"Start {resolve(GamepadButtons.Start)} " +
|
||||
$"L3 {resolve(GamepadButtons.L3)} " +
|
||||
$"R3 {resolve(GamepadButtons.R3)} ");
|
||||
};
|
||||
#if DEBUG
|
||||
ImGui.Text($"GamepadInput {this.dalamud.ClientState.GamepadState.GamepadInput.ToString("X")}");
|
||||
if (ImGui.IsItemHovered()) ImGui.SetMouseCursor(ImGuiMouseCursor.Hand);
|
||||
if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"{this.dalamud.ClientState.GamepadState.GamepadInput.ToString("X")}");
|
||||
#endif
|
||||
|
||||
helper(
|
||||
"Buttons Raw",
|
||||
this.dalamud.ClientState.GamepadState.ButtonsRaw,
|
||||
this.dalamud.ClientState.GamepadState.Raw);
|
||||
helper(
|
||||
"Buttons Pressed",
|
||||
this.dalamud.ClientState.GamepadState.ButtonsPressed,
|
||||
this.dalamud.ClientState.GamepadState.Pressed);
|
||||
helper(
|
||||
"Buttons Repeat",
|
||||
this.dalamud.ClientState.GamepadState.ButtonsRepeat,
|
||||
this.dalamud.ClientState.GamepadState.Repeat);
|
||||
helper(
|
||||
"Buttons Released",
|
||||
this.dalamud.ClientState.GamepadState.ButtonsReleased,
|
||||
this.dalamud.ClientState.GamepadState.Released);
|
||||
ImGui.Text($"LeftStickLeft {this.dalamud.ClientState.GamepadState.LeftStickLeft:0.00} " +
|
||||
$"LeftStickUp {this.dalamud.ClientState.GamepadState.LeftStickUp:0.00} " +
|
||||
$"LeftStickRight {this.dalamud.ClientState.GamepadState.LeftStickRight:0.00} " +
|
||||
$"LeftStickDown {this.dalamud.ClientState.GamepadState.LeftStickDown:0.00} ");
|
||||
ImGui.Text($"RightStickLeft {this.dalamud.ClientState.GamepadState.RightStickLeft:0.00} " +
|
||||
$"RightStickUp {this.dalamud.ClientState.GamepadState.RightStickUp:0.00} " +
|
||||
$"RightStickRight {this.dalamud.ClientState.GamepadState.RightStickRight:0.00} " +
|
||||
$"RightStickDown {this.dalamud.ClientState.GamepadState.RightStickDown:0.00} ");
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ namespace Dalamud.Interface
|
|||
private readonly ComponentDemoWindow componentDemoWindow;
|
||||
private readonly ColorDemoWindow colorDemoWindow;
|
||||
private readonly ScratchpadWindow scratchpadWindow;
|
||||
private readonly GamepadModeNotifierWindow gamepadModeNotifierWindow;
|
||||
|
||||
private readonly WindowSystem windowSystem = new WindowSystem("DalamudCore");
|
||||
|
||||
|
|
@ -116,6 +117,9 @@ namespace Dalamud.Interface
|
|||
};
|
||||
this.windowSystem.AddWindow(this.scratchpadWindow);
|
||||
|
||||
this.gamepadModeNotifierWindow = new GamepadModeNotifierWindow();
|
||||
this.windowSystem.AddWindow(this.gamepadModeNotifierWindow);
|
||||
|
||||
Log.Information("[DUI] Windows added");
|
||||
|
||||
if (dalamud.Configuration.LogOpenAtStartup)
|
||||
|
|
@ -553,5 +557,13 @@ namespace Dalamud.Interface
|
|||
{
|
||||
this.scratchpadWindow.IsOpen ^= true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggle the gamepad notifier window window.
|
||||
/// </summary>
|
||||
internal void ToggleGamePadNotifierWindow()
|
||||
{
|
||||
this.gamepadModeNotifierWindow.IsOpen ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ namespace Dalamud.Interface
|
|||
{
|
||||
this.dalamud = dalamud;
|
||||
|
||||
this.Size = new Vector2(740, 500);
|
||||
this.Size = new Vector2(740, 550);
|
||||
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.\nToggle the PluginInstaller window via R3 if ImGui navigation is enabled."));
|
||||
|
||||
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();
|
||||
|
||||
|
|
|
|||
47
Dalamud/Interface/GamepadModeNotifierWindow.cs
Normal file
47
Dalamud/Interface/GamepadModeNotifierWindow.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
using System.Numerics;
|
||||
|
||||
using CheapLoc;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// Class responsible for drawing a notifier on screen that gamepad mode is active.
|
||||
/// </summary>
|
||||
internal class GamepadModeNotifierWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GamepadModeNotifierWindow"/> class.
|
||||
/// </summary>
|
||||
public GamepadModeNotifierWindow()
|
||||
: base(
|
||||
"###DalamudGamepadModeNotifier",
|
||||
ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoMouseInputs
|
||||
| ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoNav
|
||||
| ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoSavedSettings,
|
||||
true)
|
||||
{
|
||||
this.Size = Vector2.Zero;
|
||||
this.SizeCondition = ImGuiCond.Always;
|
||||
this.IsOpen = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a light grey-ish, main-viewport-big filled rect in the background draw list alongside a text indicating gamepad mode.
|
||||
/// </summary>
|
||||
public override void Draw()
|
||||
{
|
||||
var drawList = ImGui.GetBackgroundDrawList();
|
||||
drawList.PushClipRectFullScreen();
|
||||
drawList.AddRectFilled(Vector2.Zero, ImGuiHelpers.MainViewport.Size, 0x661A1A1A);
|
||||
drawList.AddText(
|
||||
Vector2.One,
|
||||
0xFFFFFFFF,
|
||||
Loc.Localize(
|
||||
"DalamudGamepadModeNotifierText",
|
||||
"Gamepad mode is ON. Press R1+L3 to deactivate, press R3 to toggle PluginInstaller."));
|
||||
drawList.PopClipRect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,6 +292,21 @@ 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;
|
||||
}
|
||||
|
||||
// NOTE (Chiv) Explicitly deactivate on dalamud boot
|
||||
ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableGamepad;
|
||||
|
||||
ImGuiHelpers.MainViewport = ImGui.GetMainViewport();
|
||||
|
||||
Log.Information("[IM] Scene & ImGui setup OK!");
|
||||
|
|
@ -461,6 +477,45 @@ namespace Dalamud.Interface
|
|||
}
|
||||
|
||||
// TODO: mouse state?
|
||||
|
||||
var gamepadEnabled = (ImGui.GetIO().BackendFlags & ImGuiBackendFlags.HasGamepad) > 0;
|
||||
|
||||
// NOTE (Chiv) Activate ImGui navigation via L1+L3 press
|
||||
// (mimicking 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.ClientState.GamepadState.NavEnableGamepad ^= true;
|
||||
this.dalamud.DalamudUi.ToggleGamePadNotifierWindow();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (this.dalamud.ClientState.GamepadState.Pressed(GamepadButtons.R3) > 0)
|
||||
{
|
||||
this.dalamud.DalamudUi.TogglePluginInstaller();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Display()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue