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
+69
View File
@@ -0,0 +1,69 @@
using VRio.Core.Device;
namespace VRio.Core.Input;
/// <summary>
/// How a key drives an axis: <see cref="Deflect"/> holds the axis at the
/// binding's value while the key is down and springs back when released
/// (stick, pedals); <see cref="Rate"/> walks the axis by value-per-second
/// while held and the position sticks (throttle).
/// </summary>
public enum KeyAxisMode
{
Deflect,
Rate,
}
/// <summary>A keyboard key pressing a RIO input address.</summary>
/// <param name="Key">Key name as the host reports it (System.Windows.Forms
/// Keys.ToString(): "W", "Up", "NumPad1", …); matched case-insensitively.</param>
/// <param name="Address">RIO input address (button 0x000x47 or keypad 0x500x6F).</param>
/// <param name="Toggle">Latch on press instead of momentary press/release.</param>
public sealed record KeyButtonBinding(string Key, int Address, bool Toggle);
/// <summary>
/// A keyboard key driving a RIO axis in normalized units, where 1 is the
/// axis' full realistic travel (<see cref="RioAxisRange.Full"/> carries the
/// wire sign, so bindings never deal with the throttle's negative direction).
/// </summary>
/// <param name="Value">Deflect: the normalized position held while the key is
/// down. Rate: normalized units per second, signed.</param>
public sealed record KeyAxisBinding(string Key, RioAxis Axis, KeyAxisMode Mode, float Value);
/// <summary>A gamepad button pressing a RIO input address.</summary>
public sealed record PadButtonBinding(PadButtons Button, int Address, bool Toggle);
/// <summary>
/// A gamepad analog control driving a RIO axis. With <paramref name="Rate"/>
/// zero the (deadzoned, optionally inverted) pad value maps directly to the
/// normalized axis position; with a positive rate the pad value is a speed —
/// the axis integrates by value × rate per second and holds (throttle-style).
/// </summary>
public sealed record PadAxisBinding(PadAxis Source, RioAxis Axis, bool Invert, float Deadzone, float Rate);
/// <summary>An immutable set of input bindings (one parsed profile file).</summary>
public sealed class BindingProfile
{
public static readonly BindingProfile Empty = new(
Array.Empty<KeyButtonBinding>(), Array.Empty<KeyAxisBinding>(),
Array.Empty<PadButtonBinding>(), Array.Empty<PadAxisBinding>());
public BindingProfile(
IReadOnlyList<KeyButtonBinding> keyButtons,
IReadOnlyList<KeyAxisBinding> keyAxes,
IReadOnlyList<PadButtonBinding> padButtons,
IReadOnlyList<PadAxisBinding> padAxes)
{
KeyButtons = keyButtons;
KeyAxes = keyAxes;
PadButtons = padButtons;
PadAxes = padAxes;
}
public IReadOnlyList<KeyButtonBinding> KeyButtons { get; }
public IReadOnlyList<KeyAxisBinding> KeyAxes { get; }
public IReadOnlyList<PadButtonBinding> PadButtons { get; }
public IReadOnlyList<PadAxisBinding> PadAxes { get; }
public int Count => KeyButtons.Count + KeyAxes.Count + PadButtons.Count + PadAxes.Count;
}