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 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-06 18:46:02 -05:00
co-authored by Claude Fable 5
parent f77cd55b11
commit 41f6ef364d
5 changed files with 72 additions and 13 deletions
+16 -12
View File
@@ -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);
+25
View File
@@ -9,12 +9,37 @@
<LangVersion>latest</LangVersion>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AssemblyTitle>vRIO — Virtual RIO device</AssemblyTitle>
<!-- StampGitVersion already puts the sha in InformationalVersion; stop the
SDK appending "+fullsha" on top of it. -->
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\VRio.Core\VRio.Core.csproj" />
</ItemGroup>
<!-- Stamp commit date + short sha into InformationalVersion; the window
title shows it (Application.ProductVersion) so a running build can be
matched to its vYYYY.MM.DD release tag at a glance. Falls back to the
default 1.0.0 when git isn't available. -->
<Target Name="StampGitVersion" BeforeTargets="GetAssemblyVersion" Condition="'$(DesignTimeBuild)' != 'true'">
<!-- %%cs survives cmd's percent expansion as %cs: committer date, YYYY-MM-DD. -->
<Exec Command="git -C &quot;$(MSBuildProjectDirectory)&quot; log -1 --format=%%cs"
ConsoleToMSBuild="true" StandardOutputImportance="low" IgnoreExitCode="true" ContinueOnError="true">
<Output TaskParameter="ConsoleOutput" PropertyName="GitCommitDate" />
<Output TaskParameter="ExitCode" PropertyName="GitDateExitCode" />
</Exec>
<!-- The exclude flag keeps describe off the release tags: always the short
sha, with a "dirty" suffix when the working tree has local edits. -->
<Exec Command="git -C &quot;$(MSBuildProjectDirectory)&quot; describe --always --dirty --exclude=*"
ConsoleToMSBuild="true" StandardOutputImportance="low" IgnoreExitCode="true" ContinueOnError="true">
<Output TaskParameter="ConsoleOutput" PropertyName="GitShortSha" />
</Exec>
<PropertyGroup Condition="'$(GitDateExitCode)' == '0' And '$(GitShortSha)' != ''">
<InformationalVersion>$(GitCommitDate.Trim().Replace('-', '.')) ($(GitShortSha))</InformationalVersion>
</PropertyGroup>
</Target>
<ItemGroup>
<PackageReference Include="PolySharp" Version="1.14.1">
<PrivateAssets>all</PrivateAssets>
+9
View File
@@ -57,6 +57,13 @@ public sealed class InputRouter
/// <summary>A routed address went down (true) or came back up (false) — for panel highlighting.</summary>
public event Action<int, bool>? AddressHeldChanged;
/// <summary>
/// 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.
/// </summary>
public bool InvertJoystickY { get; set; }
/// <summary>
/// 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));
+20
View File
@@ -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()
{
+2 -1
View File
@@ -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);
}