From 41f6ef364d25a15b1a08770a8386932a058f4069 Mon Sep 17 00:00:00 2001 From: Cyd Date: Mon, 6 Jul 2026 18:46:02 -0500 Subject: [PATCH] Invert-Y input toggle; git-stamped version in the window title - InputRouter.InvertJoystickY flips the composed joystick Y (keys and pad alike) before wire scaling; panel drags write the device directly and stay untouched. New "Invert joystick Y" checkbox in the Input group, default on. - StampGitVersion target bakes "YYYY.MM.DD (shortsha)" of HEAD into InformationalVersion; the title shows it via Application.ProductVersion so a running build can be matched to its vYYYY.MM.DD release tag. Co-Authored-By: Claude Fable 5 --- src/VRio.App/MainForm.cs | 28 +++++++++++++---------- src/VRio.App/VRio.App.csproj | 25 ++++++++++++++++++++ src/VRio.Core/Input/InputRouter.cs | 9 ++++++++ tests/VRio.Core.Tests/InputRouterTests.cs | 20 ++++++++++++++++ tests/VRio.Core.Tests/VRioDeviceTests.cs | 3 ++- 5 files changed, 72 insertions(+), 13 deletions(-) diff --git a/src/VRio.App/MainForm.cs b/src/VRio.App/MainForm.cs index 6c210ef..5fd415e 100644 --- a/src/VRio.App/MainForm.cs +++ b/src/VRio.App/MainForm.cs @@ -49,24 +49,25 @@ internal sealed class MainForm : Form private readonly CheckBox _kbInput = new() { Text = "Keyboard", Location = new Point(10, 22), AutoSize = true, Checked = true }; private readonly CheckBox _padInput = new() { Text = "Xbox gamepad", Location = new Point(120, 22), AutoSize = true, Checked = true }; + private readonly CheckBox _invertY = new() { Text = "Invert joystick Y", Location = new Point(10, 46), AutoSize = true, Checked = true }; private readonly Label _padStatus = new() { Text = "No controller detected.", - Location = new Point(10, 46), + Location = new Point(10, 70), AutoSize = true, ForeColor = Color.Gray, }; - private readonly Button _reloadBindings = new() { Text = "Reload bindings", Location = new Point(10, 68), Width = 140, Height = 26 }; - private readonly Button _editBindings = new() { Text = "Edit bindings…", Location = new Point(156, 68), Width = 140, Height = 26 }; + private readonly Button _reloadBindings = new() { Text = "Reload bindings", Location = new Point(10, 92), Width = 140, Height = 26 }; + private readonly Button _editBindings = new() { Text = "Edit bindings…", Location = new Point(156, 92), Width = 140, Height = 26 }; private readonly CheckBox _kbLights = new() { Text = "Mirror lamps on RGB keyboard (Dynamic Lighting)", - Location = new Point(10, 100), + Location = new Point(10, 124), AutoSize = true, }; private readonly ComboBox _kbLightsTarget = new() { - Location = new Point(10, 122), + Location = new Point(10, 146), Width = 286, DropDownStyle = ComboBoxStyle.DropDownList, Enabled = false, // populated while the mirror is on @@ -80,14 +81,14 @@ internal sealed class MainForm : Form private readonly Label _counters = new() { - Location = new Point(12, 390), + Location = new Point(12, 414), AutoSize = true, Font = new Font("Consolas", 8f), }; private readonly Label _help = new() { - Location = new Point(12, 446), + Location = new Point(12, 470), MaximumSize = new Size(306, 0), AutoSize = true, ForeColor = Color.Gray, @@ -98,7 +99,7 @@ internal sealed class MainForm : Form private readonly TextBox _logBox = new() { - Location = new Point(12, 530), + Location = new Point(12, 554), Multiline = true, ReadOnly = true, ScrollBars = ScrollBars.Both, // long wire lines don't wrap — scroll to read @@ -122,7 +123,8 @@ internal sealed class MainForm : Form public MainForm() { - Text = "vRIO — Virtual RIO cockpit device"; + // ProductVersion carries the git stamp (see StampGitVersion in the csproj). + Text = $"vRIO v{Application.ProductVersion} — Virtual RIO cockpit device"; // Fit the window to its content: the cockpit canvas plus the 330px // control strip, with just enough height for the strip's log area. ClientSize = new Size(_canvas.Width + 332, Math.Max(_canvas.Height, 640)); @@ -188,6 +190,8 @@ internal sealed class MainForm : Form if (!_kbInput.Checked) _router.ReleaseAllKeys(); }; + _invertY.CheckedChanged += (_, _) => _router.InvertJoystickY = _invertY.Checked; + _router.InvertJoystickY = _invertY.Checked; // the field initializer raises no event _kbLights.CheckedChanged += (_, _) => { _lampMirror.Enabled = _kbLights.Checked; @@ -250,13 +254,13 @@ internal sealed class MainForm : Form device.Controls.AddRange(new Control[] { _spring, _centerAxes, _lampsOff, _testEnter, _testExit }); panel.Controls.Add(device); - var input = new GroupBox { Text = "Input", Location = new Point(12, 224), Size = new Size(306, 158) }; - input.Controls.AddRange(new Control[] { _kbInput, _padInput, _padStatus, _reloadBindings, _editBindings, _kbLights, _kbLightsTarget }); + var input = new GroupBox { Text = "Input", Location = new Point(12, 224), Size = new Size(306, 182) }; + input.Controls.AddRange(new Control[] { _kbInput, _padInput, _invertY, _padStatus, _reloadBindings, _editBindings, _kbLights, _kbLightsTarget }); panel.Controls.Add(input); panel.Controls.Add(_counters); panel.Controls.Add(_help); - panel.Controls.Add(new Label { Text = "Wire log:", Location = new Point(12, 512), AutoSize = true }); + panel.Controls.Add(new Label { Text = "Wire log:", Location = new Point(12, 536), AutoSize = true }); _logBox.Size = new Size(306, ClientSize.Height - _logBox.Top - 44); panel.Controls.Add(_logBox); diff --git a/src/VRio.App/VRio.App.csproj b/src/VRio.App/VRio.App.csproj index e755852..d2e9355 100644 --- a/src/VRio.App/VRio.App.csproj +++ b/src/VRio.App/VRio.App.csproj @@ -9,12 +9,37 @@ latest app.manifest vRIO — Virtual RIO device + + false + + + + + + + + + + + + + $(GitCommitDate.Trim().Replace('-', '.')) ($(GitShortSha)) + + + all diff --git a/src/VRio.Core/Input/InputRouter.cs b/src/VRio.Core/Input/InputRouter.cs index fac9776..f00221b 100644 --- a/src/VRio.Core/Input/InputRouter.cs +++ b/src/VRio.Core/Input/InputRouter.cs @@ -57,6 +57,13 @@ public sealed class InputRouter /// A routed address went down (true) or came back up (false) — for panel highlighting. public event Action? AddressHeldChanged; + /// + /// Flip the sign of the composed joystick Y before it reaches the device. + /// Applies to all routed sources (keys and pad) alike; panel drags write + /// the device directly and are unaffected. + /// + public bool InvertJoystickY { get; set; } + /// /// The active bindings. Assigning releases everything currently held /// (keys, toggles, pad buttons) so a reload never strands a pressed input. @@ -216,6 +223,8 @@ public sealed class InputRouter if (b.Rate <= 0f) total += Shape(_pad.Axis(b.Source), b); } + if (InvertJoystickY && axis == RioAxis.JoystickY) + total = -total; total = ClampNorm(axis, total); short raw = (short)Math.Round(total * RioAxisRange.Full(axis)); diff --git a/tests/VRio.Core.Tests/InputRouterTests.cs b/tests/VRio.Core.Tests/InputRouterTests.cs index 2be571e..64d5863 100644 --- a/tests/VRio.Core.Tests/InputRouterTests.cs +++ b/tests/VRio.Core.Tests/InputRouterTests.cs @@ -191,6 +191,26 @@ public class InputRouterTests Assert.Equal(0, _device.GetAxis(RioAxis.JoystickY)); } + [Fact] + public void InvertJoystickY_flips_the_composed_value_for_all_sources() + { + UseProfile("key Up axis JoystickY deflect 1\npadaxis LeftStickY axis JoystickY"); + _router.InvertJoystickY = true; + + _router.KeyDown("Up"); + _router.Tick(0.016); + Assert.Equal(-RioAxisRange.JoystickExtent, _device.GetAxis(RioAxis.JoystickY)); + _router.KeyUp("Up"); + + _router.SetPadState(new PadState(PadButtons.None, 0, 0.5f, 0, 0, 0, 0)); + _router.Tick(0.016); + Assert.Equal(-RioAxisRange.JoystickExtent / 2, _device.GetAxis(RioAxis.JoystickY)); + + _router.InvertJoystickY = false; // toggling back re-sends the upright value + _router.Tick(0.016); + Assert.Equal(RioAxisRange.JoystickExtent / 2, _device.GetAxis(RioAxis.JoystickY)); + } + [Fact] public void Rate_key_walks_the_throttle_and_position_sticks() { diff --git a/tests/VRio.Core.Tests/VRioDeviceTests.cs b/tests/VRio.Core.Tests/VRioDeviceTests.cs index a98688b..d8ee2da 100644 --- a/tests/VRio.Core.Tests/VRioDeviceTests.cs +++ b/tests/VRio.Core.Tests/VRioDeviceTests.cs @@ -48,6 +48,7 @@ public class VRioDeviceTests var device = new VRioDevice(); var wire = new Wire(device); device.SetAxis(RioAxis.Throttle, 1000); + device.SetAxis(RioAxis.JoystickY, 3000); device.SetAxis(RioAxis.JoystickX, -5000); Send(device, PacketBuilder.Build(RioCommand.AnalogRequest)); @@ -60,7 +61,7 @@ public class VRioDeviceTests Assert.Equal(1000, AnalogCodec.Combine(p[0], p[1])); // throttle Assert.Equal(0, AnalogCodec.Combine(p[2], p[3])); // left pedal Assert.Equal(0, AnalogCodec.Combine(p[4], p[5])); // right pedal - Assert.Equal(0, AnalogCodec.Combine(p[6], p[7])); // joystick Y + Assert.Equal(3000, AnalogCodec.Combine(p[6], p[7])); // joystick Y Assert.Equal(-5000, AnalogCodec.Combine(p[8], p[9])); // joystick X Assert.Equal(1, device.AnalogRequests); }