Keyboard and Xbox-gamepad input drive the panel

New input pipeline: sources feed an InputRouter (VRio.Core.Input) that
calls the same VRioDevice press/release/axis entry points the mouse
canvas uses, so routed input is indistinguishable on the wire.

- Bindings live in %APPDATA%\vRIO\bindings.txt (plain text, commented
  defaults written on first run; Reload/Edit buttons in the new Input
  group). Keys and pad buttons press any RIO address, momentary or
  toggle; axes bind in normalized units of each axis realistic travel
  window with deflect (spring-back), rate (position holds), deadzone,
  and invert options - RioAxisRange supplies the wire signs.
- Router suppresses key auto-repeat, edge-detects pad buttons, and
  hold-counts per address so overlapping sources press once and
  release last; axes only write when the composed value changes, so
  mouse drags keep working while sources idle.
- Xbox pad via a zero-dependency XInput P/Invoke poller (xinput1_4,
  fallback xinput9_1_0), throttled rescan while disconnected.
- MainForm intercepts bound keys in ProcessCmdKey so arrows/space
  reach the panel instead of moving focus; keys release on focus
  loss; center-axes and host ResetRequest reset the rate integrators.
- Canvas highlights router-held addresses like clicks.
- Defaults: arrows=stick, W/S=throttle, Q/E=pedals, numpad=internal
  keypad, IJKL/Space=hat+main; pad left stick/triggers/right stick =
  stick/pedals/throttle-rate, ABXY/dpad/shoulders = named buttons.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-06 11:30:55 -05:00
co-authored by Claude Fable 5
parent 1f0781f06a
commit e590b89c47
9 changed files with 1315 additions and 6 deletions
+76
View File
@@ -0,0 +1,76 @@
namespace VRio.Core.Input;
/// <summary>
/// Gamepad button flags, bit-for-bit the XInput <c>wButtons</c> mask so the
/// App-side poller can cast the native value straight through.
/// </summary>
[Flags]
public enum PadButtons : ushort
{
None = 0,
DPadUp = 0x0001,
DPadDown = 0x0002,
DPadLeft = 0x0004,
DPadRight = 0x0008,
Start = 0x0010,
Back = 0x0020,
LeftThumb = 0x0040,
RightThumb = 0x0080,
LeftShoulder = 0x0100,
RightShoulder = 0x0200,
A = 0x1000,
B = 0x2000,
X = 0x4000,
Y = 0x8000,
}
/// <summary>A gamepad analog control usable as a binding source.</summary>
public enum PadAxis
{
LeftStickX,
LeftStickY,
RightStickX,
RightStickY,
LeftTrigger,
RightTrigger,
}
/// <summary>
/// One normalized gamepad snapshot: sticks 1..+1 (up/right positive),
/// triggers 0..1. The default value is "pad at rest / no pad connected".
/// </summary>
public readonly struct PadState
{
public PadState(PadButtons buttons,
float leftStickX, float leftStickY, float rightStickX, float rightStickY,
float leftTrigger, float rightTrigger)
{
Buttons = buttons;
LeftStickX = leftStickX;
LeftStickY = leftStickY;
RightStickX = rightStickX;
RightStickY = rightStickY;
LeftTrigger = leftTrigger;
RightTrigger = rightTrigger;
}
public PadButtons Buttons { get; }
public float LeftStickX { get; }
public float LeftStickY { get; }
public float RightStickX { get; }
public float RightStickY { get; }
public float LeftTrigger { get; }
public float RightTrigger { get; }
/// <summary>Value of one analog control.</summary>
public float Axis(PadAxis axis) => axis switch
{
PadAxis.LeftStickX => LeftStickX,
PadAxis.LeftStickY => LeftStickY,
PadAxis.RightStickX => RightStickX,
PadAxis.RightStickY => RightStickY,
PadAxis.LeftTrigger => LeftTrigger,
PadAxis.RightTrigger => RightTrigger,
_ => 0f,
};
}